18

Hiding the output of a shell command usually involves redirecting stderr and stdout. Is there any builtin facility or command which by default hides the output but on error dumps all the accumulated output? I would like to run this as a wrapper for remote ssh commands. Now I have them using redirection but I don't get a clue as to what made them fail, and they are just too verbose.

EDIT: In the end I created the following template based on the answer by @Belmin which I tweaked a little bit to accumulate all the previous commands from the script, use the current process identifier, automatically remove the log, and add a failure red error message when something goes wrong. In this template the initial silent wrappers will succeed, then fail the third command because the directory already exists:

#!/bin/sh

set -e

SILENT_LOG=/tmp/silent_log_$$.txt
trap "/bin/rm -f $SILENT_LOG" EXIT

function report_and_exit {
    cat "${SILENT_LOG}";
    echo "\033[91mError running command.\033[39m"
    exit 1;
}

function silent {
    $* 2>>"${SILENT_LOG}" >> "${SILENT_LOG}" || report_and_exit;
}

silent mkdir -v pepe
silent mkdir -v pepe2
silent mkdir -v pepe
silent mkdir -v pepe2
4
  • 2
    If you redirect only stdout, stderr will still show up; is this sufficient for you, or do you want to see stdout as well if there's an error?
    – Kromey
    Jun 25, 2014 at 16:19
  • I want to see both but only if something goes wrong, otherwise I don't want to see anything. Jun 25, 2014 at 16:32
  • 2
    What I do is print stdout & stderr to a logfile so it doesn't clutter the screen. I also print stderr to the screen, so if there is and error I can see it. If I need details I can check the logfile, which contains the program output and the program errors in context. This way, I can 'see both but only if something goes wrong'. Does this help? See stackoverflow.com/questions/2871233/… Jun 25, 2014 at 17:26
  • 1
    Is it safe to redirect stderr and stdout to the same file with two separate redirects? I thought we should always use 2>&1, something like: $* >>"${SILENT_LOG}" 2>&1" || report_and_exit
    – psmith
    Feb 13, 2018 at 11:23

7 Answers 7

5

I'd setup a bash function like this:

function suppress
{
   /bin/rm --force /tmp/suppress.out 2> /dev/null; \
   ${1+"$@"} > /tmp/suppress.out 2>&1 || \
   cat /tmp/suppress.out; \
   bin/rm /tmp/suppress.out;
}

Then, you could just run the command:

suppress foo -a bar
5
  • An attacker who has non-root access on your system, could try to make a symlink between rm and command call, which would point to /etc/passswd or some other critical file and get the contents overwritten.
    – Mitar
    Jun 18, 2017 at 18:55
  • 1
    BTW, order of redirects above should be: $* > /tmp/surpress.out 2>&1 This really captures the stderr.
    – Mitar
    Jun 19, 2017 at 17:32
  • 2
    $* is not the best to handle arbitrary input. Especially when it contains spaces or flags. Most portable is ${1+"$@"} according to stackoverflow.com/questions/743454/…
    – balrok
    Oct 17, 2017 at 13:38
  • Modified per both comments. Thanks---good info. Oct 18, 2017 at 14:37
  • Spelling merriam-webster.com/dictionary/suppress
    – Hatshepsut
    Mar 5, 2018 at 19:45
8

It should be easy enough to write a script for this purpose.

Something like this completely untested script.

OUTPUT=`tempfile`
program_we_want_to_capture &2>1 > $OUTPUT
[ $? -ne 0 ]; then
    cat $OUTPUT
    exit 1
fi
rm $OUTPUT

On the other hand for commands I run as part of a script I usually want something better than simply print all the output. I often limit what I see to the unknown. Here is a script I adapted from something I read over a decade ago.

#!/bin/bash

the_command 2>&1 | awk '
BEGIN \
{
  # Initialize our error-detection flag.
  ErrorDetected = 0
}
# Following are regex that will simply skip all lines
# which are good and we never want to see
/ Added UserList source/ || \
/ Added User/ || \
/ init domainlist / || \
/ init iplist / || \
/ init urllist / || \
/ loading dbfile / || \
/^$/ {next} # Uninteresting message.  Skip it.

# Following are lines that we good and we always want to see
/ INFO: ready for requests / \
{
  print "  " $0 # Expected message we want to see.
  next
}

# any remaining lines are unexpected, and probably error messages.  These will be printed out and highlighted.
{
  print "->" $0 # Unexpected message.  Print it
  ErrorDetected=1
}

END \
{
  if (ErrorDetected == 1) {
    print "Unexpected messages (\"->\") detected in execution."
    exit 2
  }
}
'
exit $?
6

Don't reinvent the wheel on this one. It's a common problem, notably with cron jobs, which is why cronic/chronic were created. You can likely install one from your package manager, and the usage is as simple as this:

cronic noisy_command --param paramval

It will suppress all output unless the noisy command produces non-trace error output or has a non-zero exit status.

3
  • I don't see it on Ubuntu 18.04. Aug 23, 2021 at 1:09
  • @MattAlexander It's there for me. Have you enabled the universe source for apt?
    – Walf
    Aug 23, 2021 at 1:47
  • You are correct, I was mistaken. Aug 23, 2021 at 3:33
5

I don't think there is a clean way of doing this, the only thing I can think of is

  • Capture the output of the command.
  • Check the return value of the command and if it failed
    • display the captured output.

Implementing this might though be a interesting project but perhaps beyond Q&A.

2
  • Should be doable with a function. Hm, let me give it a try. Jun 25, 2014 at 17:43
  • cronic does exactly this and existed at least 7 years before your answer.
    – Walf
    Apr 7, 2022 at 8:09
3

Try so:

out=`command args...` || echo $out
3
  • 3
    I'd write it as out="$(command args...)" || echo "$out"
    – kasperd
    Jun 26, 2014 at 13:38
  • This only deals with STDOUT, not STDERR, where most error messages, and many info/warning messages are printed. A simple demonstration of this is define a shell function that prints to STDERR function foobar { >&2 echo "$@" } then use it in your one-liner out="$(foobar x y z)" || echo "$out". You can see that the exit code ($?) is true, but it still produces output.
    – Walf
    Nov 9, 2021 at 23:34
  • Using it in a script to check if the k8s-cluster is accessible: versioncheck=$(kubectl version 2>&1 ) || { echo "Something is wrong with your kubectl configuration. Are you logged in?"; echo "$versioncheck"; exit 1; }
    – blaimi
    Jan 16, 2023 at 11:58
1

going short with something like tehcommand &>/tmp/$$ || cat /tmp/$$

depends how much usability/typing you want/need. (e.g. using it as a pipe or passing the command by argument)

@zoredache short script is basically a proto-wrapper for this, which would give more robustness, handle concurrency, etc

0

Based on @Belmin Fernandez' answer, I wrote a version that uses an secure, non-world-readable file location (so multiple instances of this script can run at the same time without race condition). It also logs the exit code.

function suppress
{
   TMP=$(mktemp)
   ${1+"$@"} > "$TMP" 2>&1 || ( echo "$* exited with $?" && cat "$TMP" )
   rm "$TMP"
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .