In `while IFS= read..`, why does IFS have no effect?
(Sorry, long explanation)
Yes, the IFS
variable in while IFS=" " read; do …
has no effect on the rest of the code.
Let's first precise that the shell command line features two different kinds of variables:
- shell variables (which only exist within a shell, and are local to the shell)
- environment variables, which exist for every process. Those are usually preserved upon
fork()
andexec()
, so child processes inherit them.
When you call a command with:
A=foo B=bar command
the command is executed in within an environment where (environment) variable A
is set to foo
and B
is set to bar
. But with this command line, the current shell variables A
and B
are are left unchanged.
This is different from:
A=foo; B=bar; command
Here, shell variables A
and B
are defined and the command is run without environment variables A
and B
defined. Values of A
and B
are unaccessible from command
.
However, if some shell variables are export
-ed, the corresponding environment variables are synchronized with their respective shell variables. Example:
export A
export B
A=foo; B=bar; command
With this code, both shell variables and the shell environment variables are set to foo
and bar
. Since environment variables are inherited by sub-processes, command
will be able to access their values.
To jump back to your original question, in:
IFS='a' read
only read
is affected. And in fact, in this case, read
doesn't care about the value of the IFS
variable. It uses IFS
only when you ask the line to be split (and stored in several variables), like in:
echo "a : b : c" | IFS=":" read i j k; \
printf "i is '%s', j is '%s', k is '%s'" "$i" "$j" "$k"
IFS
is not used by read
unless it is called with arguments. (Edit: This is not exactly true: whitespace characters, i.e. space and tab, present in IFS
are always ignored at the beginning/end of the input line. )
Put it simple, you must read to more than one variable at a time for the IFS=<something> read ...
construct to have a visible effect in your examples1.
You miss the scope of read
in the examples. There is no modification of IFS inside the loop in your test cases. Allow me to point exactly, where does the second IFS have its effect in each of your lines:
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b read; do echo ...
^ ^
| |
from here --' `- to here :)
It is just as with any program executed in the shell. The variable you (re)define at the command-line affects the program execution. And only that (since you do not export). Therefore, to make a use of redefined IFS
in such line, you'd have to ask read
to assign values to more than one variable. Have a look a these examples:
$ data="a b c"
$ echo "$data" | while read A B C; do echo \|$A\|$B\|\|$C\|; done
|a|b||c|
$ echo "$data" | while IFS= read A B C; do echo \|$A\|$B\|\|$C\|; done
|a b c||||
$ echo "$data" | while IFS='a' read A B C; do echo \|$A\|$B\|\|$C\|; done
|| b c|||
$ echo "$data" | while IFS='ab' read A B C; do echo \|$A\|$B\|\|$C\|; done
|| || c|
1 As I've just learned from Gilles, there might actually be a benefit of setting IFS=''
(blank) when reading only one field: it avoids truncation of whitespace at the beginning of the line.