I would like to simplify the output of a script by suppressing the output of secondary commands that are usually successful.

However, using -q on them hides the output when they occasionally fail, so I have no way of understanding the error. Additionally, these commands log their output on stderr.

Is there a way to suppress a command's output only if it succeeds?

For example (but not limited to) something like this:

mycommand | fingerscrossed

If all goes well, fingerscrossed catches the output and discards it. Else it echoes it to the standard or error output (whatever).


moreutils' chronic command does just that:

chronic mycommand

will swallow mycommand's output, unless it fails, in which case the output is displayed.

  • 1
    Thank you. I take it it's not installed by default on most Unix OSes? – Matthieu Napoli Jan 18 '16 at 17:08
  • 1
    Probably not, although it's widely packaged so it should be easy to install. – Stephen Kitt Jan 18 '16 at 17:10
  • 1
    Debian has it in package moreutils. Nice for me, anyway :) – Tom Zych Jan 18 '16 at 20:28
  • 7
    Note that it stores the whole output in memory. – Stéphane Chazelas Jan 19 '16 at 10:49
  • 1
    @StéphaneChazelas that's likely the only way to implement something like this, the output needs to be stored while the command is running in case it is needed. – Centimane Jan 19 '16 at 13:52
### do this bit once at the top of your script
exec 3<>"${divert:=$(mktmp)}" 4<>/dev/null
rm -- "$divert"; unset divert
### then do this bit as often as needed
command >&3 2>&3
cat <&3 >&"$(((RTN=$?)?2:4))"

That should probably do the trick. It will buffer the output of each command into a deleted temporary file, and afterward siphon its output into either /dev/null or stderr depending on whether or not its return status was not zero. Because the temp file is deleted ahead of time it cannot be read by any process but the current shell and its children on its file descriptor (barring sneaky /proc/$pid/fd snoops with appropriate permissions), and it does not require cleaning up when you're through.

Perhaps a more convenient solution on linux systems:

    "$@" >&3 2>&3 ||
    eval "cat <&3
          return $?"
}   3<<"" 3<>/dev/fd/3

... which, in most shells, works much like the other, except that you can call it like: divert some simple-command with args. Beware of high output commands in "$@", though for dash, yash, or some other shells which do here-documents with pipes - I think it may be possible in those shells to fill the pipe buffer (at a default of around 128kb on linuxes) and so deadlock. That shouldn't be a worry for ksh, mksh, bash, zsh, or the Bourne shell, though - all of those do basically the same thing as I did explicitly above with exec.


Usually in case of error the command outputs messages to stderr so for you task you can just supress stdout

mycommand > /dev/null
  • 9
    Be careful – PyRulez Jan 18 '16 at 22:38
  • Thank you, but as I said in the question my commands log all the output on stderr (so it has no effect). – Matthieu Napoli Jan 19 '16 at 9:07

I just found this much simpler answer on this other question:

output=`mycommand 2>&1` || echo $output

Works like a charm!

  • Note: for my use case this was a much simpler solution (avoid installing extra stuff on all CI servers) so I chose to mark this one as accepted. YMMV. – Matthieu Napoli Jan 22 '16 at 13:35
  • Note that if you use set -o xtrace in your shell script then all the output will be there again as part of logging the details of the assignment output=... :-). In that case it's probably better to use chronic. – Jan-Philip Gehrcke Dec 5 '19 at 19:40
  • One IMPORTANT difference from chronic -- if your script exits on failure (ala set -e) - chronic returns false, and execution will stop. If you want your simpler version to also stop, it needs: (echo $output && false) – Jeff Ward Sep 4 '20 at 15:41
  • 2>&1 is the key! – Nishant Dec 10 '20 at 11:05

To make your own chronic

my_chronic() {
  tmp=$(mktemp) || return # this will be the temp file w/ the output
  "$@"  > "$tmp" 2>&1 # this should run the command, respecting all arguments
  [ "$ret" -eq 0 ] || cat "$tmp"  # if $? (the return of the last run command) is not zero, cat the temp file
  rm -f "$tmp"
  return "$ret" # return the exit status of the command

I do something like this in my makefiles:

if (mycommand) &> mycommand.log; then 
  echo success 
  echo;echo -e "Bad result from previous command, see mycommand.log for more details";echo;
  (exit $c)

Adapting that to your situation, you could do something like this:

if ! (mycommand) &> mycommand.log; then 
  cat mycommand.log
  rm mycommand.log
  (exit $c)

So, "if" runs the command and pipes the output to mycommand.log. If you need to catch stdout vs stdout vs whatever, you may need to change the pipe command '&>' to '>'. If the command fails then capture the error code, print out the contents of mycommand.log, remove mycommand.log, and finally return with the original error code.

Without the (exit $c) you would return with the exit code that matches the what the 'rm' command returned.

Finally, if you want a one liner, something like this would work.

mycommand &> mycommand.log || cat mycommand.log; rm mycommand.log
  • 2
    Do you really wrap your commands/etc. in (...) like that? Because it doesn't do anything useful for you but spawn extra sub-shells. – Etan Reisner Jan 18 '16 at 23:00
  • @EtanReisner (exit $c) is setting $?, which is something you can't do otherwise. if ! (mycommand) &>x is meaningful with the redirection if the command uses e.g. time or would give a shell error. – Michael Homer Jan 19 '16 at 20:30
  • @MichaelHomer - for those things there are { ; } the curlies... though exit is a little tricky there, admittedly. – mikeserv Jan 19 '16 at 21:14
  • In a makefile snippet if you are trying to exit with the previously saved $? then you can just use exit $c but yes, in other cases (exit $?) has value (though a shell function rret() { return $1; } would be better in general I'd argue). The subshell for commands still doesn't really though as mikeserv indicated. – Etan Reisner Jan 20 '16 at 2:03

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.