Bash – Iterate through command line arguments in Bash

bash

Can someone explain the difference between these two code blocks? I would think Block #2 would output the same as Block #1 but it does not. Can someone explain why?

# ./arguments.sh hello my name is X

Block #1

for i
do
    echo $i
done

Output:

hello
my
name
is
X

Block #2

args=$#
for (( i=1; i<=$args; i+=1 ))
do
    echo $i
done

Output:

1
2
3
4
5

Best Answer

roaima's answer answers the question that you actually asked:

Q: What is the difference between these two code blocks?  Why do they give different output?

A: The first loop is iterating over the command line arguments; the second one is iterating over the argument numbers (indices).

... although I presume that you would have figured that much out for yourself in another six to eight minutes — it's kind-of obvious. You probably want the second program to do something like

echo $$i                                        # This doesn't do what you want.

to display the argument that is indexed by the number that is stored in variable i (and referenced as $i).  As noted in the comment, that doesn't do what you want.  (It does do something; I encourage you to experiment and figure out what it does.)  But this is close to something that does work:

eval echo \$$i                                  # Don't do this.

or, equivalently,

eval echo '$'"$i"                               # Don't do this.

These commands

  • get the value of i (one of the numbers 1, 2, 3, ...)
  • stick a $ in front of it, forming $1, $2, $3, etc.
  • use the eval command to say, "take this command line that I've just constructed, and evaluate it as if I had typed it.

So that would have the effect of executing

echo $1
echo $2
echo $3
   ︙

But, as the comments suggest, you should try to avoid this.  eval can be dangerous if the input is anything other than plain words.  Search this site; you'll find plenty of explanations of that.

But there is a fairly safe way to get the second program to do the same thing the first one does: change

echo $i

to

echo ${!i}

OK, first of all, ${i} is pretty much the same as $i.  The ! gives you an effect similar to that of the eval command — ${!x} looks up the value of x (i.e., $x or ${x}) and uses that as the name of the variable to look up.  So, if x=foo, then ${!x} is the same as $foo.  The above code does the same thing with i, fetching the parameter whose name is the value of i.

By the way, you should always quote all references to shell variables (e.g., "$i", "$#", "$args", "${i}" and "${!i}") unless you have a good reason not to, and you’re sure you know what you’re doing.

Related Question