This is an old revision of the document!
Handling positional parameters
Intro
The day will come when you want to give arguments to your scripts. These arguments are reflected as the positional parameters inside your script. Most relevant special parameters are described below:
Parameter(s) | Description |
---|---|
$0 | the first positional parameter, equivalent to argv[0] in C, see the first argument |
$FUNCNAME | the function name (attention: inside a function, $0 is still the $0 of the shell, not the function name) |
$1 … $9 | the argument list elements from 1 to 9 |
${10} … ${N} | the argument list elements beyond 9 (note the parameter expansion syntax!) |
$* | all positional parameters except $0 , see mass usage |
$@ | all positional parameters except $0 , see mass usage |
$# | the number of arguments, not counting $0 |
These positional parameters reflect exactly what was given to the script when it was called. There are no special things interpreted: Option-switch parsing (-h
for displaying help) is not done in this stage.
See also the dictionary entry for "parameter".
The first argument
The very first argument you can access is referenced by $0
. It usually is set to the script's name exactly like it was called, and it's set on shell initialization:
Testscript - it just echos $0
:
#!/bin/bash echo "$0"You see,
$0
is always set to however you call that script ($
is the prompt…):
> ./testscript ./testscript
> /usr/bin/testscript /usr/bin/testscript
However, this isn't true for login shells:
> echo "$0" -bash
Also, to be over-exact: $0
is not a positional parameter, it's a special parameter independent from the real parameter list. Also it really can be set to anything. In the ideal case it's the pathname of the script, but since this is set on invocation, the invoking program can easily influence it (the login
program does that for login shells, by prepending a dash, for example).
Inside a function, $o
still reflects what was told above. To get the function name, use $FUNCNAME
.
Shifting
The builtin command shift
is used to change the positional parameter values:
$1
will be discarded$2
will become$1
$3
will become$2
- …
- in general:
$N
will become$N-1
The command can take a number as argument: How many positions to shift.
So, a shift 4
will shift $5
to $1
.
Using them
Enough theory, you want to access your script-arguments. Well, here we go.
One by one
One way is to access specific parameters:
#!/bin/bash echo "Total number of arguments: $#" echo "Argument 1: $1" echo "Argument 2: $2" echo "Argument 3: $3" echo "Argument 4: $4" echo "Argument 5: $5"Well, it might be useful in one or the other situation, but this way is not very flexible. You're fixed in your maximum number of arguments - which is a bad idea if you write a script that takes many filenames as arguments.
⇒ forget that one
Loopings
There are several ways to loop through the positional parameters.
You can code a C-style for-loop using $#
as end-value. On every iteration, the shift
-command is used to shift the argument list:
numargs=$# for ((i=1 ; i <= numargs ; i++)); do echo "$1" shift doneNot very stylish, but okay, usable. The
numargs
variable is used to store the initial value of $#
because it will change due to the shifting.
Another way to iterate one-by-one is the for
-loop without given wordlist, it will use the positional parameters as wordlist then:
for arg; do echo "$arg" doneAdvantage: The positional parameters will be preserved and not shifted into nirvana!
The next way is similar to the first example (the for
-loop), but it doesn't test for reaching $#
. It shifts and checks if $1
still expands to something, using the test command:
while [ "$1" ]; do echo "$1" shift doneLooks nice, but it has the disadvantage to stop when
$1
is empty (null-string). Let's modify it to run as long as $1
is defined (but may be empty), using parameter expansion for an alternate value:
while [ "${1+defined}" ]; do echo "$1" shift done
Getopts
There is a small tutorial dedicated to ''getopts'' (under construction).
Mass usage
All Positional Parameters
Sometimes it's necessary to just "relay" or "hand through" given arguments to another program. It's very inefficient to do that in one of these loops, also you will destroy integrity, most likely (spaces!).
The shell-developers invented $*
and $@
for this purpose.
Without being quoted (double-quoted), both have the same effect: All positional parameters from $1
to the last used one are expanded without any specials. A subsequent wordsplitting will recognize as much words as expanded before (i.e. it "doesn't preserve words").
When the $*
special parameter is doublequoted, it expands to the equivalent of: "$1c$2c$3c$4c……..$N"
, where 'c' is the first character of IFS
.
But when the $@
special parameter is used inside doublequotes, it expands to the equivanent of…
"$1" "$2" "$3" "$4" ….. "$N"
…which exactly reflects all positional parameters like they were initially set and given to the script or the function. If you want to re-use your positional parameters to call another program (for example in a wrapper-script), then this is the choice for you, use the doublequoted "$@"
.
Well, let's just say: You almost always want "$@"
!
Range Of Positional Parameters
Another way to mass-expand the positional parameters is similar to what is possible for a range of characters using the substring expansion on normal parameters and the range mass expansion of arrays.
${@:START:COUNT}
${*:START:COUNT}
"${@:START:COUNT}"
"${*:START:COUNT}"
The rules for using @
or *
and the quoting are the same as above. This will expand COUNT
number of positional parameters starting at START
. COUNT
can be omitted (${@:START}
), in this case all positional parameters beginning at START
are expanded.
If START
is negative, the positional parameters are counted from the last one forward.
Example: Extract all but the last positional parameter:
echo "${@: -1}"
Attention: Since Bash 4, a
START
of 0
includes the special parameter $0
, i.e. the shell name or whatever it's set to, when the positional parameters are in use. A START
of 1
begins at $1
. In Bash 3 and older, both 0
and 1
began at $1
.
Setting Positional Parameters
Letting the caller set the positional parameters, by giving parameters on commandline, is not the only way to set them. The set builtin command can be used to "artificially" change the positional parameters from inside the script or function:
set "This is" my new "set of" positional parameters # RESULTS IN # $1: This is # $2: my # $3: new # $4: set of # $5: positional # $6: parameters
It's wise to signal "end of options" when setting positional parameters this way. If not, dashes might be interpreted as option tag by set
itself:
# both ways work, but behave different. See the article about the set command! set -- ... set - ...
continue
Production examples
Using a while loop
This one uses a while loop to switch from argument to argument, and a case statement to interpret these arguments.
while [ "${1+isset}" ]; do case "$1" in -x|--extmode) MY_EXTENDEDMODE=1 shift ;; -u|--user) MY_USERNAME="$2" shift 2 ;; -f|--configfile) MY_CONFIG="$2" shift 2 ;; -h|--help) display_help # a function ;-) # no shifting needed here, we'll quit! exit ;; *) echo "Error: Unknown option: $1" >&2 exit 1 ;; esac done
It`s not that flexible, it doesn't auto-interpret mixed options, and some other things, but it works and is a good rudimentary way to parse your arguments.
Tuned while loop
Basically it's the very same as the example above, but this one only reacts on arguments matching -*
(beginning with a dash), otherwise it quits. That way you can provide the parsing of the recommended UNIX®-argument-way:
COMMAND <options> <mass-options>
like
cat -A *
where *
might expand to thousands of filenames.
while [[ $1 = -* ]]; do case "$1" in -x|--extmode) MY_EXTENDEDMODE=1 shift ;; -u|--user) MY_USERNAME="$2" shift 2 ;; -f|--configfile) MY_CONFIG="$2" shift 2 ;; -h|--help) display_help # a function ;-) # no shifting needed here, we'll quit! exit ;; *) echo "Error: Unknown option: $1" >&2 exit 1 ;; esac done echo "Processing files..." for file; do echo "File: $arg" # ... done
Due to the shifting in the while-loop, the mass-arguments begin at $1
, they're fully accessible by "$@"
or for i
.
Note the while
-condition: It's not the test command, it's the conditional expression, since we do pattern matching.
Filter unwanted options with a wrapper script
This simple wrapper allows to filter unwanted options (here: -a
and –all
for ls
) out of the commandline. It reads the positional parameters and builds a (filtered) array out of them, then it calls ls
with the new option set. It also respects the –
as "end of options" for ls
and doesn't change anything after it:
#!/bin/bash # simple ls(1) wrapper that doesn't allow the -a option EOO=0 # end of options reached while [[ $1 ]]; do if ! ((EOO)); then case "$1" in -[^-]*a*|-a?*) OPTIONS+=("${1//a}") shift ;; -a) shift ;; --all) shift ;; --) EOO=1 OPTIONS+=("$1") shift ;; *) OPTIONS+=("$1") shift ;; esac else OPTIONS+=("$1") # Another (worse) way of doing the same thing: # OPTIONS=("${OPTIONS[@]}" "$1") shift fi done /bin/ls "${OPTIONS[@]}"
Using getopts
There is a small tutorial dedicated to ''getopts'' (under construction).
See also
- Internal: Small getopts tutorial
- Internal: The while-loop
- Internal: The C-style for-loop
- Internal: Arrays (for equivalent syntax for mass-expansion)
- Internal: Substring expansion on a parameter (for equivalent syntax for mass-expansion)
- Dictionary, internal: Parameter
Discussion
Without double quotes, $* and $@ are expanding the positional parameters separated by only space, not by IFS.
(Edited: Inserted code tags)
Thank you very much for this finding. I know how
$*
works, thus I can't understand why I described it that wrong. I guess it was in some late night session.Thanks again.
#!/bin/bash
OLDIFS="$IFS" IFS='-' #export IFS='-'
#echo -e $* #echo -e $@ #should be echo -e "$*" echo -e "$@" IFS="$OLDIFS"
#should be echo -e "$*"
I would suggext using a different prompt as the $ is confusing to newbies. Otherwise, an excellent treatise on use of positional parameters.
Thanks for the suggestion, I use "> " here now, and I'll change it in whatever text I edit in future (whole wiki). Let's see if "> " is okay.
Here's yet another non-getopts way.
http://bsdpants.blogspot.de/2007/02/option-ize-your-shell-scripts.html
Hi there!
What if I use "$@" in subsequent function calls, but arguments are strings?
I mean, having:
If you use it
But having
you get:
As you can see, there is no way to make know the function that a parameter is a string and not a space separated list of arguments.
Any idea of how to solve it? I've test calling functions and doing expansion in almost all ways with no results.
I don't know why it fails for you. It should work if you use
"$@"
, of course.See the exmaple I used your second script with:
Thanks a lot for this tutorial. Especially the first example is very helpful.