equivalent of pipefail in dash shell
Asked Answered
P

4

43

Is there some similar option in dash shell corresponding to pipefail in bash?

Or any other way of getting a non-zero status if one of the commands in pipe fail (but not exiting on it which set -e would).

To make it clearer, here is an example of what I want to achieve:

In a sample debugging makefile, my rule looks like this:

set -o pipefail; gcc -Wall $$f.c -o $$f 2>&1 | tee err; if [ $$? -ne 0 ]; then vim -o $$f.c err; ./$$f; fi;

Basically it runs opens the error file and source file on error and runs the programs when there is no error. Saves me some typing. Above snippet works well on bash but my newer Ubunty system uses dash which doesn't seem to support pipefail option.

I basically want a FAILURE status if the first part of the below group of commands fail:

gcc -Wall $$f.c -o $$f 2>&1 | tee err

so that I can use that for the if statement.

Are there any alternate ways of achieving it?

Thanks!

Preemie answered 19/7, 2013 at 23:34 Comment(4)
Why not just get rid of the tee? if gcc -Wall $$f.c -o $$f >$$f.log 2>&1; then cat $$f.log; ./$$f; else vim -o $$f.c $$f.log; fi (Alternatively, install bash on Ubunty. It's only an apt-get away.)Amarillo
@Amarillo Thanks! I was using tee because I also wanted the stderr output printed to screen - mainly to see if there were any warnings. But if there is no other way I'll probably switch to what you suggested. About installing bash: I had assumed (without any research) that dash is a step forward from bash so I didn't want to switch back but now googling a little bit about it seems that might not be necessarily so. I'll read a little more about the differences and then decide. Thank you once again for both your pointers!Preemie
The "stock" answer for POSIX implementations of PIPESTATUS / pipefail is in the comp.unix.shell FAQ Q11Salesin
I'd hoped that the set -e mentioned above would provide me with a dash equivalent of set -o pipefail but not so: $ dash -c 'set -e; false | cat'; echo $? 0 $Parthenope
L
9

I ran into this same issue and the bash options of set -o pipefail and ${PIPESTATUS[0]} both failed in the dash shell (/bin/sh) on the docker image I'm using. I'd rather not modify the image or install another package, but the good news is that using a named pipe worked perfectly for me =)

mkfifo named_pipe
tee err < named_pipe &
gcc -Wall $$f.c -o $$f > named_pipe 2>&1
echo $?

See this answer for where I found the info: https://mcmap.net/q/73702/-pipe-output-and-capture-exit-status-in-bash

Lilalilac answered 27/4, 2020 at 23:55 Comment(0)
C
6

The Q.'s sample problem requires:

I basically want a FAILURE status if the first part of the ... group of commands fail:

Install moreutils, and try the mispipe util, which returns the exit status of the first command in a pipe:

sudo apt install moreutils

Then:

if mispipe "gcc -Wall $$f.c -o $$f 2>&1" "tee err" ; then \
     ./$$f 
else 
     vim -o $$f.c err 
fi

While 'mispipe' does the job here, it is not an exact duplicate of the bash shell's pipefail; from man mispipe:

   Note that some shells, notably bash, do offer a
   pipefail option, however, that option does not
   behave the same since it makes a failure of any
   command in the pipeline be returned, not just the
   exit status of the first.
Chemaram answered 11/2, 2019 at 2:24 Comment(0)
F
2

Probably an ugly trick ...

This is meant (and tested) for "/bin/sh" type of scripts (i.e. currently executed by dash on Debian).

Since elements of a pipeline cannot modify the parent environment, the idea here is to use signalling. Each child process can communicate with its parent process using the USR1 signal (an arbitrary choice). When the parent process (the main script) receives that signal, its signal handler sets a flag variable to 1.

This allows to catch failing commands in any pipeline component (not just the first, as with mispipe).

## Set flag variable to non-zero
usr1_handler() { LAST_PIPELINE=1; }

## Execute above function when a USR1 signal is received
trap usr1_handler USR1

## Report a failed component in the pipeline
sig_fail() { kill -s USR1 "$$"; }

Example 1: successful pipeline

LAST_PIPELINE=0
{ seq 10 -1 1 || sig_fail; } | { grep -x -e '[2468]' || sig_fail; } | sort
printf 'standard exit status: %u\t\tUSR1 status: %u\n' "$?" "$LAST_PIPELINE"

Output:

2
4
6
8
standard exit status: 0     USR1 status: 0

Example 2: failing pipeline (2nd component)

LAST_PIPELINE=0
{ seq 10 -1 1 || sig_fail; } | { grep -x -e 'foo' || sig_fail; } | sort
printf 'standard exit status: %u\t\tUSR1 status: %u\n' "$?" "$LAST_PIPELINE"

Output:

standard exit status: 0     USR1 status: 1

Example 3: failing pipeline (1st component)

LAST_PIPELINE=0
{ seq 10 -1 foo || sig_fail; } | { grep -x -e '6' || sig_fail; } | sort
printf 'standard exit status: %u\t\tUSR1 status: %u\n' "$?" "$LAST_PIPELINE"

Output:

seq: invalid floating point argument: ‘foo’
Try 'seq --help' for more information.
standard exit status: 0     USR1 status: 1

If you think it is too annoying to remember typing "LAST_PIPELINE=0" before every pipeline occurrences, or if you just want 'syntactic sugar' for shorter code, you could define an alias like :

alias catchfail='LAST_PIPELINE=0; '

and execute your pipeline tests like this :

catchfail { command1 || sig_fail; } | { command2 || sig_fail; } ...
Fiat answered 24/6, 2023 at 23:39 Comment(0)
S
1

dash has support for pipefail since this commit:

https://git.kernel.org/pub/scm/utils/dash/dash.git/commit/?id=6347b9fc52d742f36a0276cdea06cd9ad1f02c77

In Debian, that commit has been cherry-picked for version 0.5.12-7:

https://tracker.debian.org/news/1530876/accepted-dash-0512-7-source-into-unstable/

Before:

$ sh -c "exit 1" | cat
$ echo $?
0

After:

$ set -o pipefail
$ sh -c "exit 1" | cat
$ echo $?
1
Skit answered 18/5 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.