开发者

Linux shell bug? Variable assignment in pipe does not work

开发者 https://www.devze.com 2022-12-12 06:15 出处:网络
How come FILE_FOUND is 0 at the end of this bugger : FILE_FOUND=0 touch /tmp/$$.txt ls -1 /tmp/$$.* 2>/dev/null | while read item; do

How come FILE_FOUND is 0 at the end of this bugger :

FILE_FOUND=0

touch /tmp/$$.txt

ls -1 /tmp/$$.* 2>/dev/null | while read item; do
    FILE_FOUND=1
    e开发者_运维技巧cho "FILE_FOUND = $FILE_FOUND"
done

echo "FILE_FOUND = $FILE_FOUND"

rm -f /tmp/$$.txt 2>/dev/null

??!!

On Unix FILE_FOUND stays at 1 (as it should), but on Linux (RedHat, Cygwin, ..) it jumps back to 0!!

Is it a Linux shell feature, not a bug? :)

Please help.


common issue which is caused because you're piping into the while, which is therefore run in a subshell, which can't pass environment variables back to its parent. I'm guessing that "unix" is different in this regard as you're running a different shell there (ksh?)

piping to the while loop may not be required. could you use this idiom instead?

for item in /tmp/$$.*; do
    ....
done

If you must use a subshell, then you'll have to do something external to the processes like:

touch /tmp/file_found


This is a "feature" of the "bash" shell. The "bash" manual entry states this clearly:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

The same construct executed with the Korn shell ("ksh") runs the "while" in the same process (not in a subshell) and hence gives the expected answer. So I checked the spec.

The POSIX shell specification is not crystal clear on this, but its does not say anything about changing the "shell execution environment", so I think that the UNIX / Korn shell implementations are compliant and the Bourne Again shell implementation is not. But then, "bash" does not claim to be POSIX compliant!


It has already been mentioned, but since you're piping into the while, the entire while-loop is run in a subshell. I'm not exactly sure which shell you're using on 'Unix', which I suppose means Solaris, but bash should behave consistenly regardless of platform.

To solve this problem, you can do a lot of things, the most common is to examin the result of the while loop somehow, like so

result=`mycommand 2>/dev/null | while read item; do echo "FILE_FOUND"; done`

and look for data in $result. Another common approach is to have the while loop produce valid variable assignments, and eval it directly.

eval `mycommand | while read item; do echo "FILE_FOUND=1"; done`

which will be evaluated by your 'current' shell to assign the given variables.

I'm assuming you don't want to just iterate over files, in which case you should be doing

for item in /tmp/$$.*; do
  # whatever you want to do
done


As others have mentioned, it's the extra shell you're creating by using the pipe notation. Try this:

while read item; do
    FILE_FOUND=1
    echo "FILE_FOUND = $FILE_FOUND"
done < <(ls -1 /tmp/$$.* 2>/dev/null)

In this version, the while loop is in your script's shell, while the ls is a new shell (the opposite of what your script is doing).


another way , bring the result back,

FILE_FOUND=0
result=$(echo "something" | while read item; do
    FILE_FOUND=1
    echo "$FILE_FOUND"

done )
echo "FILE_FOUND outside while = $result"


Ummmm... ls -1 not l is on your script?

0

精彩评论

暂无评论...
验证码 换一张
取 消