I met an interesting issue while working with this code from Stack Overflow: tripleee's answer on "How to check if a file contains only zeros in a Linux shell?"
Why does the same bash code produce different result depending on interactive shell or subshell?
Make all-zeros file with name your_file
.
$ truncate -s 1K your_file
Interactive shell example
$ tr -d '0円' <your_file | grep -am 1 ^ ; echo $?
1
The same code but using subshell
$ bash -c 'tr -d '0円' <your_file | grep -am 1 ^ ; echo $?'
0
And also interesting fact. I changed original example by adding -a
option ("equivalent to --binary-files=text") because without this option interactive shell works but subshell:
$ bash -c 'tr -d '0円' <your_file | grep -m 1 ^ ; echo $?'
grep: (standard input): binary file matches
0
P.S. I use bash 5.2.37(1)-release from Ubuntu 25.04
1 Answer 1
This is just a quoting issue. When you run this:
bash -c 'tr -d '0円' <your_file | grep -am 1 ^ ; echo $?'
It becomes:
bash -c 'tr -d '
0円
(which is just0
)' <your_file | grep -am 1 ^ ; echo $?'
You are using single quotes within the single quotes, so your '0円'
actually becomes 0円
which is just 0
. You can see it happen with set -x
:
$ bash -c 'tr -d '0円' <your_file | grep -am 1 ^ ; echo $?'
+ bash -c 'tr -d 0 <your_file | grep -am 1 ^ ; echo $?'
0
The presence of NUL (0円
) in a file will mark it as binary data. So the fact that grep
complained about that is also a hint. As you can see above, you are not actually removing 0円
, you are removing 0
.
Next, the exit status of grep
, which is what you have in your $?
variable, is 0
if at least one line matches and 1
if no line matches. In your first command, tr -d '0円' <your_file | grep -am 1 ^ ; echo $?
, you are deleting all 0円
from a file that consists of nothing but 0円
so you end up with an empty file. Therefore, nothing is matched and you get an exit status of 1
.
With your second command, bash -c 'tr -d '0円' <your_file | grep -am 1 ^ ; echo $?'
, because of the quoting issue, you are removing 0
. The file has no 0
, so nothing is removed and there is one "line" to be found and so the output is 0
.
The second shell is completely irrelevant, you can get the same behavior without it:
$ tr -d '0円' <your_file | grep -am 1 ^ ; echo $?
1
$ tr -d 0 <your_file | grep -am 1 ^ ; echo $?
0
To get the expected output in the second shell, use:
$ bash -c 'tr -d "0円" <your_file | grep -am 1 ^ ; echo $?'
1
'0円'
, try'\''0円'\''
. Or just use double-quotes around the entire sub-shell command (but you'll need to escape the $ in $? as\$?
so that the shell you're running it from doesn't interpolate it)