1

The variable x in the first example doesn't get decremented, while in the second example it works. Why?

Non working example:

#!/bin/bash

x=100
f() {
  echo $((x--)) | tr 0-9 A-J
  # this also wouldn't work: tr 0-9 A-J <<< $((x--))
  f
}
f

Working example:

#!/bin/bash

x=100
f() {
  echo $x | tr 0-9 A-J
  ((x--))
  # this also works: a=$((x--))
  f
}
f

I think it's related to subshells since I think that the individual commands in the pipeline are running in subshells.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
FranMowinckel
  • 4,233
  • 1
  • 30
  • 26
  • The thing to understand is that a subshell is a **completely separate process**. When it exits, all its state exits with it. – Charles Duffy Apr 21 '17 at 15:02
  • 1
    ...so, all variables not explicitly made local are global to the shell they're in, but when you start a subshell, it's *not actually the same shell* running that code (in a command substitution, a process substitution, an out-of-process pipeline component, etc). – Charles Duffy Apr 21 '17 at 15:03
  • 1
    ("out-of-process" pipeline component because POSIX doesn't actually specify which, if any, parts of a pipeline run in a subshell, and different shells do it differently: For instance, in ksh the last component is in-process, whereas in most versions of bash [excepting very new ones with `lastpipe` enabled and job control turned off] *no* component is in-process). – Charles Duffy Apr 21 '17 at 15:05
  • 1
    By the way, [BashFAQ #24](http://mywiki.wooledge.org/BashFAQ/024) is closely related. – Charles Duffy Apr 21 '17 at 15:08

1 Answers1

0

It does decrement if you don't use a pipeline (and avoid a sub shell forking):

x=10

f() {
   if ((x)); then
      echo $((x--))
      f
   fi
}

Then call it as:

f

it will print:

10
9
8
7
6
5
4
3
2
1

Since decrement is happening inside the subshell hence current shell doesn't see the decremented value of x and goes in infinite recursion.


EDIT: You can try this work around:

x=10

f() {
   if ((x)); then
      x=$(tr 0-9 A-J <<< $x >&2; echo $((--x)))
      f
   fi
}

f

To get this output:

BA
J
I
H
G
F
E
D
C
B
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • The pipe it's required, that's the problem. I don't mind about the non-exit condition, it's just a made up quick example. But your last paragraph it's interesting, the `x` variable it's like *copied* to the subshell? No way to update the original from it? I thought that all variables in bash were global by default – FranMowinckel Apr 21 '17 at 14:49
  • Changes made in child shell are not ported back to parent shell. Only way to do that is ti print the value in child shell and then set it again in parent shell. – anubhava Apr 21 '17 at 14:52