How to use arguments like $1 $2 ... in a for loop?
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:
- http://www.in-ulm.de/~mascheck/various/bourne_args/
- What is the purpose of the “do” keyword in Bash for loops?
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
Related videos on Youtube
AmirrezaFiroozi
Updated on September 18, 2022Comments
-
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 over 7 yearsInteresting 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 isbash-4.0.0 ... -4.0.27
. May we call thata little biased report
? -
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 over 7 yearsDoes 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 beecho "$i"
. -
Charles Duffy over 7 years...and
printf '%s\n' "$i"
is actually more correct thanecho "$i"
-- if your argument is-n
,printf
will print it, butecho
may not (the POSIX specification forecho
defines behavior as entirely undefined if the first argument is-n
, or if any argument contains a backslash literal; moreover, some popularecho
implementations defy black-letter POSIX by having-e
or-E
arguments have a behavior other than printing like strings on output). -
done over 7 yearsExactly my point: there is a (somewhat corner case) issue also with zsh.
-
done over 7 yearsAbout
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 isfor arg <newline> do
. -
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 thatfor arg <newline> do
is the most portable. I'm not sure I understand the point you're trying to make. -
hschou over 7 yearsThe script does nothing. Pls explain!
-
Charles Duffy over 7 yearsI'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 excludingecho
, it tries to implement a superset of the POSIX sh specification (and evenecho
can be strictly compliant, when built with--enable-xpg-echo
or given appropriate runtime configuration). -
Charles Duffy over 7 years@hschou, if it "does nothing", then you aren't passing it any arguments.
-
Sergiy Kolodyazhnyy over 7 yearsMaybe 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 theman
page forbash
and has been tested withmksh
, andksh
. 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 over 7 years@hschou, I have updated the answer. The answer explains everything.
-
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 over 7 yearsWhat 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 over 7 years@sorontar, exactly, the mascheck page is about
"$@"
, this Q&A is about looping over the positional parameters for which the syntax isfor i do
. I only mentionfor 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 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 over 7 yearsChanging 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 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 over 5 years@Harold, yes it's POSIX (I've updated the answer) but not the most portable.
-
Stéphane Chazelas almost 5 yearsWhat if the script is called as
my-script foo '' bar
? It would be better to usewhile [ "$#" -gt 0 ]
(or ksh/bash/zsh-specificwhile (($# > 0))
) -
lousycoder over 2 yearsI'd prefer ${!i}.