6

Consider case 1:

$ COUNT=0 ; while [ $COUNT -ne 3  ]; do echo $COUNT; COUNT=$(expr $COUNT + 1 );done
0
1
2
$ echo $COUNT
3

By the end of the loop COUNT variable is 3, as expected

Consider case 2:

$ COUNT=1; find . -print0 | while IFS= read -r -d '' FILE; do echo "$FILE"; expr $COUNT + 1; COUNT=$(expr $COUNT + 1 )  ;done 
.
2
./file name
3
./file
4
./a
b
5
$ echo $COUNT
1

As you can see, in this case COUNT remained the same. Why ? It can be seen that it's changing inside the while loop, but not once it's out of the loop.

What exactly I am missing here ?

Sergiy Kolodyazhnyy
  • 103,293
  • 19
  • 273
  • 492
  • 3
    This looks like useful: http://stackoverflow.com/a/16854326/1391444 – Jacob Vlijm Nov 30 '15 at 09:10
  • @JacobVlijm that's pretty much a duplicate of my question . . . except we cannot close-as-dupe across sites :) Thanks – Sergiy Kolodyazhnyy Nov 30 '15 at 09:19
  • No, I wasn't intending to have it closed. I was pretty sure however that one of the `bash` -persons would be much more into it than I am (in `bash`), to point out what exactly happens *and* to propose an elegant solution in this case :).... And I see you did it :) – Jacob Vlijm Nov 30 '15 at 09:22
  • 1
    Hehe, well maybe I should start posting python questions so that you can answer those :) Well frankly, I expected more enthusiasm from bash folks here as well . . . This was utterly confusing me for half the evening before i decided to post a question. But in any case I just found this : http://mywiki.wooledge.org/BashFAQ/024 . . .There's pretty much several workarounds. Case closed – Sergiy Kolodyazhnyy Nov 30 '15 at 09:28
  • That's quite a nice story! – Jacob Vlijm Nov 30 '15 at 09:31
  • @SergiyKolodyazhnyy you're lucky you spent only half the evening tracking down this boondoggle! only problem for me is, I'm piping out to `dialog` for a long-running loop, but need to track several variables that are modified in the loop – Doktor J Sep 08 '22 at 21:58

1 Answers1

7

In your first case, all commands executed in the same shell. The content of COUNT is changed.

In your second case a subshell is started with piping |, and changes in the subshell have no effect in the current shell. But he subshell knows the variable COUNT and the first output is 2.

A.B.
  • 89,123
  • 21
  • 245
  • 323
  • Makes sense. How does one make it work as expected though ? – Sergiy Kolodyazhnyy Nov 30 '15 at 09:17
  • OK, got it. I have to group the while together : `COUNT=0; find . -print0 | ( while IFS= read -r -d '' FILE; do echo "$FILE"; COUNT=$((COUNT+1));done ; echo $COUNT )` – Sergiy Kolodyazhnyy Nov 30 '15 at 09:24
  • Maybe you could edit it into the answer if @Serg agrees. Always nice to have the explanation & solution together in the answer. – Jacob Vlijm Nov 30 '15 at 09:27
  • Sure, please use that ! I am always supporting anything that can help other non-pro users like myself :) – Sergiy Kolodyazhnyy Nov 30 '15 at 09:30
  • 3
    @Serg That won't work, because the `while` loop still runs in a forked shell; in this case you can use a process substitution to invert the commands so that `find` runs in the forked shell and the `while` loop runs in the current shell: `COUNT=0; while IFS= read -r -d '' FILE; do echo "$FILE"; COUNT=$((COUNT+1));done <<(find . -print0); echo $COUNT` – kos Nov 30 '15 at 11:28
  • @kos or a named pipe :D I've been reading up on all the various workarounds to this this night. Fun stuff ! – Sergiy Kolodyazhnyy Nov 30 '15 at 11:29
  • @Serg: Named pipes are a PITA because you have to get rid of them when you're done. – Kevin Nov 30 '15 at 16:58