A compilation of documentation   { en , fr }

Browse the parameters of a shell script... both ways

Tags:
Created on:
Last edited on:
Author:
Xavier Béguin

Browsing arguments from first to last

In command-line interpreters (also called shells) like bash, the positional parameters of the script or of a function can be read individually using the variables automatically set by the interpreter: $1 for the first argument, $2 for the second one, $3, etc.

To browse the positional parameters one by one in sequential order, a simple for loop as this one can be used:

1for arg
2do
3 echo "I received the argument $arg";
4done

This works because, used without the keyword in, for by default loops on the positional parameters, which amounts to using for arg in "$@" (which works as fine, and incidentally have the merit of being more explicit, "$@" being replaced by the list of positional arguments).

Browsing arguments from last to first

To browse the list of these parameters in the reverse order (that is from the last to the first positional parameter), we need to use the internal shell function eval:

1for i in $(seq $# -1 1)
2do
3 arg="$(eval "echo \$$i")"
4 echo "I received the argument $arg"
5done

Here, for loops on the number of the parameters we want to process, from the last one, stored in the automatic variable $#, to the first, numbered 1 ($0 isn't a parameter but the name of the script).

Then, to get the value of the positional parameter numbered $i (that is, $4, $3, …), we'll use eval on line 3 to execute the command echo \$$i.

To correctly understand this line 3, note that:

  • the command provided to eval is evaluated twice: the first time on the normal execution of the script, then the result of this evaluation is evaluated by the function eval itself;
  • the first $ needs to be despecialized using a backslash so that the first evaluation doesn't interpret it, but pass it as is to the next evaluation;
  • on the second evaluation, this first $ will be used together with the number resulting from the evaluation of the following $i: eval will thus execute a function similar to echo $3 (the actual number obviously depends on the value of i) and will return its result.

Alternative solution specific to the bash interpreter

Using the bash interpreter, browsing the list of parameters in the reserve order is possible without using eval, thanks to the indirect expansion mecanism specific to bash:

1for i in $(seq $# -1 1)
2do
3 arg=${!i}
4 echo "argument $arg";
5done

Warning: these variable indirections do not exist in the POSIX norm (norm IEEE 1003.1) and are specific to the bash interpreter. They will not work with interpreters limited to this norm (as it is for example usually the case for interpreters simply named sh). Use echo $SHELL to see what shell you are using.

In this case, to get the value of a positional parameter (stored in $4, $3, ...), we're introducing a level of variable indirection using the exclamation point: it indicates that we want to read the value of a variable whose name is stored in another variable.

Thus, if $i contains 3, ${!i} will be replaced by the value of the variable $3, that is to say the third positional argument of the script or function, just like the command eval "echo \$$i" would.

Corollary: thanks to the indirect expansion mecanism of bash, the last positional parameter of a bash script or function can be obtained directly using the expression ${!#} that you should now understand correctly.

Note that we need to use the command seq rather than the handy bash internal series generator (of the type {10..1}) because the latter uses accolades that are evaluated before the substitution of the variables. We thus can't write something like {$#..1} to obtain the series of numbers between the last and first positional argument.