Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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).

share|improve this question
    
Sounds like a duplicate of Suppress output unless non-zero exit code. – don_crissti Jan 19 at 22:14

moreutils' chronic command does just that:

chronic mycommand

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

share|improve this answer
1  
Thank you. I take it it's not installed by default on most Unix OSes? – Matthieu Napoli Jan 18 at 17:08
1  
Probably not, although it's widely packaged so it should be easy to install. – Stephen Kitt Jan 18 at 17:10
1  
Debian has it in package moreutils. Nice for me, anyway :) – Tom Zych Jan 18 at 20:28
6  
Note that it stores the whole output in memory. – Stéphane Chazelas Jan 19 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. – Dave Jan 19 at 13:52
### do this bit once at the top of your script
divert=
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:

divert(){
    "$@" >&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.

share|improve this answer

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

mycommand > /dev/null
share|improve this answer
7  
Be careful – PyRulez Jan 18 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 at 9:07

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=$?
  [ "$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
}
share|improve this answer

I do something like this in my makefiles:

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

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

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

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
share|improve this answer
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 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 at 20:30
    
@MichaelHomer - for those things there are { ; } the curlies... though exit is a little tricky there, admittedly. – mikeserv Jan 19 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 at 2:03
up vote 2 down vote accepted

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

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

Works like a charm!

share|improve this answer
    
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 at 13:35

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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