Why is this shell script printing inputs twice?
I expected the script to ignore the inputs after 5.
Script:
#! /bin/bash echo "Enter 5 words : " read a b c d e printf "> %s %s %s %s %s <" $a $b $c $d $e
Output:
<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="deabadbbac9eb2b7b0aba6">[email protected]</a>:~$ pico ifs2.sh <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2653554354664a4f48535e">[email protected]</a>:~$ ./ifs2.sh Enter 5 words : 1 2 3 4 5 > 1 2 3 4 5 <<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a2d7d1c7d0e2cecbccd7da">[email protected]</a>:~$ ./ifs2.sh Enter 5 words : 1 2 3 4 5 6 > 1 2 3 4 5 <> 6 <<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3742445245775b5e59424f">[email protected]</a>:~$ ./ifs2.sh Enter 5 words : 1 2 3 4 5 6 7 8 9 0 > 1 2 3 4 5 <> 6 7 8 9 0 <<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="641117011624080d0a111c">[email protected]</a>:~$
And, the following script works no matter what is set to $IFS. Why?
#! /bin/bash old="$IFS" IFS=":" echo "IFS = $IFS" echo "Enter 5 words : " read a b c d e printf "> %s %s %s %s %s <" $a $b $c $d $e IFS="$old"
Output:
<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3144425443715d585f4449">[email protected]</a>:~$ ./ifs2.sh IFS = : Enter 5 words : 1 2 3 4 5 > 1 2 3 4 5 <<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6d181e081f2d0104031815">[email protected]</a>:~$ ./ifs2.sh IFS = : Enter 5 words : 1 2 3 4 5 > 1 2 3 4 5 <<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="651016001725090c0b101d">[email protected]</a>:~$ ./ifs2.sh IFS = : Enter 5 words : 1:2:3:4:5 > 1 2 3 4 5 <<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d3a6a0b6a193bfbabda6ab">[email protected]</a>:~$
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
You have three problems:
- With
read, if there are fewer variable names than fields in the input, the last var will be bound to all the remaining fields on the line, with delimiters. That means that$egets5 6in your first unexpected example. - Because all of
$a..$eare unquoted, their values undergo field splitting. If$eholds “5 6” then it expands into two arguments to the command. -
printfconsumes all its arguments, using as many arguments at once as there are%substitutions, repeatedly. This is buried in the documentation as:The
formatoperand shall be reused as often as necessary to satisfy the argument operands. Any extracorsconversion specifiers shall be evaluated as if a null string argument were supplied; other extra conversion specifications shall be evaluated as if a zero argument were supplied.In other words, if there are unused arguments it starts over again and processes them from the beginning too, including the whole format string. This is useful when you want to format an entire array, say:
printf '%b ' "${array[@]}"Your
printfcommand gets one argument from each of$a..$d, and then however many are left over from$e. When$eis “5 6“,printfhas two goes around, the second just getting6to format. When it’s5 6 7 8 9 10it has the full range of substitutions for the second printing.
You can avoid all of these by adding an extra dummy field to read, and quoting your parameter substitutions (which is always a good idea):
read a b c d e dummy printf "> %s %s %s %s %s <" "$a" "$b" "$c" "$d" "$e"
This will give:
Enter 5 words : 1 2 3 4 5 6 7 8 9 10 > 1 2 3 4 5 <
dummy gets all the extra fields, and printf only gets the five arguments you expected.
Your second edited-in question has a similar answer: only a gets a value when IFS doesn’t have a space. That means $b..$e expand to nothing, so printf only gets a single argument. Your spaces from the format string are printed, with nothing substituted in between them (“as if a null string argument were supplied”).
Method 2
printf "> %s < " 1 2 3
will print
> 1 <> 2 <> 3 <
printf "> %s %s <" 1 2 3
prints
> 1 2 <> 3 <
printf eats up all the arguments to satisfy its format string and then repeats until all arguments are processed.
The second scripts works because only $a is ever assigned to and therefore the command doesn’t overflow into additional iterations (there’s only ever one iteration).
This behavior is documented in the text provided with help printf:
… The format is re-used as necessary to consume all of the
arguments. If
there are fewer arguments than the format requires, extra format
specifications behave as if a zero value or null string, as appropriate,
had been supplied. …
and is mandated by http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0