3

In shell scripts I often use multiple pipes like this:

cmd1 | cmd2 | cmd3

Now if cmd2 is missing the pipe will result "broken" and cmd3 won't receive any input.

As workaround I could test explicitly if cmd2 exist, but this requires some code replication I'd like to avoid.

Instead I'd like a more concise test like this:

cmd1 | cmd2; [ $? -eq 127 ] && cat | cmd3
  • the above test does not work, any alternative? – eadmaster Apr 3 '16 at 5:35
  • You can use { and } for grouping. E.g. cmd1 | { which cmd2 >/dev/null && cmd2 || cat; } | cmd3 should have the desired effect. Are you looking for a way to forward the output of cmd1 that has already been consumed by cmd2 to cmd3 if cmd2 fails at any point? If so, you're probably better off writing to a temporary file. – Gregory Nisbet Apr 3 '16 at 5:59
  • Why not just test for the existence of cmd2 before you even run the pipeline? – Wildcard Apr 3 '16 at 6:01
  • @GregoryNisbet, would you like to add that as an answer? It's what I was going to suggest next, so I will post it as answer if you don't. ;) – Wildcard Apr 3 '16 at 6:27
  • 1
    Related: How to test if a command is installed? – Wildcard Apr 3 '16 at 6:42
1

Based on your example it looks like you're looking for a way to group arguments to operations with lower precedence than |, (i.e. && and ||).

The { and } metacharacters can be used for grouping in this way.

Your example above could be written like this (using the exit status of which to determine whether cmd2 exists or not).

cmd1 | { which cmd2 >/dev/null && cmd2 || cat; } | cmd3

For example

printf "hi\n" | { : && rev || cat; } | cat

Will print ih

Your suggestion of using a subshell will also work, but uses another process unnecessarily

printf "hi\n" | ( : && rev || cat ) | cat

I would say it is probably less tricky and better for maintainability if you use an explicit if outside the pipe.

if [ which cmd2 >/dev/null ]
then
    cmd1 | cmd3
else
    cmd1 | cmd2 | cmd3
fi
  • as i've said i'd like to avoid an explicit test because cmd1,2,3,etc. may not be simple commands and i may need to test more than one. Also you need to provide the different pipelines for each case (code duplication). – eadmaster Apr 6 '16 at 6:26
  • @eadmaster In that case using something like \@meuh's answer and having an explicit function for each potential step in your process might make sense if you want to avoid temporary files. What is the processing you're actually trying to do? It might be cleaner to process a line at a time in a python/perl/awk script. – Gregory Nisbet Apr 6 '16 at 17:52
  • no, i think it is fine with command grouping so i can easily provide alternatives like this: cmd1 | { alt1 || alt2 || cat ; } | cmd3. The only thing i don't get in your answer is the : && at the beginning of each group. – eadmaster Apr 8 '16 at 14:08
  • : is a shell builtin that just exits 0. Your suggestion cmd1 | { alt1 || alt2 ; } | cmd3 will also work. – Gregory Nisbet Apr 8 '16 at 15:15
4

You can write a mini function to make it more concise:

ifexists(){
    if command -v "$1"
    then "$@"
    else echo "doing cat for missing $1" >&2
         cat
    fi
}

echo hi | ifexists mycommand | cat -n

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.