Print shell arguments in reverse order

21,728

Solution 1

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.

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

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}"
  fi
done

Solution 2

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

#!/bin/sh

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

Example usage:

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

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

Solution 3

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

Test:

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

Test output:

6
5
2
1

Solution 4

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.

Solution 5

With zsh:

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

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>
<a>
Share:
21,728

Related videos on Youtube

WarrenFaith
Author by

WarrenFaith

Updated on September 18, 2022

Comments

  • WarrenFaith
    WarrenFaith almost 2 years

    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:

    #!/bin/bash
    
    i=$#
    for arg in "$@"
    do
        case $i
        in
            3) ;;
            4) ;;
            *) eval echo "$i. Parameter: \$$i";;
        esac
        i=`expr $i - 1`
    done
    

    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.

  • WarrenFaith
    WarrenFaith almost 13 years
    so ${!i} isn't part of the standard syntax?
  • WarrenFaith
    WarrenFaith almost 13 years
    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.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 13 years
    @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.
  • WarrenFaith
    WarrenFaith almost 13 years
    Ok, I think I can work with this :)
  • Shawn J. Goff
    Shawn J. Goff almost 13 years
    No, it's a Bashism. Here are the POSIX parameter expansions: pubs.opengroup.org/onlinepubs/009695399/utilities/…
  • WarrenFaith
    WarrenFaith about 9 years
    no explanation at all, nice. Downvote from me. Explain what your code does and I might change my mind.
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    Can be simplified to for ((i = $# - 1; i >= 0; i--))
  • G-Man Says 'Reinstate Monica'
    G-Man Says 'Reinstate Monica' about 9 years
    Am I hallucinating?  I thought I saw words in the question that said "print the arguments ... except the third and fourth".
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    Note that the antique Bourne shells wouldn't be able to access positional parameters beyond $9 anyway.
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    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
    Stéphane Chazelas about 9 years
    @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
    Stéphane Chazelas about 9 years
    eval is not the only portable way as Ryan has shown.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 9 years
    @StéphaneChazelas I meant portable way to access a positional parameter directly, not to perform the task as a whole.Clarified.
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    Well, using shift is still another option (and more portable since ${10} doesn't work in the Bourne shell). (like getn() { shift "$1"; n=$1; }; getn 14 "$@" to get the 14th parameter)
  • G-Man Says 'Reinstate Monica'
    G-Man Says 'Reinstate Monica' about 9 years
    @StéphaneChazelas: Why quote $#?  Can it be anything other than a non-negative integer?
  • Stéphane Chazelas
    Stéphane Chazelas about 9 years
    @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.
  • starfry
    starfry almost 8 years
    I found that, on Android, I had to declare local arg=$1