9

Note: Question does not duplicate Ignoring specific errors in a shell script .


Suppose it is needed to capture the leading characters of a encoded representation of a file.

In shell (tested in Bash), it is easy to use the following form:

encoded="$(< file base64 | head -c16)"

The statement functions desired, except under certain alterations to the environment.

Consider the following:

set -o errexit -o pipefail
shopt -s inherit_errexit
encoded="$(< file base64 | head -c16)"

The final line would cause termination of a script, because of the non-zero return status (141) given by base64, unhappy with closed pipe. The return status is propagated to the pipe and then to the invoking shell.

The undesired effect requires a workaround, such as follows:

set -o errexit -o pipefail
shopt -s inherit_errexit
encoded="$((< file base64 || :) | head -c16)"

The : has the same effect as would have the keyword true, to evaluate as a non-error.

However, this approach leads to a further unwanted effect.

The following shows a variation with a different error:

set -o errexit -o pipefail
shopt -s inherit_errexit
encoded="$((< /not/a/real/file base64 || :) | head -c16)"
echo $?

The printed code is zero. Now, a true error has been masked.

The most obvious solution, as follows, is rather verbose

set -o errexit -o pipefail
shopt -s inherit_errexit
encoded="$((< /not/a/real/file base64 || [ $? == 141 ]) | head -c16)"
echo $?

Is a more compact form available? Is any environment alteration available such that statements masks only the particular status code, without the explicit inline expression?

brainchild
  • 754
  • 1
  • 7
  • 21
  • 1
    I doubt there's anything better. Distinguishing different exit codes is relatively rare, so there's no shortcut for it. – Barmar Feb 01 '22 at 02:30
  • 1
    Your solution seems good; if you're using an unruly command a lot then you could define a function `mybase64() { base64 "$@" || [[ $? == 141 ]]; }` – Fravadona Feb 03 '22 at 02:04
  • I do not think you need round parenthesis, which will cause a process fork. Curly parenthesis should be enough. – ceving Feb 03 '22 at 08:36
  • @ceving: Might you suggest a working example for this idea? I have not discovered one. – brainchild Feb 11 '22 at 23:17
  • Do you require a redirection as your input ? For what @ceing means, i guess it's : `encoded="$( { base64 $1 ; } | head -c16)"` with `$1` as the file. – Zilog80 Feb 14 '22 at 13:41
  • @Zilog80: Giving the file as an argument may be cleaner than as redirection, but the central issue, of capturing the exit code from the command, seems not affected by this choice. – brainchild Feb 14 '22 at 22:25
  • Maybe you could write a version of cat or base64 that ignore SIGPIPE. – masterxilo May 07 '22 at 19:36

2 Answers2

1

First off, apparently, to actually provoke the 141 error the file needs to be fairly, large, e.g.

head -c 1000000 /dev/urandom > file

now, as you said, this script sh.sh will terminate before showing encoded: ...:

#!/bin/bash
set -o errexit -o pipefail
shopt -s inherit_errexit

encoded="$(< file base64 | head -c16)"

echo "encoded: $encoded"

instead of checking for the error code, you could let base64 continue to pipe the rest of its data to /dev/null by invoking cat > /dev/null after head is done:

#!/bin/bash
set -o errexit -o pipefail
shopt -s inherit_errexit

encoded="$(< file base64 | ( head -c16 ; cat > /dev/null ) )"

echo "encoded: $encoded"

now you will get encoded: NvyX2Zx4nTDjtQO8 or whatever.

And this does not mask other errors like the file not existing:

$ ./sh.sh
./sh.sh: line 5: file: No such file or directory

However, it will be less efficient because the whole file will be read.

Eugene Berdnikov
  • 2,150
  • 2
  • 23
  • 30
masterxilo
  • 2,503
  • 1
  • 30
  • 35
0

For your specific example program, you could also just change your approach. 16 base 64 characters represent 16 * 6 = 96 bits, so 96/8 = 12 bytes of data from the file are needed:

encoded="$(head -c12 file | base64)"

this will not cause SIGPIPE.

masterxilo
  • 2,503
  • 1
  • 30
  • 35
  • 1
    Yes, it may be true, but the question is about the general workings of the shell, not any specific commands. – brainchild May 08 '22 at 14:28