dracoblue.net

bash-tip: avoiding subshells with HERE-documents

While I was working on toolsapi-shell client, I ran into a quite common bash problem.

#!/bin/bash
POS=0
echo "POS before: $POS"
ls | while read line
do
  # do handling for each line (skipped here)
    echo "current POS: $POS"
  let 'POS += 1'
done
echo "POS after: $POS"
# Result:
#  POS before: 0
#  current POS: 0
#  current POS: 1
#  current POS: 2
#  POS after: 0

This is not what one or another expects here.

But the reason is simple: since we pipe the output of ls into while, everything between do and done is executed in a subshell. We can check this with the BASH_SUBSHELL variable:

#!/bin/bash
POS=0
echo "POS before: $POS $BASH_SUBSHELL"
ls | while read line
do
  # do handling for each line (skipped here)
    echo "current POS: $POS $BASH_SUBSHELL"
  let 'POS += 1'
done
echo "POS after: $POS $BASH_SUBSHELL"
# Result:
#  POS before: 0 0
#  current POS: 0 1
#  current POS: 1 1
#  current POS: 2 1
#  POS after: 0 0

The solution:

Bash's HERE-Documents.

POS=0
echo "POS before: $POS $BASH_SUBSHELL"
while read line
do
  # do handling for each line (skipped here)
    echo "current POS: $POS $BASH_SUBSHELL"
  let "POS += 1"
done<<EOF
`ls`
EOF
echo "POS after: $POS $BASH_SUBSHELL"
# Result:
#  POS before: 0 0
#  current POS: 0 0
#  current POS: 1 0
#  current POS: 2 0
#  POS after: 2 0

Because we don't use | here to feed the while loop, there is no extra subshell while running the loop. The important part is:

ls | while read line
do
# your logic
done

becomes (mind the backticks around ls!)

while read line
do
# your logic
done<<EOF
`ls`
EOF
In bash by
@ 25 Aug 2013, Comments at Reddit & Hackernews