Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am a bit stuck. My task is to print the arguments to my script in reverse order except the third and fourth.

What I have is this code:


for arg in "$@"
    case $i
        3) ;;
        4) ;;
        *) eval echo "$i. Parameter: \$$i";;
    i=`expr $i - 1`

As I hate eval (greetings to PHP), I am looking for a solution without it but I am not able to find one.

How can I define the position of the argument dynamically?

PS: No its not a homework, I am learning shell for an exam so I try to solve old exams.

share|improve this question
up vote 12 down vote accepted

eval is the only portable way to access a positional parameter by its dynamically-chosen position. Your script would be clearer if you explicitly looped on the index rather than the values (which you aren't using). Note that you don't need expr unless you want your script to run in antique Bourne shells; $((…)) arithmetic is in POSIX. Limit the use of eval to the smallest possible fragment; for example, don't use eval echo, assign the value to a temporary variable.

while [ "$i" -gt 0 ]; do
  if [ "$i" -ne 3 ] && [ "$i" -ne 2 ]; then
    eval "value=\${$i}"
    echo "Parameter $i is $value"

In bash, you can use ${!i} to mean the value of the parameter whose name is $i. This works when $i is either a named parameter or a number (denoting a positional parameter). While you're at it, you can make use of other bash convenience features.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
share|improve this answer
I can't use arg as they are ordered correctly and not in reverse. To the usage of expr, I am limited to use the standard only. – WarrenFaith Sep 25 '11 at 16:07
@WarrenFaith If your script starts with #!/bin/bash, you can use ${!i} and (()). If you want to stick to standard sh, these aren't available, but $((…)) is. – Gilles Sep 25 '11 at 16:10
Ok, I think I can work with this :) – WarrenFaith Sep 25 '11 at 16:11
Note that the antique Bourne shells wouldn't be able to access positional parameters beyond $9 anyway. – Stéphane Chazelas Jun 12 '15 at 14:41
eval is not the only portable way as Ryan has shown. – Stéphane Chazelas Jun 12 '15 at 15:04

With basically any shell:

printf '{ PS4=\${$(($#-$x))}; } 2>&3; 2>&1\n%.0s' |
x=LINENO+1 sh -sx "$@" 3>/dev/null

And you don't need to use subshells. For example:

set -x a b c
{ last= PS4=\${last:=\${$#}}; set +x; } 2>/dev/null
echo "$last"



And here is a shell function which can set a shell alias for you that will print the arguments either forward or backward:

tofro() case $1 in (*[!0-9]*|'') ! :;;(*) set "$1"
        until   [ "$1" -eq "$(($#-1))" ]    &&
                shift && alias args=":; printf \
            \"%.\$((\$??\${#*}:0))s%.\$((!\$??\${#*}:0))s\n\" $* "
        do      [ "$#" -gt 1 ] &&
                set "$@ \"\${$#}\" " '"${'"$((1+$1-$#))"'}"' ||
                set "$1" '"$1" "${'"$1"'}"'
        done;   esac

It doesn't attempt to store the literal values for any arguments, but rather it puts a string like this in the args alias:

:;printf    "%.$(($??${#*}:0))s%.$((!$??${#*}:0))s\n" \
            "$1" "${3}" "${2}"  "${2}" "${3}"  "${1}"

...and so stores only references to the parameters backwards and forwards. It will store up to a count as given it as an argument. And so the above alias was generated like:

tofro 3

printf's behavior is affected based on the return value of the previous command - which is always : the null command, and so usually true. printf will skip half of its arguments each time it prints - which will, by default, get the arguments printed out from smallest numbered to largest. However, if you just do:

! args

...it prints them in reverse.

Because the alias does not store any literal values, its value remains static while the actual args might change, but it will still reference as many as it might. For example:

set one two three
tofro 3
args; ! args
shift; args; ! args

...which prints...



But resetting the alias can be done like:

tofro 2
args; ! args

...and so it prints...

share|improve this answer

Assuming the positional parameters don't contain newline characters:

[ "$#" -gt 0 ] && printf '%s\n' "$@" | #print in order
sed '3,4 d' |                          #skip 3 and 4
tac                                    #reverse (try tail -r on
                                       #non-GNU systems).


set 1 2 3 4 5 6
printf '%s\n' "$@" | 
sed '3,4 d' |

Test output:

share|improve this answer
Thanks for the edit, @StéphaneChazelas. – PSkocik Jun 12 '15 at 15:03

With zsh:

$ set a 'foo bar' c '' '*'
$ printf '<%s>\n' "${(Oa)@}"
<foo bar>

Oa is a parameter expansion flag to sort the array elements upon expansion in reverse array indices.

To exclude 3 and 4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<foo bar>
share|improve this answer

I keep a script reverse on my path that does this:


if [ "$#" -gt 0 ]; then
    reverse "$@"
    printf '%s\n' "$arg"

Example usage:

$ reverse a b c '*' '-n'

You can also use a function instead of a dedicated script.

share|improve this answer
Note that that one (contrary to the other answers posted so far) would also work in the Bourne shell (not that there's any reason to use that shell nowadays) – Stéphane Chazelas Jun 12 '15 at 14:43
@StéphaneChazelas: Why quote $#?  Can it be anything other than a non-negative integer? – G-Man Jun 12 '15 at 17:05
@G-Man, leaving a variable unquoted in list context is invoking the split+glob operator. There's not reason why you'd want to invoke it here. That has nothing to do with the content of the variable. See also unix.stackexchange.com/a/171347 towards the end. Also note that some shells (dash, posh for instance) still inherit IFS from the environment. – Stéphane Chazelas Jun 12 '15 at 18:44
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
share|improve this answer
no explanation at all, nice. Downvote from me. Explain what your code does and I might change my mind. – WarrenFaith Jun 12 '15 at 11:22
Can be simplified to for ((i = $# - 1; i >= 0; i--)) – Stéphane Chazelas Jun 12 '15 at 11:33
Am I hallucinating?  I thought I saw words in the question that said "print the arguments ... except the third and fourth". – G-Man Jun 12 '15 at 12:18
@G-Man, the title says "print arguments in reverse" which this is answering without using eval. Excluding 3 and 4 from that is trivial. I don't think the downvotes are justified. It is also self-explanatory (though as I said could be simplified a great deal). – Stéphane Chazelas Jun 12 '15 at 15:02

This is a correct and non-dangerous use of eval. You fully control the content that you are evaling.

If it still gives you bad feelings, then if you don't care about portability, you can use Bash's ${!i} indirection syntax.

share|improve this answer
so ${!i} isn't part of the standard syntax? – WarrenFaith Sep 25 '11 at 16:05
No, it's a Bashism. Here are the POSIX parameter expansions: pubs.opengroup.org/onlinepubs/009695399/utilities/… – Shawn J. Goff Sep 25 '11 at 16:18

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.