How to use arguments like $1 $2 ... in a for loop?

81,558

Solution 1

In any Bourne-like shell, it's:

for arg
do printf 'Something with "%s"\n' "$arg"
done

That is, for does loop on the positional parameters ($1, $2...) by default (if you don't give a in ... part).

Note that that's more portable than:

for arg; do
  printf 'Something with "%s"\n' "$arg"
done

Which was not POSIX until the 2016 edition of the standard nor Bourne (though works in most other Bourne-like shells including bash even in POSIX mode)

Or than:

for arg in "$@"; do
  printf 'Something with "%s"\n' "$arg"
done

Which is POSIX but doesn't work properly in the Bourne shell or ksh88 when $IFS doesn't contain the space character, or with some versions of the Bourne shell when there's no argument, or with some shells (including some versions of bash) when there's no argument and the -u option is enabled.

Or than

for arg do
  printf 'Something with "%s"\n' "$arg"
done

which is POSIX and Bourne but doesn't work in very old ash-based shells. I personally ignore that and use that syntax myself as I find it's the most legible and don't expect any of the code I write will ever end up interpreted by such an arcane shell.

More info at:


Now if you do want $i to loop over [1..$#] and access the corresponding elements, you can do:

in any POSIX shell:

i=1
for arg do
  printf '%s\n' "Arg $i: $arg"
  i=$((i + 1))
done

or:

i=1
while [ "$i" -le "$#" ]; do
  eval "arg=\${$i}"
  printf '%s\n' "Arg $i: $arg"
  i=$((i + 1))
done

Or with bash

for ((i = 1; i <= $#; i++ )); do
  printf '%s\n' "Arg $i: ${!i}"
done

${!i} being an indirect variable expansion, that is expand to the content of the parameter whose name is stored in the i variable, similar to zsh's P parameter expansion flag:

for ((i = 1; i <= $#; i++ )); do
  printf '%s\n' "Arg $i: ${(P)i}"
done

Though in zsh, you can also access positional parameters via the $argv array (like in csh):

for ((i = 1; i <= $#; i++ )); do
  printf '%s\n' "Arg $i: $argv[i]"
done

Solution 2

I would use shift. This one [ -n "$1" ] means while arg-1 is non-empty, keep looping.

#! /bin/bash 
while [ -n "$1" ]; do
  echo "$1"
  wget "https://ssl.gstatic.com/dictionary/static/sounds/de/0/$1.mp3"
  shift
done

Solution 3

Easiest way

#!/bin/bash
for i
do
 echo $i
done

and run

./a.sh personality brave selfish

and here is the print on the stdout

personality
brave
selfish
Share:
81,558

Related videos on Youtube

AmirrezaFiroozi
Author by

AmirrezaFiroozi

Updated on September 18, 2022

Comments

  • AmirrezaFiroozi
    AmirrezaFiroozi almost 2 years

    I have this script which designed to download the pronounciation of the words you give it as argument:

    #!/bin/bash 
    m=$#
    for ((i=1;i<=m;i++));do
    
    echo $i
    #wget https://ssl.gstatic.com/dictionary/static/sounds/de/0/"$i".mp3
    done
    

    if I run it by this command

    ./a.sh personality brave selfish
    

    it should print on the stdout

    personality 
    brave 
    selfish
    

    but instead it prints

    1
    2
    3
    

    would you help me solve this problem?

    p.s : If I write the script without for loop with $1 for example it will work correctly but I want to download many files at the same time

  • done
    done over 7 years
    Interesting that you do not mention (from the mascheck page you link to) zsh until release 4.3.0, in sh emulation mode (which means the option shwordsplit is set), does word splitting on the resulting arguments. and that the only version of bash (the only shell you specifically mention by name) that needs a very corner case addition is bash-4.0.0 ... -4.0.27. May we call that a little biased report?
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @sorontar, I mention bash because that's the shell mentioned by the OP. For zsh in sh emulation there used to be a problem with ${1+"$@"}, not "$@" alone.
  • Charles Duffy
    Charles Duffy over 7 years
    Does this add anything to the prior answer, which AFAICT includes this idiom? echo $i is buggy, by the way -- if you have an argument passed as "*", you would instead have a list of filenames in the current directory emitted; should be echo "$i".
  • Charles Duffy
    Charles Duffy over 7 years
    ...and printf '%s\n' "$i" is actually more correct than echo "$i" -- if your argument is -n, printf will print it, but echo may not (the POSIX specification for echo defines behavior as entirely undefined if the first argument is -n, or if any argument contains a backslash literal; moreover, some popular echo implementations defy black-letter POSIX by having -e or -E arguments have a behavior other than printing like strings on output).
  • done
    done over 7 years
    Exactly my point: there is a (somewhat corner case) issue also with zsh.
  • done
    done over 7 years
    About for arg do (without the ;), the same mascheck page says: The original Almquist shell (ash) accepts all but the second variant.. Thus, the only really portable solution is for arg <newline> do.
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @sorontar, the zsh issue you mention is not relevant to this Q&A, it's about the ${1+"$@"} construct which is not needed to loop over the positional parameters. My whole answer does say that for arg <newline> do is the most portable. I'm not sure I understand the point you're trying to make.
  • hschou
    hschou over 7 years
    The script does nothing. Pls explain!
  • Charles Duffy
    Charles Duffy over 7 years
    I'm... not really sure that calling bash more Bourne-like than POSIX-like is fair. bash doesn't try to be Bourne-compatible -- witness echo hello ^ cat not treating ^ as a pipe -- whereas excluding echo, it tries to implement a superset of the POSIX sh specification (and even echo can be strictly compliant, when built with --enable-xpg-echo or given appropriate runtime configuration).
  • Charles Duffy
    Charles Duffy over 7 years
    @hschou, if it "does nothing", then you aren't passing it any arguments.
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 7 years
    Maybe it's also worth mentioning that with -c flag in bourne-style shells , the $0 gets assigned the first argument value instead of the shell/interpreter name. This is mentioned in the man page for bash and has been tested with mksh, and ksh. The typical trick is to use a dummy argument. For instance, bash -c 'for i;do echo "$i"; done' sh one two three. And just a small addition - same trick with different shells: paste.ubuntu.com/23272236
  • Karthik Marri
    Karthik Marri over 7 years
    @hschou, I have updated the answer. The answer explains everything.
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @Charles, I never said bash was or intended to be Bourne compatible. It is a Bourne-like shell in the loose sense like ksh, yash or zsh. It can't be Bourne-compatible as it aims to be POSIX compliant (at least in some environment as you say). AFAIK, it's the only open-source shell that has been POSIX and Unix certified (in OS/X at least)
  • done
    done over 7 years
    What are you talking about? The whole and unique purpose of the mascheck page is to describe the need, use and consequences of using the ${1+"$@"} construct. It is equally relevant to any shell named there. If it is relevant to bash, it is equally relevant to zsh. What it say about bash is: At least these shells need that workaround (the use of the ${1+"$@"} construct. What it say about zsh is: ${1+"$@"} is reliable and portable -- with one exception: zsh until release 4.3.0 … (the failure to accept the ${1+"$@"} construct).
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @sorontar, exactly, the mascheck page is about "$@", this Q&A is about looping over the positional parameters for which the syntax is for i do. I only mention for i in "$@" because that was the solution proposed by another answer (now deleted). "$@" would still be relevant to some other Q&A like how to loop over the position parameters *and something else*? or How to pass the positional parameters as-is to a command, in which case the answer might want to point out the ${1+"$@"} WA for the bugs in those old shells, and then mention the different behaviour of old zsh in sh emulat
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @sorontar, Note that zsh in sh emulation doesn't pretend to be Bourne or POSIX compatible (just like in csh emulation it doesn't pretend to be csh compatible), though it tries as much as possible. Strictly speaking ${1+"$@"} being subject to work splitting in those old versions was not a bug, but down to how zsh nested parameter expansions in this instance (other shells don't support nesting expansions consistently like zsh does). (continued)
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    Changing it meant breaking backward compatibility (and a fundamental change) which explains why it was delayed for so long even though the compatibility issue with sh was known from the start. Now ${=foo+"$bar"} doesn't split $bar like it did before (though ${(s: :)foo+"$bar"} still does)
  • Harold Fischer
    Harold Fischer over 5 years
    @StéphaneChazelas, is for arg in "$@"; do ... not POSIX compliant? I prefer it because it is the most explicit way to loop through the arguments.
  • Stéphane Chazelas
    Stéphane Chazelas over 5 years
    @Harold, yes it's POSIX (I've updated the answer) but not the most portable.
  • Stéphane Chazelas
    Stéphane Chazelas almost 5 years
    What if the script is called as my-script foo '' bar? It would be better to use while [ "$#" -gt 0 ] (or ksh/bash/zsh-specific while (($# > 0)))
  • lousycoder
    lousycoder over 2 years
    I'd prefer ${!i}.