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?

7
  • 1
    I doubt there's anything better. Distinguishing different exit codes is relatively rare, so there's no shortcut for it. Commented Feb 1, 2022 at 2:30
  • 1
    Your solution seems good; if you're using an unruly command a lot then you could define a function mybase64() { base64 "$@" || [[ $? == 141 ]]; } Commented Feb 3, 2022 at 2:04
  • I do not think you need round parenthesis, which will cause a process fork. Curly parenthesis should be enough. Commented Feb 3, 2022 at 8:36
  • @ceving: Might you suggest a working example for this idea? I have not discovered one. Commented Feb 11, 2022 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. Commented Feb 14, 2022 at 13:41

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

1 Comment

Yes, it may be true, but the question is about the general workings of the shell, not any specific commands.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.