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:
1 for
2 do
3 ;
4 done
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
:
1 for
2 do
3 arg=""
4
5 done
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 functioneval
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 toecho $3
(the actual number obviously depends on the value ofi
) 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:
1 for
2 do
3 arg=
4 ;
5 done
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.