1

I don't understand the shift in following code:

    #! /usr/local/bin/bash
    # process command line options
    interactive=
    filename=
    while [[ -n $1 ]]; do
        case $1 in 
            -f | --file)    shift #don't understand the shift #No.1
                            filename=$1 ;;
            -i | --interactive) interactive=1
                                ;;
            -h | --help)    usage
                            exit;;
            *)              echo "usage >&2 exit 1";;
        esac
        shift # don't understand the shift #2
    done

    #interactive mode
    if [[ -n $interactive ]]; then
       echo "do something"
    fi
    #output html page
    if [[ -n $filename ]]; then
        if touch $filename && [[ -f $filename ]]; then
           echo "write_html_page > $filename" #debug 
        else
            echo "$program: Cannot write file $filename " >&2
            exit 1
        fi
    else
        echo "write_html_page to terminal" # debug
    fi

Test it

    $ bash question.sh -f test
    write_html_page > test

    $ bash question.sh -f
    write_html_page to terminal

When I delete shift and change filename=$1 to filename=$2

    $ bash question.sh -f
    write_html_page to terminal
  # it works properly

    $ bash question.sh -f test
    usage >&2 exit 1
    write_html_page > test 
  # it almost function  nicely except that `usage >&2 exit 1` is executed.

So the shift cannot be replaced by filename=$2 entirely.

The second shift at the botton if deleted, the loop run endlessly.

Could I interpret shift intuitively?
I did't find such a magic command in other languages.

AbstProcDo
  • 19,953
  • 19
  • 81
  • 138
  • `shift` allows the code above to parse the command line in a loop without caring about the order of the options (`-f`, `-i`, `-h` etc). Using `shift`, the loop consumes one (or two) command line parameters on each iteration (the parameters that were already interpreted). – axiac Apr 11 '18 at 15:55

2 Answers2

3

shift will remove the first positional argument, and shift every other argument left one.

For example, let's consider the following:

#!/bin/bash
echo "$@"
shift
echo "$@"
shift
echo "$@"

Given that echo "$@" will print all of the arguments, if you were to run this, then the following would happen:

./test.bash 1 2 3 4 5

echo "$@"  # prints 1 2 3 4 5
shift      # Removes 1 and shifts everything else along
echo "$@"  # prints 2 3 4 5
shift      # shifting again
echo "$@"  # prints 3 4 5

In your example, the script is parsing all of the flags. -i and -h are just switches and handle no following arguments. However, -f requires a filename.

The second shift will process the flag, shift the arguments, and then process them again. Therefore you can have ./program.bash -i -f filename. The -i will be shifted by the second shift, and then the filename will be processed on the next loop.

If you were to run ./program.bash -f filename -i, then the filename would need to be shifted along with the -f. Therefore, on the case block for -f there is an extra shift. In this example, -f would be shifted inside the case block, and then filename would be shifted by the second shift. Then the loop would run again to process any further flags.

As the while loop is [[ -n $1 ]], the loop will run until there are no more arguments.

Robert Seaman
  • 2,432
  • 15
  • 18
  • Does the `shift` within `case` not effect the value on `while [[ -n $1 ]]`. – AbstProcDo Apr 11 '18 at 14:55
  • ` -f | --file) shift` this shift change the value of `while [[ -n $1 ]]`, ty, I got your ideas. It's like popleft a array. – AbstProcDo Apr 11 '18 at 14:56
  • Yes - just like a popleft array. but you need to pop twice with `-f`. Given that the second shift pops on every iteration, we just need to pop one more time for `-f` thus why the first shift is required also. – Robert Seaman Apr 11 '18 at 14:57
2

The example on this page:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_07.html

explains what it's doing.

EDIT:

The example:

A shift statement is typically used when the number of arguments to a command is not known in advance, for instance when users can give as many arguments as they like. In such cases, the arguments are usually processed in a while loop with a test condition of (( $# )). This condition is true as long as the number of arguments is greater than zero. The $1 variable and the shift statement process each argument. The number of arguments is reduced each time shift is executed and eventually becomes zero, upon which the while loop exits.

KdgDev
  • 14,299
  • 46
  • 120
  • 156
Koen De Groote
  • 111
  • 1
  • 8