Shell – Elegant way to construct a pipeline based on return value and not exit code

aptdebianexit-statusshellshell-script

When the status code is useless, is there anyway to construct a pipeline based on output from stdout?

I'd prefer the answer not address the use-case but the question in the scope of shell scripting. What I'm trying to do is find the most-specific package available in the repository by guessing the name based on country and language codes.

Take for instance this,

  • $PACKAGE1=hunspell-en-zz
  • $PACKAGE2=hunspell-en

The first guess is more appropriate but it may not exist. In this case, I want to return hunspell-en ($PACKAGE2) because the first option hunspell-en-zz ($PACKAGE1) does not exist.

pipelines of apt-cache

The command apt-cache returns success (which is defined by shell as exit-code zero) whenever the command is able to run (from the docs of apt-cache)

apt-cache returns zero on normal operation, decimal 100 on error.

That makes using the command in a pipeline more difficult. Normally, I expect the package-search equivalent of a 404 to result in an error (as would happen with curl or wget). I want to search to see if a package exists, and if not fall back to another package if it exists.

This returns nothing, as the first command returns success (so the rhs in the || never runs)

apt-cache search hunspell-en-zz || apt-cache search hunspell-en

apt-cache search with two arguments

This returns nothing, as apt-cache ANDs its arguments,

apt-cache search hunspell-en-zz hunspell-en

From the docs of apt-cache

Separate arguments can be used to specify multiple search patterns that are and'ed together.

So as one of those arguments clearly doesn't exist, this returns nothing.

The question

What is the shell idiom to handle conventions like those found in apt-cache where the return code is useless for the task? And success is determined only by the presence of output on STDOUT?

Similar to

  • make find fail when nothing was found

    they both stemming from the same problem. The chosen answer there mentions find -z which sadly isn't applicable solution here and is use-case specific. There is no mention of an idiom or constructing a pipeline without using null-termination (not an option on apt-cache)

Best Answer

Create a function that takes a command and returns true iff it has some output.

r() { local x=$("$@"); [ -n "$x" ] && echo "$x"; }

( ( r echo -n ) || echo 'nada' ) | cat      # Prints 'nada'
( ( r echo -n foo ) || echo 'nada' ) | cat  # Prints 'foo'

So for this use case it 'll work like this,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en
Related Question