Bash While Dead Loop
I'm used to bash
's builtin read
function in while loops, e.g.:
There are 3 basic loop constructs in Bash scripting, for loop, while loop, and until loop. In this tutorial, we will cover the basics of for loops in Bash as well as the break and continue statements to alter the flow of a loop.
I've been working on some make
project, and it became prudent to split files and store intermediary results. As a consequence I often end up shredding single lines into variables. While the following example works pretty well,
it's sort of stupid, because the while loop will never run more than once. But without the while
,
Bash Read Array While Loop
The read variables are always empty when I use them. I never noticed this behaviour of read
, because usually I'd use while loops to process many similar lines. How can I use bash
's read
builtin without a while loop? Or is there another (or even better) way to read a single line into multiple (!) variables?
Conclusion
The answers teach us, it's a problem of scoping. The statement
is interpreted such that the commands cmd0
, cmd1
, and cmd4
are executed in the same scope, while the commands cmd2
and cmd3
are each given their own subshell, and consequently different scopes. The original shell is the parent of both subshells.
3 Answers
It's because the part where you use the vars is a new set of commands. Use this instead:
Note that, in this syntax, there must be a space after the {
and a ;
(semicolon) before the }
. Also -n1
is not necessary; read
only reads the first line.
For better understanding, this may help you; it does the same as above:
Edit:
It's often said that the next two statements do the same:
Well, not exactly. Fallout 4 planter mod. The first one is a pipe from head
to bash
's read
builtin. One process's stdout to another process's stdin.
The second statement is redirection and process substitution. It is handled by bash
itself. It creates a FIFO (named pipe, <(..)
) that head
's output is connected to, and redirects (<
) it to the read
process.
So far these seem equivalent. But when working with variables it can matter. In the first one the variables are not set after executing. In the second one they are available in the current environment.
Every shell has another behavior in this situation. See that link for which they are. In bash
you can work around that behavior with command grouping {}
, process substitution (< <()
) or Here strings (<<<
).
To quote from a very useful article wiki.bash-hackers.org:
This is because the commands of the pipe run in subshells that cannot modify the parent shell. As a result, the variables of the parent shell are not modified (see article: Bash and the process tree).
As the answer has been provided a few times now, an alternative way (using non builtin commands..) is this:
As you noted, the problem was that a pipe to read
is run in a subshell.
One answer is to use a heredoc:
This method is nice in that it will behave the same way in any POSIX-like shell.
Not the answer you're looking for? Browse other questions tagged bashshell-scriptpipesubshellread or ask your own question.
I have a problem in one of my shell scripts. Asked a few colleagues, but they all just shake their heads (after some scratching), so I've come here for an answer.
According to my understanding the following shell script should print 'Count is 5' as the last line. Except it doesn't. It prints 'Count is 0'. If the 'while read' is replaced with any other kind of loop, it works just fine. Here's the script:
Why does this happen and how can I prevent it? I've tried this in Debian Lenny and Squeeze, same result (i.e. bash 3.2.39 and bash 4.1.5.I fully admit to not being a shell script wizard, so any pointers would be appreciated.
wolfgangszwolfgangsz4 Answers
See argument @ Bash FAQ entry #24: 'I set variables in a loop. Why do they suddenly disappear after the loop terminates? Or, why can't I pipe data to read?' (most recently archived here).
Summary:This is only supported from bash 4.2 and up.You need to use different ways like command substitutions instead of a pipe if you are using bash.
Ignacio Vazquez-AbramsIgnacio Vazquez-AbramsThis is kind of a 'common' mistake. Pipes create SubShells, so the while read
is running on a different shell than your script, that makes your CNT
variable never changes (only the one inside the pipe subshell).
Group the last echo
with the subshell while
to fix it (there are many other way to fix it, this is one. Iain and Ignacio's answers have others.)
Long explanation:
- You declare
CNT
on your script to be value 0; - A SubShell is started on the
to
while read
; - Your
$CNT
variable is exported to the SubShell with value 0; - The SubShell counts and increase the
CNT
value to 5; - SubShell ends, variables and values are destroyed (they don't get back to the calling process/script).
- You
echo
your originalCNT
value of 0.
Try passing the data in a sub-shell instead, like it's a file before the while loop. This is similar to lain's solution, but assumes you don't want some intermittent file: