0

TL;DR

I've researched this, and you can see what I've found in the More Details and My Attempts section.

My command is

date && echo "hi 1" && echo "1/0" | bc >/dev/null 2>&1 && echo "hi 2" && date +'%s'

What I expect/want for output is something like

Tue, Nov 17, 2020  3:13:49 PM
hi 1

and have it stop. My thought is that the "echo / bc" part of the expression (with assumed operational precedence indicated by parentheses)

<part-before> ( echo "1/0" | bc >/dev/null 2>&1 ) && <part-after>

should make it so that <part-after> doesn't get reached. However, my actual output (with the date commands as for right now) is

Tue, Nov 17, 2020  3:13:49 PM
hi 1
hi 2
1605651229

The Question: How can I get it to fail and stop at the divide-by-zero problem without outputting anything?


(Anything after this is to provide more details.)

A quick note: Even without redirection, the divide-by-zero failure doesn't stop the && chain, i.e. the short-circuited-'and'-operator chain. Behold,

bballdave025@MACHINE ~
$ date && echo "hi 1" && echo "1/0" | bc && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:14:47 PM
hi 1
Runtime error (func=(main), adr=3): Divide by zero
hi 2
1605651287

bballdave025@MACHINE ~
$

I'd like to use the bc method, mainly to avoid bash-isms like the $(( )) that will follow. (Speaking of bash-isms, trying &>/dev/null didn't change anything.) I want to end up with something like the following example

bballdave025@MACHINE ~
$ date && echo "hi 1" && echo "$((1/0))" >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:15:17 PM
hi 1
-bash: 1/0: division by 0 (error token is "0")

bballdave025@MACHINE ~
$

except I DON'T want the error to show up. I can get the error to be gone as long as the error result of division by zero isn't output, i.e. isn't echo-ed (which defeats the purpose of redirecting stdout and stderr) like so

### NOT WHAT I WANT, other than what is output ###
bballdave025@MACHINE ~
$ date && echo "hi 1" && ((1/0)) >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:15:44 PM
hi 1

bballdave025@MACHINE ~
### NOT WHAT I WANT, other than what is output. ###

On a more theoretical note: Something is passing a 0 (zero) to the &&; what is it?

I already found this source (archived), which states that

Redirection is not a command.

I'm not sure where to look, now.


Edit: I've done a bit more research. There are two things I'd like to add.

  1. I found a couple of sources. One of them (archived) I think might hold the key, but I still don't understand how it applies. The other (archived) describes short-circuiting, which I think might be important.

  2. I found a strange behavior when I accidentally replaced >/dev/null with >dev/null, thus introducing an error after the divide-by-zero problem. Here's what I saw,

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:41:39 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:41:48 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$

That honestly confuses me even more, especially given that first, possibly-the-key source.


More Details and My Attempts

I'm trying to understand the && chain and redirection to /dev/null in order to set up a longer chain that includes bigger executables. I also want to understand command list separators (archived) better, in general No matter how those executables work with &&, I'd really like to understand what is happening here.

I've been working on this toy example before I go on to my real example. I've been trying to "work myself backwards" to see if I could find the problem. Maybe this will be of use for figuring things out.

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:16:41 PM
hi 1
hi 2
1605651401

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:16:52 PM
hi 1
hi 2
1605651412

## LET'S SEE WHAT GETS SENT TO /dev/null

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:17:06 PM
hi 1
hi 2
1605651426

bballdave025@MACHINE ~
$ cat abc
1

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:17:18 PM
hi 1
hi 2
1605651438

bballdave025@MACHINE ~
$ cat abc
Runtime error (func=(main), adr=3): Divide by zero

## LET'S TRY GIVING IT ANOTHER ERROR RIGHT AFTER

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:24:35 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:00 PM
hi 1
-bash: dev/null: No such file or directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:14 PM
hi 1
hi 2
1605651914

bballdave025@MACHINE ~
$ cat abc
1

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:24 PM
hi 1
hi 2
1605651924

bballdave025@MACHINE ~
$ cat abc
Runtime error (func=(main), adr=3): Divide by zero

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" | bc >abc/def 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:41 PM
hi 1
-bash: abc/def: Not a directory

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" | bc >abc/def 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:25:46 PM
hi 1
-bash: abc/def: Not a directory

## GO BACK WITHOUT `bc` and without redirection

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:03 PM
hi 1
hi 2
1605652143

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" >/dev/null 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:12 PM
hi 1
hi 2
1605652152

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:21 PM
hi 1
hi 2
1605652161

bballdave025@MACHINE ~
$ cat abc
1/1

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" >abc 2>&1 && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:29:29 PM
hi 1
hi 2
1605652169

bballdave025@MACHINE ~
$ cat abc
1/0

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$b" && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:30:09 PM
hi 1
1/1
hi 2
1605652209

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "$a/$c" && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:30:14 PM
hi 1
1/0
hi 2
1605652214

bballdave025@MACHINE ~
$ a=1; b=1; c=0; date && echo "hi 1" && echo "hi 2" && date +'%s'
Tue, Nov 17, 2020  3:30:26 PM
hi 1
hi 2
1605652226

bballdave025@MACHINE ~
$

This SO exchange (archived) might have something useful.

4
  • Thanks for the comment, @steeldriver . Why wouldn't an arithmetic error not cause bc to set a non-zero exit? My understanding is that the very reason for non-zero exit statuses is to show that an error occurred. Nov 17 '20 at 22:50
  • Thanks to all who commented and answered. I accepted the answer from @Ángel over that of ilkkachu (won't let me notify one user) due to its completeness and the fact that it showed how to get the desired output I posted. Nov 19 '20 at 17:17
  • It was a very hard decision, though, as the answer from @ilkkachu was also excellent. Nov 19 '20 at 17:18
  • My apologies for lack of clarity. I was just investigating the &&-chain behavior and the first and (I thought) easiest)error I could think of quickly was division by zero. Thanks to @Ángel for showing me that there are at least three issues at play which mix, and thanks for descibing that mix. It was precisely the mixing that confused me, and the answer helped me to understand the &&-chain concept, exit codes/outputs, pipes much better. I hope it will help others as well. Nov 19 '20 at 17:21
2

Even without redirection, the divide-by-zero failure doesn't stop the && chain, i.e. the short-circuited-'and'-operator chain.

GNU bc:

$ echo "1/0" | bc 
Runtime error (func=(main), adr=3): Divide by zero
$ echo $?
0

Busybox:

$ echo "1/0" | busybox bc 
bc: divide by zero
$ echo $?
1

POSIX bc:

NAME
bc - arbitrary-precision arithmetic language
EXIT STATUS
The following exit values shall be returned:
0 -- All input files were processed successfully.
unspecified -- An error occurred.

RATIONALE
The exit status for error conditions has been left unspecified for several reasons:

  • The bc utility is used in both interactive and non-interactive situations. Different exit codes may be appropriate for the two uses.
  • It is unclear when a non-zero exit should be given; divide-by-zero, undefined functions, and syntax errors are all possibilities.
  • It is not clear what utility the exit status has

It doesn't break the chain because your bc doesn't consider a division by zero something that would deserve returning a falsy exit status.

GNU bc does return a falsy exit status if it can't read an input file, though.

I'd like to use the bc method, mainly to avoid bash-isms like the $(( )) that will follow.

$(( )) isn't a Bashism, it's part of the POSIX shell language (Arithmetic expansion).


I'm not sure what the context here is, how the division by zero relates to the conditional and the && chain, and what you're trying to do in the end, so it's hard to offer any suggestions.

An alternative to relying on detecting the division by zero would be to check for a zero divisor before trying the division in the first place.

1

This is a mixing of XY problem, question about pipes, outputs and division. I will try to clarify them, although I feel there is not a 'single' question to answer.

Shell language

Grouping

echo "before" && echo inside1 && echo inside2 && echo "after"

The way to group several statements would be to use either round or curly brackets:

echo "before" && ( echo inside1 && echo inside2 ) && echo "after"
echo "before" && { echo inside1 && echo inside2; } && echo "after"

() would run in a subshell, while { } just form a list in the current environment. Both can be used to redirect the output of that portion.

echo "before" && ( echo inside1 && echo inside2 ) > /dev/null && echo "after"
echo "before" && { echo inside1 && echo inside2; } > /dev/null && echo "after"

Return values

Each command returns a value when it finishes. It is saved on $?, so you can view it with echo $? immediately after the execution.

It is agreed that returning zero means it was successful, and non-zero unsuccessful. Whatever the program may consider a success. The and && and or || shell operators work with this concept. There are also a couple of handy tiny utils called true and false which always return zero (success) and nonzero (failure) respectively.

if true && false; then
 echo Yes
else
 echo No
fi

When you chain a series of &&, the right hand of the operator is only executed if the left hand returned success.

For example, you may want to create a folder named photos and move there all the files.

You first could try with

mv *.jpg photos

But if there's a single photo, and no photos folder, it will rename the photo to photos.

You can use && to only move the images if the folder creation succeeded:

mkdir photos && mv *.jpg photos

If the mkdir failed, the mv is not executed.

This is so because on failure mkdir will return non-zero.

However, if the folder photos already exists, mkdir will also return an error condition (it couldn't create the folder).

So a more complex check could be:

( test -d photos || mkdir photos ) && mv *.jpg photos/

If photos is already a directory, it won't attempt to create it and will instead jump to moving the images. Whereas if photos was not a directory, but it was able to make it, it will also succeed and move the photos.

If photos was not a folder (or a symbolic link to one) and it wasn't created, the lefthand part of the && would fail and the photos not moved.

Side note: for this particular example, you could have used the more simple mkdir -p photos && mv *.jpg photos/, since -p flag makes mkdir create intermediate folders and not bail out if the folder already exists, but it's less didactic.

Pipelines

When you have a pipeline, by default its return value is the one of the rightmost command. If you execute

command1 | command2 | command3

the return value of the pipeline as a whole will be that of command3. It doesn't matter if command1 or command2 returned non-zero. This can be changed by setting the option pipefail so that the return value will be the one of the rightmost command in the pipeline that returned non-zero.

Division by zero

in your case, echo 1/0 | bc is not stopping the pipeline because bc does return a 0 code, even though it provided a diagnostic on the screen about the attempt to divide by zero.

Directly doing $((1/0)) is problematic since the error happens on the expansion. You can wrap it on a new shell process:

echo hi1 && bash -c 'echo $((1/0))' 2> /dev/null && echo hi2

or even just a subshell

echo hi1 && ( echo $((1/0))' ) 2> /dev/null && echo hi2

There is also the basic solution of actually checking if the divisor is zero . For instance:

division() {
  if [ "$2" -eq 0 ]; then
     return 1
  fi
  echo "$(( $1 / $2 ))"
}

echo hi1 && division 1 0 && echo hi2

You could even do something similar with a bc function (it will output a 0 rather than empty, though):

define division(x,y) {
   if (y == 0) return;
   return x/y;
}
division(1,0)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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