chronic is a Perl script from moreutils collection, runs a command quietly. It eats up the output of the command, but if the command exits with error, which is exit status does not equal to zero, then chronic would show you the output.

From its manpage, it provides a clear example how it could be useful with cron:


0 1 * * * chronic backup # instead of backup >/dev/null 2>&1

Note

I use vixin cron, I don’t need to redirect stderr to stdout, just chronic backup >/dev/null would do in the old way.

The old way, using redirection is fine, would not give you message sent to stdout since they are redirected to /dev/null, thought you would see get stderr message from log or email if you have sent up.

chronic would get you both.

I made a simple Bash script, chronic.sh:


#!/bin/bash

tmpfile="$(mktemp)"
tmpcode="$(mktemp)"

run_cmd() {
sh -c "$*"
echo "$?" > "$tmpcode"
}

exec 3>&1
( run_cmd "$@" | while read line ; do echo "1 $line" >> "$tmpfile" ; done ) 2>&1 1>&3 | while read line ; do echo "2 $line" >> "$tmpfile" ; done
exec 3>&-

exitcode=$(cat "$tmpcode")
((exitcode > 0)) && awk '
/^1 / {print substr($0, 3)}
/^2 / {print substr($0, 3) > "/dev/stderr"}
'
< "$tmpfile"

rm "$tmpfile"
rm "$tmpcode"
exit $exitcode

It stores messages into temporary file, also exit code to another temporary file. If command fails, it prints out stdout and stderr.


% ./chronic.sh "echo 'error' 1>&2 ; echo 'normal' ; grep sdfk sdk" 2>/dev/null ; echo $?
normal
2
$ ./chronic.sh "echo 'error' 1>&2 ; echo 'normal' ; grep sdfk sdk" 1>/dev/null ; echo $?
error
grep: sdk: No such file or directory
2
$ ./chronic.sh "echo 'error' 1>&2 ; echo 'normal' ; grep sdfk sdk" ; echo $?
error
normal
grep: sdk: No such file or directory
2

The test case is an error message error, then normal stdout normal, then an incorrect grep command which returns error code 2. As you can see, the order and type of messages are still intact, so is exit code.

If you use this Perl, the message order wouldn’t be the same, it groups stdout and stderr to their own group, then prints out stdout, then stderr.


% ./chronic bash -c "echo 'error' 1>&2 ; echo 'normal' ; grep sdfk sdk" ; echo $?
normal
error
grep: sdk: No such file or directory
2

The order has been changed, but they are still in right stdout/stderr.

There are few thing which can improve my script, e.g. flatening down run_cmd to just a subshell, ( sh -c "$*" ; echo "$?" > "$tmpcode" ). It would save a few lines.

I’m currently writing more about tools from moreutils collection, more will be up soon.