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.


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}"
  • 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. Sep 25 '11 at 16:07
  • 1
    @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. Sep 25 '11 at 16:10
  • Ok, I think I can work with this :) Sep 25 '11 at 16:11
  • Note that the antique Bourne shells wouldn't be able to access positional parameters beyond $9 anyway. Jun 12 '15 at 14:41
  • 1
    eval is not the only portable way as Ryan has shown. Jun 12 '15 at 15:04

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.

  • 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) Jun 12 '15 at 14:43
  • @StéphaneChazelas: Why quote $#?  Can it be anything other than a non-negative integer? Jun 12 '15 at 17:05
  • 2
    @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. Jun 12 '15 at 18:44
  • I found that, on Android, I had to declare local arg=$1
    – starfry
    Sep 4 '16 at 9:50

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:


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.


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>

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...

declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
  • 2
    no explanation at all, nice. Downvote from me. Explain what your code does and I might change my mind. Jun 12 '15 at 11:22
  • 3
    Can be simplified to for ((i = $# - 1; i >= 0; i--)) 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". Jun 12 '15 at 12:18
  • 1
    @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). Jun 12 '15 at 15:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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