There are two types of "for" loops in the bash(1) shell:
for VARNAME in LIST do # COMMANDS. FOR EXAMPLE ... echo ENTERED LOOP ############################ # break example # if [ "$VARNAME" = STOP ] then break # break out of loop fi ############################ # continue example # if [ "$VARNAME" = CONTINUE ] then echo "BACK TO TOP" continue # continue from the top of the loop fi ############################ echo MADE IT TO BOTTOM OF LOOP done
"LIST" contains a list of values. The list can be variables that contain several words separated by spaces (or more rarely, other field delimiters as set by the IFS variable) the loop will be executed once for each item in the list.
VARNAME is an arbitrary variable name. The current item from the list will be stored in the variable "VARNAME" each time through the loop.
If you don't specify the keyword "in" followed by a list of values in the loop, it will use the positional parameters (i.e the arguments that are passed to the shell script). That is,
for NAME
is essentially an abbreviation for
for NAME in "$@"
which is NOT always equivalent to
for NAME in $*
when the parameters have field separators (typically whitespace) in them. A quoted $@ retains the original parameters as entities, while a $* is parsed as a list of fields. To see this sometimes subtle difference, execute the following (we assume it is called "testit") using
bash testit A B 'a and b' C D
The "testit" script ...
#!/bin/bash # will print five strings. One will be "a and b" i=0 for NAME in "$@"; do echo "@ NAME $((++i)) $NAME" done # will print five strings. One will be "a and b" i=0 for NAME ; do echo " NAME $((++i)) $NAME" done # will print seven one-word strings. i=0 for NAME in $*; do echo "\* NAME $((++i)) $NAME" done # will print one string.with all the arguments i=0 for NAME in "$*"; do echo "\* NAME $((++i)) $NAME" done
So "$@" is a useful exception to the general rule of not quoting your list in a "for" loop.
# for NUMBER in {1..100..3} # print values from one to one hundred by threes do echo NUMBER is $NUMBER done # is a lot easier than #for NUMBER in 1 4 7 10 13 16 19 22 25 28 31 \ # 34 37 40 43 46 49 52 55 58 61 \ # 64 67 70 73 76 79 82 85 88 91 \ # 94 97 100 # NOTE: # to use variables in a brace expansion is difficult because # brace expansion is done before any other shell expansion!!! # This can lead to very non-intuitive behavior when using it # with variables. There are several ways that work, a lot # that do not... START=1 END=100 #for NUMBER in {$START..$END} # WRONG: WILL NOT WORK #LIST={$START..$END}; for NUMER in $LIST # WRONG AGAIN #eval for NUMBER in {$START..$END} # STILL WRONG, YOU GET THE IDEA. for NUMBER in $(eval echo {$START..$END}) # THIS WILL WORK do echo NUMBER is $NUMBER done # so I only recommend brace expansion for a fixed sequence of numbers # C-style syntax can generate a numeric sequence quite nicely, by the way. # As we will see shortly, you can use START=1 END=100 INCREMENT=3 for (( i=$START; i<=$END; i=i+$INCREMENT )) do echo " i is $i" done
# # So assuming you have the seq(1) command it is more intuitive to use # command output to generate the list, for example. START=1 END=100 INCREMENT=3 for NUMBER in $(seq $START $INCREMENT $END) # generate list using command do echo NUMBER is $NUMBER done This can be very powerful, with uses too numerous to list. Just one more example for now ... # cut(1) or awk(1) to select fields with different delimiters i=1 for username in $(awk -F: '{print $1}' /etc/passwd) do echo "Username $((i++)) : $username" done
# THIS IS VERY COMMONLY USED TO PERFORM MULTIPLE COMMANDS ON FILES # echo "Building library ..." for SOURCE_FILE in *.c # loop on all files in current directory ending in .c do echo FILE IS $SOURCE_FILE cc -c $SOURCE_FILE ar rv ${SOURCE_FILE%.c}.o rm -f ${SOURCE_FILE%.c}.o done
# DO NOT QUOTE YOUR LIST OR IT WILL BE TREATED AS ONE VARIABLE. # MAKE SURE YOU KNOW WHY THE OUTPUT FROM THIS EXAMPLE IS WHAT IT IS A=first B=second for STRING in "$A $B" 'with blanks' $A $B do echo STRING "$STRING" done
The second form of the for loop is similar to the for loop in "C" programming, which has three expressions (initialization, conditional and update).
SYNTAX:
for (( EXPR1; EXPR2; EXPR3 )) do COMMAND1 COMMAND2 .. done
Before the first iteration, EXPR1 is evaluated. This is usually used to initialize variables.
All the statements between "do" and "done" are executed repeatedly until the value of EXPR2 is TRUE.
After each iteration of the loop, EXPR3 is evaluated. This is usually used to increment a loop counter.
When you don't provide the conditional in the bash C-style for loop (or you provide one that will never be true), it will become an infinite loop.
for ((i=1; ;i++ )) do echo "Sleeping for $i seconds (ctrl-C or ctrl-Z might stop me!)" sleep $i done
Typically, you press Ctrl-C to break out of this infinite loop. Otherwise it will take a LONG time to finally overflow.
You can actually have multiple expressions in the three sections of the for loop header, separated by commas.
for ((i=1, j=10; i*j <= 500 ; i++, j=j+5)) do echo "Numbers: i=$i: j=$j" done # ONLY THE LAST EXPRESSION IN THE CONDITIONAL STATEMENT COUNTS # FOR EXITING THE LOOP. SO THIS WILL DO THE SAME THING !!: # for ((\ i=1, j=10 ;\ i<4, j<30, i*j <= 500 ;\ i++, j=j+5 \ ))
for foo in a b c ; do echo $foo; done
Note how the semi-colons indicate the end of each statement.
( # start subprocess i=1 # initialize counter set -f # no file name globbing IFS=: # use : as field separator for WORD in `grep -w "^$USER:" /etc/passwd` do case "$i" in 1) echo "===========================================" echo "USERNAME $WORD" ;; 2) ;; 3) echo "UID $WORD" ;; 4) echo "GID $WORD" ;; 5) echo "DESCRIPTION $WORD" ;; 6) echo "HOME $WORD" ;; 7) echo "SHELL $WORD" ;; *) echo "SURPRISE: $WORD" ;; esac i=$(( i % 7 + 1)) done )An array as a list
XNAME=(a b c d e f g) XNAME[0]=A XNAME[1]=B XNAME[2]='C and another C' echo $XNAME echo ${XNAME[1]} echo ${XNAME[2]} echo ${XNAME[0]} echo ${XNAME[*]} echo ${XNAME[@]} # Note the difference for STRING in "${XNAME[@]}" do echo "STRING $STRING" done for STRING in ${XNAME[*]} do echo "STRING $STRING" done # note list is expanded when loop starts even for arrays for STRING in ${XNAME[*]} do XNAME[i++]=CHANGED$i echo "STRING $STRING" done echo ARRAY IS NOW ${XNAME[@]} # note list is expanded when loop starts A=A B=B for STRING in $A $B do echo before STRING $STRING A=a B=b echo after STRING $STRING done echo $A $B
Generated by John. S. Urban