Bash: Piped `while-read' loop starts subshell

From FVue
Jump to: navigation, search

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:

Comments

blog comments powered by Disqus