14

Is there a way to re-write the command structure A && B || C | D so that either B or C is piped into D?

With the current command either only B or both C and D are run.

For example:

enter image description here

31

Yes, in bash you can use parentheses:

(A && B || C) | D

This way the output of A && B || C will be piped into D.

  • 6
    Or A && (B || C) | D, if you don't want B, C, or D to run when A fails – zwol Apr 17 '17 at 15:37
  • 2
    You can also use parens in sh :) – EKons Apr 18 '17 at 10:28
  • It's unclear whether using parentheses instead of curly braces was an intentional decision (if so, what are the benefits?) or just an oversight. Could you explain? – Tom Fenech Apr 18 '17 at 13:31
  • @TomFenech Parens cause the expression to run in a sub-shell, which is a (single) separate process from the POV of the rest of the script. Therefore, whatever the output of the expression inside parens, it will be piped together. (Since A is inside the subshell, this also includes A.) – jpaugh Apr 18 '17 at 15:48
  • 1
    @jpaugh I think your point about isolation is a good one but the output would be piped in the same way when using curly braces, wouldn't it? – Tom Fenech Apr 18 '17 at 15:51
14

You can write this as

if A; then B; else C; fi | D

You say you want to run either B or C, but A && B || C doesn't achieve that. If A succeeds, but B runs and fails, it would execute C.

Note 1: if you can somehow guarantee that B always succeeds and want to stick with a short version, then I'd still opt for

{ A && B || C; } | D

over ( ... ), as the latter unnecessarily forces a new subshell to be created, which may or may not get optimised away.

Note 2: both forms assume A produces no output, which is true in your example but not necessarily so in general. That can be avoided by

A; if [ "$?" -eq 0 ]; then B; else C; fi | D
  • Are you sure that { … } does not force a subshell to be created due to the pipe? I observe the following behavior: pgrep bash and pgrep bash | cat and if true; then pgrep bash; fi and { pgrep bash; } have one line of output; ( pgrep bash; ) and ( pgrep bash; ) | cat and { pgrep bash; } | cat and if true; then pgrep bash; fi | cat have two lines of output. – wchargin Apr 18 '17 at 2:10
  • @wchargin ... | ... causes a subshell to be created, that's unavoidable. ( ... ), at least in theory, causes an additional subshell to be created that { ...; } avoids, but that's what I meant with "may or may not get optimised away": it's possible that in this particular case, the shell realises it doesn't matter, the effect would be the same. – hvd Apr 18 '17 at 4:51
5

The accepter answer is correct but it doesn't cover the potential use case to not have the output of A as the input of D. To achieve that you’ll need a stream redirection on A depending on your needs.

  • If you want to discard the output of A anyway:

    { A >/dev/null && B || C; } | D
    
  • If you want to see the output of A on the terminal:

    { A >/dev/tty && B || C; } | D
    
  • If you need the output of A as the input of a subsequent command E you’ll need an additional command group and stream redirection:

    { { A >&3 && B || C; } | D; } 3>&1 | E
    

If all this looks too arcane to you (as it does to me) I recommend that you use the special shell variable for the exit status of A and work with that:

A
if [ $? -eq 0 ]; then
  B
else
  C
fi |
D

If you want to be more concise but not too arcane I suggest this:

A; { [ $? -eq 0 ] && B || C; } | D

(See also the last part of hvd’s answer which I didn't notice when I wrote my original answer.)

  • My answer does cover that. See what I put in my "Note 2:", where I simply moved A out of the pipeline. – hvd Apr 17 '17 at 21:17
  • @hvd: You're correct and thanks for pointing out that part of your answer to me! I altered my claim and gave you credit accordingly. – David Foerster Apr 17 '17 at 21:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.