2

I need to form a pipeline of various commands. Some elements of the pipeline, or sequences of elements, are only relevant when some condition holds. Now, I could write:

if [[ $whatever ]]; then
   cmd1 | cmd2 | cmd3 | cmd4
else
   cmd1 | cmd4
fi

but that means repeating cmd1 and cmd4, plus, there may be several conditions and I don't want to write nested if's. So, I tried writing this:

if [[ $whatever ]]; then
    pipeline_segment="| cmd2 | cmd3"
else
    pipeline_segment=""
fi

cmd1 ${pipeline_segment} | cmd4

but - the pipe symbol was not interpreted as an instruction to use a pipe.

How do I have bash execute the pipeline I want it too?

Note: You may assume a bash version of 4 or higher, but only if you must.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    `eval "cmd1 ${pipeline_segment} | cmd4"` maybe? – Nate Eldredge Jan 18 '21 at 22:32
  • in the same vein as `eval`, or a compound command being submitted to `ssh`: `bash -c "cmd1 ${pipeline_segment} | cmd4"` (keeping in mind the potential need to escape embedded double quotes, etc) – markp-fuso Jan 18 '21 at 23:00
  • Very similar: [Conditional pipelining in Bash](https://stackoverflow.com/questions/45690174/conditional-pipelining-in-bash) – oguz ismail Jan 19 '21 at 04:56

2 Answers2

7

Your attempt fails because special symbols lose their syntactical value after expansion. eval is a way, but as we know it is prone to code injection so it is best avoided. We can try

cmd1 |
if [ condition ]; then
   cmd2 | cmd3
else
   cat
fi | 
cmd4

Though I'd love to skip cat, I couldn't find a way. Real example:

echo XYZABC |
if true; then
   tr Y B | tr Z C
else
   cat
fi | 
tr X A

outputs ABCABC and changing the condition to false outputs AYZABC.

Quasímodo
  • 3,812
  • 14
  • 25
  • There's nothing wrong with having `cat` there. One can obviate the overhead by enabling loadable built-in `tee` and using it instead if that's an issue. – oguz ismail Jan 19 '21 at 05:38
  • I had considered using `cat`. But - won't that incur a significant performance penalty for large files? – einpoklum Jan 19 '21 at 07:53
  • @oguzismail: What is a "loadable built-in tee"? – einpoklum Jan 19 '21 at 07:53
  • @einpoklum Bash comes with various loadable builtins, `tee` is one of them. On my system they are stored in `/usr/lib/bash` and I can do `enable -f /usr/lib/bash/tee tee` and have `tee` as a builtin command. – oguz ismail Jan 19 '21 at 08:08
  • @oguzismail: Oh, that's a nice idea then. Can you make it into an answer? – einpoklum Jan 19 '21 at 08:25
  • @einpoklum It may be, but presumably your other commands are considerably more complex than `cat` (at least if they are text-processing commands). – Quasímodo Jan 19 '21 at 12:52
6

I presume you're not wanting to repeat anything because they are big gnarly commands. In that case, use functions to encapsulate them:

cmd1() { some big long command with many options and arguments; }
cmd2() { similar; }
... and so on

The you can literally use this

if [[ $whatever ]]; then
   cmd1 | cmd2 | cmd3 | cmd4
else
   cmd1 | cmd4
fi

or, push the whatever condition into the functions:

cmd2 () { 
    if [[ whatever ]]; then
        the big long command
    else
        cat
    fi
}
cmd3() {
    if [[ whatever ]]; then
        the other big long command
    else
        cat
    fi
}

Then, you're left with simply

cmd1 | cmd2 | cmd3 | cmd4
glenn jackman
  • 238,783
  • 38
  • 220
  • 352