Print check/cross mark in shell script

As revealed by the OP in the comments, they were calling script with sh file.sh. Depending on the default shell to which /bin/sh is symlinked it might not support unicode characters.

For instance, on Ubuntu , the default shell is dash.

$ dash
$ printf "\xE2\x9C\x94 missing\n"
\xE2\x9C\x94 missing
$ echo -e "\xE2\x9C\x94"
-e \xE2\x9C\x94

The reason why it worked when you called the command in interactive shell, is because users interactive shell is by default (on Ubuntu) /bin/bash

To properly run the script, you need to either:

  • run it as ./file.sh
  • run it as argument to proper shell bash file.sh

Alternatively, one could resort to shell-independent methods:

# this printf is standalone program, not shell built-in
$ /usr/bin/printf "\xE2\x9C\x94 check mark\n"
✔ check mark

$ python -c 'print "\xE2\x9C\x94 check mark"'
✔ check mark

$ perl -e 'print "\xE2\x9C\x94 check mark"'                                                                              
✔ check mark

Note that \xE2\x9C\x94 is the UTF-8 encoding of the U+2714 (heavy check mark) character.

Those 3 bytes will only be displayed as a check mark if the terminal's character set is UTF-8 (and uses a font that has that character).

For terminal emulators, the charset they use will typically be the one in the locale at the time they were started. Unless you have changed the locale within the shell started in that terminal, you can tell which one it was with:

locale charmap

Several printf implementations including GNU printf and the printf builtin of zsh, bash and lksh (a more POSIX compatible variant of mksh on Debian-based systems at least) support:

$ printf '\u2714\u274c\n'
✔❌

To print those characters in the correct encoding for the locale's charset (ksh93's printf builtin also supports that \uXXXX notation but always outputs in UTF-8 regardless of the locale's charset). Shells whose printf builtin support it and have an echo that expands escape sequences (possibly with -e) typically also support \uXXXX there.

Now, AFAICT, the only two charsets where those two U+274C and U+2714 characters are available on a typical GNU system are UTF-8 and GB18030. In locales using different charsets, printf will not be able to display those characters as they simply don't exist. Depending on the implementation, printf will print \u274C literally or fail with an error.

Some shells (zsh where it originated, bash, ksh93, mksh, FreeBSD sh) also support that \uXXXX notation in their $'...' quotes.

So you can do:

echo $'\u2714\u274c'

Depending on the shell, those will be expanded to the locale's encoding that was in effect at the time the command was parsed (bash) or at the time it was run (zsh) or in UTF-8 always (ksh).

POSIXly (portably among Unix-like systems), if you want to print arbitrary sequences of bytes, you'll want to use printf and the octal notation.

\xE2\x9C\x94 (the UTF-8 encoding of U+2714) would be printed on a line portably with:

printf '\342\234\224\n'

And if you wanted it to be converted to the correct encoding for the locale, that would be:

printf '\342\234\224\n' | iconv -f UTF-8

POSIX doesn't specify which character encoding may be supported on a system, nor their name, but the above command would typically work on POSIX systems that support the UTF-8 encoding.


Using raw "cut and paste" Unicode we get a more readable solution:

$ echo -e '☑ done\n☒ fail\n☐ to do'
☑ done
☒ fail
☐ to do