4

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
3
  • 2
    Replace in second example echo $i by echo $i $1; shift to get more confused or enlightened.
    – Cyrus
    Commented Jan 2, 2016 at 21:31
  • @Cyrus Yes, I also found while (( "$#" )); do echo $1; shift; done It served its purpose; confusion. I would still like to know why Block #2 does not produce the same output as Block #1 :)
    – jes516
    Commented Jan 2, 2016 at 21:42
  • 1
    @jes516 in #2, you are explicitly creating a variable i and assigning an integer index to it; in #1, the shell is implicitly assigning each positional parameter in turn to i. See the for name [ [ in [ word ... ] ] ; ] do list ; done construct in the Compound commands section of man bash. Commented Jan 2, 2016 at 22:19

2 Answers 2

8

The first block iterates (implicitly) across the command line arguments "$@"

for i in "$@"    # same as your "for i"
do
    echo "$i"
done

The second block iterates explicitly across the number of arguments, printing the index as it goes:

args=$#                          # number of command line args
for (( i=1; i<=$args; i+=1 ))    # loop from 1 to N (where N is number of args)
do
    echo $i
done

Given that, as per your example, $# is 5, then the $i variable will take the values 1, 2, 3, 4, 5.

As pointed out in another (now deleted) answer, you can reference the command line arguments by index like this:

args=$#
for (( i=1; i<=$args; i++ ))
do
    echo "$i - ${!i}"
done
1
  • Just to expand on my comment below, echo "$i - ${!i}" is echoing the variable i - the variable $i, where i is the number and $i evaluates to the "shell variable" $1..
    – jes516
    Commented Jan 3, 2016 at 4:34
4

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.

5
  • Just so I am clear, bash is looking for the variable $1, $2, etc... where the dollar sign is part of the variable name. For example I did, args="$#"; a="$1"; b="$2"; echo "${a}"; echo "${b}" and that worked. I thought bash was looking for the variable 1, 2, etc... where the dollar sign was part of the syntax (for lack of better terms).
    – jes516
    Commented Jan 3, 2016 at 4:31
  • I'd say you were right the first time.  If you have a variable named a, at the risk of being redundant, its name is a.  You set it by saying a=…; e.g., a=aardvark or a="$1".  You access it (e.g., in an echo command) with "$a" or "${a}", so the $ and ${…} are part of the syntax (and that’s a perfectly appropriate term).  $1, $2, etc… are slightly different from $a, $b, etc…, but not much. Commented Jan 3, 2016 at 22:33
  • Perfect, I appreciate the insight. While I have your attention (if you don't mind) why does for i; do echo $i; done work as well to print arguments? Does bash automatically run that code block for each argument? I am used to seeing for i in list; ...
    – jes516
    Commented Jan 4, 2016 at 3:08
  • You understand for i in "$1" "$2" "$3" "$4" "$5"; do …, right?  But the problem is that you don’t know in advance how many arguments there are (i.e., how many there are going to be).  roaima’s answer brushed up against this without delving into it.  $@ (which, above all other $ expressions, should always be quoted) is a magic token that expands to the list of arguments, however long or short that is. (You can, and should, read about this, the ${…} syntax, and other things in sh/bash documentation;  … (Cont’d) Commented Jan 4, 2016 at 6:39
  • (Cont’d) …  e.g., what you get if you type man sh or man bash (if that doesn’t work for you, try this), the Bash Reference Manual, the POSIX specification for Shell Command Language, and other references.)  And for i; do … is a special shorthand for for i in "$@"; do … — which is equivalent to for i in "$1" "$2" "$3" "$4" "$5"; do … (if you have five arguments). Commented Jan 4, 2016 at 6:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .