Variables Set in a Bash 'While Read' Loop Are Unset After It

Variables set in a bash 'while read' loop are unset after it

The problem is that you add the elements in a subshell. To elaborate:

command1 | command2

causes command2 to be executed in a subshell, which is a separate execution environment. This implies that the variables set in command2 are not available to the current shell, whose execution environment is not affected. You could instead use process substitution to achieve the same:

while read line; do
...
done < <(ls -l)

Please note that parsing ls is not recommended; try using find instead.

A variable modified inside a while loop is not remembered

echo -e $lines | while read line 
...
done

The while loop is executed in a subshell. So any changes you do to the variable will not be available once the subshell exits.

Instead you can use a here string to re-write the while loop to be in the main shell process; only echo -e $lines will run in a subshell:

while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable \$foo updated to $foo inside if inside while loop"
fi
echo "Value of \$foo in while loop body: $foo"
done <<< "$(echo -e "$lines")"

You can get rid of the rather ugly echo in the here-string above by expanding the backslash sequences immediately when assigning lines. The $'...' form of quoting can be used there:

lines=$'first line\nsecond line\nthird line'
while read line; do
...
done <<< "$lines"

Variables getting reset after the while read loop that reads from a pipeline

I ran into this problem yesterday.

The trouble is that you're doing find $loc -name "*.bsp" | while read. Because this involves a pipe, the while read loop can't actually be running in the same bash process as the rest of your script; bash has to spawn off a subprocess so that it can connect the the stdout of find to the stdin of the while loop.

This is all very clever, but it means that any variables set in the loop can't be seen after the loop, which totally defeated the whole purpose of the while loop I was writing.

You can either try to feed input to the loop without using a pipe, or get output from the loop without using variables. I ended up with a horrifying abomination involving both writing to a temporary file AND wrapping the whole loop in $(...), like so:

var="$(producer | while read line; do
...
echo "${something}"
done)"

Which got me var set to all the things that had been echoed from the loop. I probably messed up the syntax of that example; I don't have the code I wrote handy at the moment.

Variable value reset after loop

Do it this way:

#!bin/bash
#
ls *.csv > /tmp/CSVfiles
ADDITIONALTABLES="FXRATES ANVIL"
while read staticFile
do
staticTable=`basename $staticFile`
echo $staticTable
ADDITIONALTABLES="$ADDITIONALTABLES ${staticTable%.csv}"
echo "in $ADDITIONALTABLES"
done < /tmp/CSVfiles
echo "out $ADDITIONALTABLES"

Like others pointed out in the comments, the pipe runs in a subshell. But if you use redirection like above it works.

Variable incremented in bash while loop resets to 0 when loop finishes

A pipe spawns a subshell, use a process substitutions instead:

while read -r f
do
count=$(($count+1))
echo "Count is at ${count}"
done < <(ls)

Also note that you shouldn't parse the output of ls.

And your example seems to count numbers of files and directories in current directory, which can be done with find and wc:

find -maxdepth 1 -mindepth 1 -printf "\n" | wc -l

or you can avoid ls with a for loop and globbing:

for f in * .*; do
[ -e "$f" ] || continue
count=$((count + 1))
echo "Count is at ${count}"
done

Shell variables set inside while loop not visible outside of it

When you pipe into a while loop in Bash, it creates a subshell. When the subshell exits, all variables return to their previous values (which may be null or unset). This can be prevented by using process substitution.

LONGEST_CNT=0
while read -r line
do
line_length=${#line}
if (( line_length > LONGEST_CNT ))
then
LONGEST_CNT=$line_length
LONGEST_STR=$line
fi
done < <(find samples/ ) # process substitution

echo $LONGEST_CNT : $LONGEST_STR

Bash: Retain variable's value after while loop

When you use a pipe you are automatically creating subshells so that the input and output can be hooked up by the shell. That means that you cannot modify the parent environment, because you're now in a child process.

As anubhava said though, you can reformulate the loop to avoid the pipe by using process substitution like so:

while read commit; do
TEMP_FLAG=true
done < <( git log --pretty="%H|%s" --skip=1 )

printf "%s\n" "$TEMP_FLAG"

bash while read loop arguments from variable issue

operators_list=$'andrii,bogdan,eios,fre,kuz,pvm,sebastian,tester,tester2,vincent,ykosogon'

So you have strings separated by ,. You can do that multiple ways:

  1. using bash arrays:

IFS=, read -a operators <<<$operators_list
for op in "${operators[@]}"; do
echo "$op"
done

  1. Using a while loop, like you wanted:

while IFS= read -d, -r op; do
echo "$op"
done <<<$operators_list

  1. Using xargs, because why not:

<<<$operators_list xargs -d, -n1 echo

The thing with IFS and read delimeter is: read reads until delimeter specified with -d. Then after read has read a full string (usually whole line, as default delimeter is newline), then the string is splitted into parts using IFS as delimeter. So you can:

while IFS=: read -d, -r op1 op2; do
echo "$op1" "$op2"
done <<<"op11:op12,op12:op22"


Related Topics



Leave a reply



Submit