13 Nov 2015
Shifting through Parameters
Using the shift
command to work through command-line arguments
Is it 'parameters' or 'arguments'? Those words that you add to a command: "ls -l foo bar
". For the purposes of this article, I'll use them both equally. Technically, "-l
" is a switch, and foo
and bar
are arguments. Or possibly parameters. Anyway, this post is about the much simpler scripts one sometimes needs to write, which don't merit all the glory of getopts
, and just take some information from the command line.
The shift
command is a very useful one for getting rid of command-line arguments once they have been parsed.
For example, if you want to write a script which will take a host, username and password (which might contain spaces), you might write this:
#!/bin/bash HOST=$1 USERNAME=$2 PASSWORD=$3 echo "Host is $HOST ; Username is $USERNAME ; Password is $PASSWORD"
However, if it was called with arguments of "node27 appuser foo bar", then you might expect that appuser
on node27
would get a password of "foo bar
". We know that hostnames can't contain spaces, and usernames don't tend to contain spaces, either, but a password could well contain a space. Disappointingly, this script would say:
Host is node27 ; USERNAME is appuser ; PASSWORD is foo
It missed the final word. So you can update your code like this:
#!/bin/bash HOST=$1 USERNAME=$2 shift 2 PASSWORD=$@
Here, we grab the first two arguments - the hostname and the username, and by running "shift 2
", we ditch those two (shift 2
) parameters, and the $PASSWORD
variable gets set to whatever arguments are left on the commandline - in this case, it's "foo bar
". That way, it doesn't matter how many spaces are in the password, we don't have to say "PASSWORD="$3 $4 $5 $6"
" and hope that there aren't more than three spaces in the password, we just say that all remaining arguments (which is what "$@
" gives us) comprise the password.
So that's good, and a useful trick to know.
However.
What if you call this script with just "node27
"?. Or as "node27 appuser
"?
The shift
command will try to push two arguments off the command-line. If it can't do that, because there aren't enough in the first place, then it just won't do anything at all.
At first, everything looks fine:
$ ./shift.sh node27 appuser foo bar Host is node27 ; Username is appuser ; Password is foo bar $ ./shift.sh node27 appuser foo Host is node27 ; Username is appuser ; Password is foo $ ./shift.sh node27 appuser Host is node27 ; Username is appuser ; Password is $
However, if you drop off the username, then there is only one argument, "$1
", the hostname. The shift
command lets us down badly here:
$ ./shift.sh node27 Host is node27 ; Username is ; Password is node27 $
Although the USERNAME
variable is - correctly - undefined, somehow the third, PASSWORD
variable, has picked up the value of "$1
". What is happening?!
Because "shift 2
" failed to shift two arguments - there weren't two arguments to shift - it simply failed to do anything at all. So setting "PASSWORD=$@
" meant that PASSWORD
got set to the original set of arguments to the script, which was just the host name.
To get around this, if you want to shift
by a specific number of arguments, then call shift
each time:
#!/bin/bash HOST=$1 USERNAME=$2 shift # drop the HOST shift # drop the USERNAME PASSWORD=$@ # PASSWORD is whatever remains
This works as expected:
$ ./shift.sh node27 appuser foo bar Host is node27 ; Username is appuser ; Password is foo bar $ ./shift.sh node27 appuser foo Host is node27 ; Username is appuser ; Password is foo $ ./shift.sh node27 appuser Host is node27 ; Username is appuser ; Password is $ ./shift.sh node27 Host is node27 ; Username is ; Password is $
A single call of shift
will always drop the first argument; the bash
man
page says: "If n
is greater than $#
, the positional parameters are not changed." (where $#
is the number of arguments passed to the script, and n
is the argument passed to shift
).
If you wanted to drop a large (or variable) number of arguments, then you could always use a loop:
#!/bin/bash HOST=$1 USERNAME=$2 SKIP_COUNT=$3 echo "Host is $HOST ; Username is $USERNAME ; Skip Count is $SKIP_COUNT" for x in `seq 1 $SKIP_COUNT` do echo "Skipping something ($1) ..." shift done echo "Anything left is: $@"
This will skip the number of variables told in argument 3:
$ ./shift-some.sh node27 appuser 5 one two three four five six seven eight Host is node27 ; Username is appuser ; Max Skips is 5 Skipping something (node27) ... Skipping something (appuser) ... Skipping something (5) ... Skipping something (one) ... Skipping something (two) ... Anything left is: three four five six seven eight $ ./shift-some.sh node27 appuser 2 one two three four five six seven eight Host is node27 ; Username is appuser ; Max Skips is 2 Skipping something (node27) ... Skipping something (appuser) ... Anything left is: 2 one two three four five six seven eight $ ./shift-some.sh node27 appuser 7 one two three four five six seven eight Host is node27 ; Username is appuser ; Max Skips is 7 Skipping something (node27) ... Skipping something (appuser) ... Skipping something (7) ... Skipping something (one) ... Skipping something (two) ... Skipping something (three) ... Skipping something (four) ... Anything left is: five six seven eight $
Invest in your career. Buy my Shell Scripting Tutorial today: