Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Why do I get different values for $x from the snippets below?

#!/bin/bash

x=1
echo fred>junk ; while read var ; do x=55 ; done <junk
echo x=$x 
#    x=55 .. I'd expect this result

x=1
cat junk | while read var ; do x=55 ; done
echo x=$x 
#    x=1 .. but why?

x=1
echo fred | while read var ; do x=55 ; done
echo x=$x 
#    x=1  .. but why?
share|improve this question
add comment

4 Answers 4

You're running into a variable scope issue. The variables defined in the while loop that is on the right side of the pipe have their own local scope context, and changes to the variable will not be seen outside of the loop. The while loop is essentially a subshell which gets a COPY of the shell environment, and any changes to the environment are lost at the end of the shell. See this StackOverflow question.

UPDATED: I neglected to point out the important fact that the while loop with it's own subshell was due to it being the endpoint of a pipe, I've updated that in the answer.

share|improve this answer
    
@jsbillings.. Okay, that explains the two last snippets, but it doesn't explain the first, where the value of $x set in the loop, is carried on as 55 (beyond the scope of the 'while' loop) –  Peter.O Mar 23 '11 at 14:39
4  
@fred.bear: It's running the while loop as the tail end of a pipeline that throws it into a subshell. –  geekosaur Mar 23 '11 at 14:44
2  
This is where bash process substitution comes into play. Instead of blah|blah|while read ..., you can have while read ...; done < <(blah|blah) –  glenn jackman Mar 23 '11 at 15:01
1  
@geekosaur: thanks for filling in the details that I neglected to include in my answer. –  jsbillings Mar 23 '11 at 15:31
1  
-1 Sorry but this answer is just wrong. It explains how this stuff works in many programming languages but not in the shell. @Gilles, below, got it right. –  jpc Mar 24 '11 at 3:13
show 3 more comments

In order to get a value out of a subshell you have to echo the value to standard out and capture it, or else store it to an external file. There is no combination of export and/or variable properties that will get a variable back out of a subshell. This is one of the reasons I don't use shell for any non-trivial control structures.

share|improve this answer
add comment

The right explanation has already been given by jsbillings and geekosaur, but let me expand on that a bit.

In most shells, including bash, each side of a pipeline runs in a subshell, so any change in the shell's internal state (such as setting variables) remains confined to that segment of a pipeline. The only information you can get from a subshell is what it outputs (to standard output and other file descriptors) and its exit code (which is a number between 0 and 255). For example, the following snippet prints 0:

a=0; a=1 | a=2; echo $a

In ksh (the variants derived from the AT&T code, not pdksh variants) and zsh, the last item in a pipeline is executed in the parent shell. (POSIX allows both behaviors.) So the snippet above prints 2.

A useful idiom is to include the continuation of the while loop (or whatever you have on the right-hand side of the pipeline, but a while loop is actually common here) in the pipeline:

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}
share|improve this answer
1  
Thanks Gilles .. That a=0; a=1 | a=2 gives a very clear picture.. and not only of the localization of internal state, but also that a pipeline doesn't actually need to send anything through the pipe (other than the exit code(?).. In itself that is an interesting insight into a pipe ... I did manage to get my script running with < <(locate -ber ^\.tag$), thanks to the original slightly unclear answer and geekosaur and glenn jackman's comemnts.. I was initially in a dilemma about accepting the answer, but the nett result was pretty clear, especially with jsbillings follow-up comment :) –  Peter.O Mar 24 '11 at 12:31
add comment

I would suggest to switch to ksh. Your script by different shells

sa@hp:~/tmp# bash 1.sh
x=55
x=1
x=1
sa@hp:~/tmp# ksh 1.sh
x=55
x=55
x=55
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.