10

I am writing a shell script and I need to print the nth argument of the script.

For example, suppose we have n=3 and our script is run with enough arguments. Now I need to print the nth argument, i.e. $3.

But if n=2, we would print argument $2.

I don't want to use if statements. I wanted to do something like

echo $($n)

but the above doesn't work the way I need it to.

Toby Speight
  • 8,678
David
  • 309
  • 3
    It looks like you mixed up command substition, $(), with parameter expansion, ${}. So you're starting from the wrong section in the manual... – Toby Speight Mar 10 '22 at 14:11

4 Answers4

26

By chronological order, in various shells:

  • csh (late 70s): $argv[$n] (also works in zsh since 1991 and fish since 2005)
  • zsh (1991): $argv[n] / $@[n] / $*[n] (the last too also supported by yash but only with extra braces: ${@[n]} / ${*[n]})
  • rc (early 90s): $$n / $*($n) (also works in es, akanga)
  • ksh93 (1993): ${@:n:1}, ${*:n:1} (also supported by bash since 1996; zsh also since 2010, though you need ${@:$n:1} or ${@: n:1} there to avoid conflict with csh-style modifiers and see there about the "$*" case)
  • bash (1996): ${!n}
  • zsh (1999): ${(P)n}.

Remember that in ksh93/bash/yash, you need to quote parameter expansions in list contexts at least, and csh can hardly be used to write reliable code.

In bash, there's a difference between "${!n}" and "${@:n:1}" in list context when the nth positional parameter is not set in that the latter then expands to no argument at all whilst the former expands to one empty element.

In Bourne-like shells (but not the Bourne shell where that won't work for indices past the 9th), and with standard POSIX sh syntax, you can also do:

eval "nth=\${$n}"

There will also be differences in behaviour among all those if $n does not contain the canonical decimal representation of an integer number strictly greater than 0. If you can't guarantee that will be the case, using most of those (not just the eval one) would introduce an arbitrary command execution vulnerability (the only exceptions being with rc and maybe csh above).

Also remember that except in zsh (with echo -E - $argv[n]), yash (with ECHO_STYLE=raw echo "${*[$n]}") and fish (with echo -- $argv[$n]), echo can't be used to output arbitrary data, use printf '%s\n' ... instead).

17

The arguments you pass to a script are stored in the @ array, so the simple way to do what you want is to take an array slice, starting at position n and having a length of 1:

#!/bin/bash

n=3 echo "Argument $n: ${@:n:1}"

If you run this with a few arguments, you get:

$ foo.sh a b c d e f
Argument 3: c

The other alternative, and what you were originally asking for, is called "indirect expansion":

#!/bin/bash

n=3 echo "Argument $n: ${!n}"

Running this:

$ foo.sh a b c d e f
Argument 3: c
terdon
  • 242,166
6

You can use variable indirection:

#!/bin/bash
n=3
echo "${!n}"
choroba
  • 47,233
5

Simple portable shell (no bash dependency) solution (modulo potential badness of the echo command; it could be replaced with printf):

print_nth () {
    shift "$1"
    echo "$1"
}

Then do:

print_nth "$n" "$@"