Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash process substitution and exit codes

Tags:

bash

I'd like to turn the following:

git status --short && (git status --short | xargs -Istr test -z str)

which gets me the desired result of mirroring the output to stdout and doing a zero length check on the result into something closer to:

git status --short | tee >(xargs -Istr test -z str)

which unfortunately returns the exit code of tee (always zero).

Is there any way to get at the exit code of the substituted process elegantly?

[EDIT]

I'm going with the following for now, it prevents running the same command twice but seems to beg for something better:

OUT=$(git status --short) && echo "${OUT}" && test -z "${OUT}"

like image 847
jodell Avatar asked Sep 28 '11 16:09

jodell


People also ask

What does $() mean in bash?

Example of command substitution using $() in Linux: Again, $() is a command substitution which means that it “reassigns the output of a command or even multiple commands; it literally plugs the command output into another context” (Source).

What is bash exit code?

What is an exit code in bash shell? Every Linux or Unix command executed by the shell script or user has an exit status. Exit status is an integer number. 0 exit status means the command was successful without any errors.

What is Pipefail in bash?

pipefail. If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.


2 Answers

Look here:

  $ echo xxx | tee >(xargs test -n); echo $?
xxx
0
  $ echo xxx | tee >(xargs test -z); echo $?
xxx
0

and look here:

  $echo xxx | tee >(xargs test -z; echo  "${PIPESTATUS[*]}")
xxx
123
  $echo xxx | tee >(xargs test -n; echo  "${PIPESTATUS[*]}")
xxx
0

That is?

See also Pipe status after command substitution

like image 148
gavenkoa Avatar answered Oct 08 '22 11:10

gavenkoa


I've been working on this for a while, and it seems that there is no way to do that with process substitution, except for resorting to inline signalling, and that can really be used only for input pipes, so I'm not going to expand on it.

However, bash-4.0 provides coprocesses which can be used to replace process substitution in this context and provide clean reaping.

The following snippet provided by you:

git status --short | tee >(xargs -Istr test -z str)

can be replaced by something alike:

coproc GIT_XARGS { xargs -Istr test -z str; }
{ git status --short | tee; } >&${GIT_XARGS[1]}
exec {GIT_XARGS[1]}>&-
wait ${GIT_XARGS_PID}

Now, for some explanation:

The coproc call creates a new coprocess, naming it GIT_XARGS (you can use any name you like), and running the command in braces. A pair of pipes is created for the coprocess, redirecting its stdin and stdout.

The coproc call sets two variables:

  1. ${GIT_XARGS[@]} containing pipes to process' stdin and stdout, appropriately ([0] to read from stdout, [1] to write to stdin),
  2. ${GIT_XARGS_PID} containing the coprocess' PID.

Afterwards, your command is run and its output is directed to the second pipe (i.e. coprocess' stdin). The cryptically looking >&${GIT_XARGS[1]} part is expanded to something like >&60 which is regular output-to-fd redirection.

Please note that I needed to put your command in braces. This is because a pipeline causes subprocesses to be spawned, and they don't inherit file descriptors from the parent process. In other words, the following:

git status --short | tee >&${GIT_XARGS[1]}

would fail with invalid file descriptor error, since the relevant fd exists in parent process and not the spawned tee process. Putting it in brace causes bash to apply the redirection to the whole pipeline.

The exec call is used to close the pipe to your coprocess. When you used process substitution, the process was spawned as part of output redirection and the pipe to it was closed immediately after the redirection no longer had effect. Since coprocess' pipe's lifetime extends beyond a single redirection, we need to close it explicitly.

Closing the output pipe should cause the process to get EOF condition on stdin and terminate gracefully. We use wait to wait for its termination and reap it. wait returns the coprocess' exit status.

As a last note, please note that in this case, you can't use kill to terminate the coprocess since that would alter its exit status.

like image 32
Michał Górny Avatar answered Oct 08 '22 11:10

Michał Górny