0

I am using Bash 5.0.17 on Ubuntu 20.04

When I run the following commands:

IFS=":"; for i in "1:2:3"; do echo $i; done
# output is: 1 2 3

IFS=":"; for i in "1:2:3"; do echo "$i"; done output is: 1:2:3

IFS=":"; for i in "1:2:3"; do printf "%s\n" $i; done

output is:

1

2

3

IFS=":"; for i in "1:2:3"; do printf "%s\n" "$i"; done

output is: 1:2:3

This is confusing for me.

  1. Why doesn't echo print each token in a separate line?
  2. Why does printf works as expected when $i is not quoted?
  3. Why both echo and printf fail when $i is quoted?

I appreciate your help

  • I'm not sure how to put a finer point on it, but when you say the utilities "fail" in point 3, you seem to be preassuming that your expectation is correct and the tools are wrong. For run-of-the-mill standard tools, that seems like a bold presumption to make. – ilkkachu Dec 19 '21 at 20:04
  • @ilkkachu: Why doesn't IFS=":" cause the string "1:2:3" to split to three tokens so the for loop iterates over it 3 times? that's the key question – Amazigh_05 Dec 19 '21 at 20:49

1 Answers1

4

The loop for i in "1:2:3"; do ... runs just once. There's no word splitting of static literal strings, only expansions, and even then only unquoted expansions. You'd see different results with e.g.

IFS=:
var="1:2:3"
for i in $var; do...

The rest is just how echo and printf work. echo joins the arguments with spaces, and prints the joined string followed by a single newline. But printf repeats the format string as many times as necessary to accommodate all arguments, so you get multiple newlines.

So, in the first one, echo $i runs with i set to 1:2:3. The expansion is unquoted, so it's split, and echo get the three arguments 1, 2, 3. It joins them with spaces, giving 1 2 3, and the output is that plus the newline. That's the same as running echo 1 2 3. (Or even echo 1 2 "3", since the number of unquoted spaces on the shell command line doesn't matter.)

The third one is similarly the same as printf "%s\n" 1 2 3, and with the format string repeated, the output is on three lines. Something like printf "%s %s\n" 1 2 3 would use two arguments per repetition, and would print 1 2 on one line, and 3 on another.

ilkkachu
  • 138,973
  • I am quoting Gilles answer: "IFS isn't directly related to looping, it's related to word splitting. IFS indirectly determines how the output from the command is broken up into pieces that the loop iterates over." https://unix.stackexchange.com/a/16195/504663 – Amazigh_05 Dec 19 '21 at 20:47
  • So I am expecting IFS=":" to cause the for loop to iterate over the string 3 times why doesn't that happen? – Amazigh_05 Dec 19 '21 at 20:48
  • 1
    @OK-Validation, because of what I said in the second sentence: IFS defines what characters are used as separators for word splitting, and there's no word splitting for plain strings on the command line. echo 1:2:3 prints 1:2:3 even if IFS contains the colon. (That was different in some ancient shells.) Word splitting only happens for the results of unquoted expansions. In for i in "1:2:3"; do, there's only one word, it's quoted, and there's no expansions involved. Hence, no splitting, and the loop runs once, with i set to 1:2:3. – ilkkachu Dec 19 '21 at 20:58
  • what do you mean by "static strings ..." are there dynamic strings? – Amazigh_05 Dec 19 '21 at 21:00
  • I am sorry but I think I have huge misunderstanding of how IFS works. Would you please mind to address that in your answer? – Amazigh_05 Dec 19 '21 at 21:01
  • 1
    @OK-Validation IFS (almost) only comes into play in expansions. In your loop, for i in "1:2:3" contains no expansion, so you have a loop over a "static string" (static in the sense that there is no variable expansion or other type of expansion involved when creating it; it's just the literal string 1:2:3). The echo command in the loop contains an expansion of the variable i. That's where IFS does something depending on whether $i is quoted or not. – Kusalananda Dec 19 '21 at 22:21
  • @they: Thanks, would you please add that as an answer? – Amazigh_05 Dec 20 '21 at 11:04
  • 1
    @OK-Validation, I'm not sure how to elaborate on that. Expansions are stuff like variable/parameter expansions ($var or ${var}, or ${var##*/}) or command substitutions ($(some commands here)). Word splitting happens on the results of those, if the expansion is not quoted. So echo $var splits, echo "$var" doesn't. It doesn't happen on a literal string, echo foobar doesn't split. (see also http://mywiki.wooledge.org/WordSplitting) Yes, "literal" would have been a better choice of word than "static". – ilkkachu Dec 20 '21 at 11:11