2

I have a Bash script that is being run interactively and as a cron job. When run interactively it outputs colored text on the terminal. However, when run in cron it doesn't have a terminal and therefore I get plenty of [1;31m and similar in the output.

Is there a way in Bash to tell it to swallow the escape codes if the output device doesn't support them?

This is related to How to check if bash can print colors which is about checking the condition. However, I want to change as little as possible in the invocation of echo or printf while retaining the dual functionality of colors or not depending on the output device.

0xC0000022L
  • 16,593

3 Answers3

3

Check whether you're printing to a terminal. That's what programs such as GNU ls and GNU grep do when you tell them --color=auto.

Even if you're printing to a terminal, in theory, it might not understand the color-changing escape sequences. In practice, all the common and most not-so-common terminals understand these sequences: all the X11 terminal emulators I've ever seen, screen, tmux, the Linux console, the *BSD consoles, PuTTY, rxvt, Console2, ConEmu, …

normal=
green=
…
if [ -t 1 ]; then
  normal=$'\e[0m'
  green=$'\e32m'
  …
 fi
 …
 echo "foobar ${green}OK${normal}"

The test is [ -t 1 ] for standard output and [ -t 2 ] for standard error.

In shells that don't support $'…' expansion, you can generate an escape character portably with esc=$(echo _ | tr _ '\033')

2

Not exactly swallowing, but you can remove them with parameter expansion:

str='Hello \e[31mc\e[32mo\e[33ml\e[34mo\e[35mr\e[m world'

# colorful output
echo -e "$str"

# colorless output    
echo -e "${str//\\e\[+([0-9;])m}"

The above in bash requires the extglob shell option to be turned on. (shopt -s extglob)

To make it easier to use, define a function:

function ecco() { [ -t 1 ] && echo -e "$1" || echo -e "${1//\\e\[+([0-9;])m}"; }

Then you just call it:

ecco 'Hello \e[31mc\e[32mo\e[33ml\e[34mo\e[35mr\e[m world'

To check that it works, just redirect its output and the colors will disappear:

ecco 'Hello \e[31mc\e[32mo\e[33ml\e[34mo\e[35mr\e[m world' | cat
manatwork
  • 31,277
1

you should just check, in your script, if it's being run interactively or not?

http://www.gnu.org/software/bash/manual/html_node/Is-this-Shell-Interactive_003f.html

Quotes:

6.3.2 Is this Shell Interactive?

To determine within a startup script whether or not Bash is running interactively, test the value of the ‘-’ special parameter. It contains i when the shell is interactive. For example:

case "$-" in
  *i*)   echo This shell is interactive ;;
   *)    echo This shell is not interactive ;;
esac

Alternatively, startup scripts may examine the variable PS1; it is unset in non-interactive shells, and set in interactive shells. Thus:

if [ -z "$PS1" ]; then
   echo This shell is not interactive
else
   echo This shell is interactive
fi

and define a set of variables to "_escape_codes_" if it's interactive and to "" if not, and use those variables to colorize the output in the script (in the interactive environment they will have escape codes, in the non-interactive they will add nothing to the text).

ex:

case "$-" in
  *i*)    _bold_="$(printf '\033[1m')"
          _norm_="$(printf '\033[0m')"
          ;;
  *)      _bold_=""
          _norm_=""
          ;;
esac
echo "this ${_bold_}text${_norm_} is important"