13

I'm printing a message in a Bash script, and I want to colourise a portion of it; for example,

#!/bin/bash

normal='\e[0m'
yellow='\e[33m'
cat <<- EOF
    ${yellow}Warning:${normal} This script repo is currently located in:
    [ more messages... ]
EOF

But when I run in the terminal (tmux inside gnome-terminal) the ANSI escape characters are just printed in \ form; for example,

\e[33mWarning\e[0m This scr....

If I move the portion I want to colourise into a printf command outside the here-doc, it works.  For example, this works:

printf "${yellow}Warning:${normal}"
cat <<- EOF
    This script repo is currently located in:
    [ more messages... ]
EOF

From man bash – Here Documents:

No parameter and variable expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion.  In the latter case, the character sequence \<newline> is ignored, and \ must be used to quote the characters \, $, and `.

I can't work out how this would affect ANSI escape codes. Is it possible to use ANSI escape codes in a Bash here document that is catted out?

4
  • I don't think this has anything to do with here documents. Your variable contains the literal string \e[33m and is substituted normally in both cases. If you have some reason to think it's confined to heredocs, or a bit of real code that shows the behaviour you're interested in, please edit it in. Mar 2, 2016 at 0:29
  • 2
    Either yellow=$'\e[33m' or yellow=$(printf '\e[33m') (as you're already doing) will put the escape character into the string directly to test with. Mar 2, 2016 at 0:31
  • 1
    @MichaelHomer So basically the string '\e[33m' is meaningless to a terminal? it only has meaning to commands like echo -e or printf - which then produce the "real" code that gets sent to the terminal? Mar 2, 2016 at 0:40
  • Y‌‌‌‌e‌‌‌‌s‌‌‌‌. Mar 2, 2016 at 0:43

2 Answers 2

31

In your script, these assignments

normal='\e[0m'
yellow='\e[33m'

put those characters literally into the variables, i.e., \e[0m, rather than the escape sequence. You can construct an escape character using printf (or some versions of echo), e.g.,

normal=$(printf '\033[0m')
yellow=$(printf '\033[33m')

but you would do much better to use tput, as this will work for any correctly set up terminal:

normal=$(tput sgr0)
yellow=$(tput setaf 3)

Looking at your example, it seems that the version of printf you are using treats \e as the escape character (which may work on your system, but is not generally portable to other systems). To see this, try

yellow='\e[33m'
printf 'Yellow:%s\n' $yellow

and you would see the literal characters:

Yellow:\e[33m

rather than the escape sequence. Putting those in the printf format tells printf to interpret them (if it can).

Further reading:

4
  • Im not understanding what you mean by put those characters literally into the variables - isn't that what you want? i.e. the variables just store the values until the variables are interpolated into the string? Mar 2, 2016 at 0:32
  • This format didnt seem to work reliably (the yellow didnt work, but the normal did). but the normal=$(tput sgr0) format is working perfectly! Mar 2, 2016 at 0:36
  • 1
    ah ok, so printf 'Yellow:%s\n' $yellow produced literal characters because printf wasn't giving Yellow "special" treatment. So the reason this works printf "%s" "$(tput setaf 3)foo$(tput sgr0)" is because tput is generating escape sequences the terminal can recognise directly, that's nice to know thanks! Mar 2, 2016 at 0:59
  • 1
    Also red=$'\e[31m' in ksh93, bash, zsh, mksh, FreeBSD sh and soon POSIX. Mar 2, 2016 at 14:35
2

This assignments are not placing escaped characters in the variable:

normal='\e[0m'                  yellow='\e[33m'

To do so, you need echo -e printf or $'...' (in bash).

Since you are using bash, you might as well use this:

normal=$'\e[0m'                 yellow=$'\e[33m'

Please note the $ before the string '\e[0m'.

However, the portable way to get escaped characters is printf, as this:

normal="$(printf '\033[0m')"    yellow="$(printf '\033[33m')"

The octal value (033) of the escape character is valid in all POSIX shells.

3
  • Note that $'\e[0m will be in the next major issue of the POSIX specification and is already supported in ksh93 (where it comes from), bash, zsh, mksh and FreeBSD sh at least. Mar 2, 2016 at 14:37
  • Note that echo -e outputs "-e\n" in POSIX compliant echo implementations. echo '\033' outputs an ESC character with Unix-conformant echos on ASCII-based systems. Mar 2, 2016 at 14:41
  • @StéphaneChazelas I had the impression that a reasonable placed printf is a good solution. And that using printf was a way to avoid presenting, discussing and finally rejecting the use of the flawed in multiple ways echo command. What makes it useful to point to the command echo in this answer?
    – user79743
    Mar 2, 2016 at 21:22

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.