sh: Add set -o pipefail

The pipefail option allows checking the exit status of all commands in a
pipeline more easily, at a limited cost of complexity in sh itself. It works
similarly to the option in bash, ksh93 and mksh.

Like ksh93 and unlike bash and mksh, the state of the option is saved when a
pipeline is started. Therefore, even in the case of commands like
  A | B &
a later change of the option does not change the exit status, the same way
  (A | B) &
works.

Since SIGPIPE is not handled specially, more work in the script is required
for a proper exit status for pipelines containing commands such as head that
may terminate successfully without reading all input. This can be something
like

(
        cmd1
        r=$?
        if [ "$r" -gt 128 ] && [ "$(kill -l "$r")" = PIPE ]; then
                exit 0
        else
                exit "$r"
        fi
) | head

PR:		224270
Relnotes:	yes
This commit is contained in:
Jilles Tjoelker 2019-02-24 21:05:13 +00:00
parent dd3a67a078
commit 484160a9cf
11 changed files with 82 additions and 9 deletions

View File

@ -105,6 +105,7 @@ struct job {
char changed; /* true if status has changed */
char foreground; /* true if running in the foreground */
char remembered; /* true if $! referenced */
char pipefail; /* pass any non-zero status */
#if JOBS
char jobctl; /* job running under job control */
struct job *next; /* job used after this one */
@ -144,6 +145,7 @@ static void setcurjob(struct job *);
static void deljob(struct job *);
static struct job *getcurjob(struct job *);
#endif
static int getjobstatus(const struct job *);
static void printjobcmd(struct job *);
static void showjob(struct job *, int);
@ -341,6 +343,20 @@ jobscmd(int argc __unused, char *argv[] __unused)
return (0);
}
static int getjobstatus(const struct job *jp)
{
int i, status;
if (!jp->pipefail)
return (jp->ps[jp->nprocs - 1].status);
for (i = jp->nprocs - 1; i >= 0; i--) {
status = jp->ps[i].status;
if (status != 0)
return (status);
}
return (0);
}
static void
printjobcmd(struct job *jp)
{
@ -377,7 +393,7 @@ showjob(struct job *jp, int mode)
}
#endif
coredump = "";
status = jp->ps[jp->nprocs - 1].status;
status = getjobstatus(jp);
if (jp->state == 0) {
statestr = "Running";
#if JOBS
@ -556,7 +572,7 @@ waitcmdloop(struct job *job)
do {
if (job != NULL) {
if (job->state == JOBDONE) {
status = job->ps[job->nprocs - 1].status;
status = getjobstatus(job);
if (WIFEXITED(status))
retval = WEXITSTATUS(status);
else
@ -781,6 +797,7 @@ makejob(union node *node __unused, int nprocs)
jp->nprocs = 0;
jp->foreground = 0;
jp->remembered = 0;
jp->pipefail = pipefailflag;
#if JOBS
jp->jobctl = jobctl;
jp->next = NULL;
@ -1076,7 +1093,7 @@ waitforjob(struct job *jp, int *signaled)
if (jp->state == JOBSTOPPED)
setcurjob(jp);
#endif
status = jp->ps[jp->nprocs - 1].status;
status = getjobstatus(jp);
if (signaled != NULL)
*signaled = WIFSIGNALED(status);
/* convert to 8 bits */

View File

@ -67,9 +67,10 @@ struct shparam {
#define Pflag optval[17]
#define hflag optval[18]
#define nologflag optval[19]
#define pipefailflag optval[20]
#define NSHORTOPTS 19
#define NOPTS 20
#define NOPTS 21
extern char optval[NOPTS];
extern const char optletter[NSHORTOPTS];
@ -97,6 +98,7 @@ static const unsigned char optname[] =
"\010physical"
"\010trackall"
"\005nolog"
"\010pipefail"
;
#endif

View File

@ -32,7 +32,7 @@
.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95
.\" $FreeBSD$
.\"
.Dd January 24, 2019
.Dd February 24, 2019
.Dt SH 1
.Os
.Sh NAME
@ -343,6 +343,18 @@ Useful for debugging.
.It Li nolog
Another do-nothing option for POSIX compliance.
It only has a long name.
.It Li pipefail
Change the exit status of a pipeline to the last non-zero exit status of
any command in the pipeline, if any.
Since an exit due to
.Dv SIGPIPE
counts as a non-zero exit status,
this option may cause non-zero exit status for successful pipelines
if a command such as
.Xr head 1
in the pipeline terminates with status 0 without reading its
input completely.
This option only has a long name.
.El
.Pp
The
@ -856,12 +868,15 @@ If the keyword
.Ic !\&
does not precede the pipeline, the
exit status is the exit status of the last command specified
in the pipeline.
in the pipeline if the
.Cm pipefail
option is not set or all commands returned zero,
or the last non-zero exit status of any command in the pipeline otherwise.
Otherwise, the exit status is the logical
NOT of the exit status of the last command.
NOT of that exit status.
That is, if
the last command returns zero, the exit status is 1; if
the last command returns greater than zero, the exit status
that status is zero, the exit status is 1; if
that status is greater than zero, the exit status
is zero.
.Pp
Because pipeline assignment of standard input or standard

View File

@ -31,6 +31,13 @@ ${PACKAGE}FILES+= killed2.0
${PACKAGE}FILES+= not1.0
${PACKAGE}FILES+= not2.0
${PACKAGE}FILES+= path1.0
${PACKAGE}FILES+= pipefail1.0
${PACKAGE}FILES+= pipefail2.42
${PACKAGE}FILES+= pipefail3.42
${PACKAGE}FILES+= pipefail4.42
${PACKAGE}FILES+= pipefail5.42
${PACKAGE}FILES+= pipefail6.42
${PACKAGE}FILES+= pipefail7.0
${PACKAGE}FILES+= redir1.0
${PACKAGE}FILES+= redir2.0
${PACKAGE}FILES+= redir3.0

View File

@ -0,0 +1,4 @@
# $FreeBSD$
set -o pipefail
: && : | : && : | : | : && : | : | : | :

View File

@ -0,0 +1,4 @@
# $FreeBSD$
set -o pipefail
(exit 42) | :

View File

@ -0,0 +1,4 @@
# $FreeBSD$
set -o pipefail
: | (exit 42)

View File

@ -0,0 +1,4 @@
# $FreeBSD$
set -o pipefail
(exit 43) | (exit 42)

View File

@ -0,0 +1,5 @@
# $FreeBSD$
set -o pipefail
(exit 42) | : &
wait %+

View File

@ -0,0 +1,6 @@
# $FreeBSD$
set -o pipefail
(exit 42) | : &
set +o pipefail
wait %+

View File

@ -0,0 +1,5 @@
# $FreeBSD$
(exit 42) | : &
set -o pipefail
wait %+