Bash: Piped `while-read' loop starts subshell
From FVue
Contents
Problem
Bash can sometimes start a subshell in a PIPED `while-read' loop. This causes variables introduced within the while-read loop to disappear. For example:
echo nothing | while read line; do foo=bar echo foo1: $foo done echo foo2: $foo # Example output: # foo1: bar # foo2: (shouldn't be empty)
Environment
- Debian 4.0
- GNU bash, version 3.1.17(1)-release (i486-pc-linux-gnu)
Solutions
Solution 1. Store loop result (preferred)
By encapsulating the while-loop as a compound command ((list)
or { list; }
makes no difference?), the result of the while-loop can be echoed and catched by an outer variable:
unset -v foo foo=$( echo nothing | { while read line; do foo=bar done echo $foo } ) echo foo2: $foo # Example output: # foo2: bar (ok)
Solution 2. Redirected while loop (not POSIX compatible)
Rewrite the script as a REDIRECTED `while-read' loop. For example, using process substitution:
while read line; do foo=bar echo foo1: $foo done < <(echo nothing) echo foo2: $foo # Example output: # foo1: bar # foo2: bar
However, when POSIX-mode is enabled (set -o posix
), bash gives an error:
bash: syntax error near unexpected token `<'
See also
- Advanced Bash-Scripting Guide: Chapter 19. I/O Redirection
- The problem is mentioned within comments of the "Advanced Bash-Scripting Guide"
- Bash: Gotchas
- More bash code which was non-intuitive to me
Journal
20070523
- Altering a variable outside the scope of a loop influenced by a subshell - nion's blog
- Blog entry (April 8, 2007) about the problem, with useful comments
- Advanced Bash-Scripting Guide - Gotchas
- See example 31-3: "Piping echo output to a read may produce unexpected results. In this scenario, the read acts as if it were running in a subshell."
20070525
- SHELLdorado Newsletter 1/2005 - April 30th, 2005
- See newsletter item: "Shell Tip: How to read a file line-by-line"
20071214
- Altering a variable outside the scope of a loop influenced by a subshell
- Blog article with proposed solution to work in bash POSIX mode, but it doesn't work:
set -o posix foo= echo nothing | { while read line; do foo=bar echo foo1: $foo done;} echo foo2: $foo # Example output: # foo1: bar # foo2:
Advertisement