14

If I have a string with nonprintable characters, new lines, or tabs, is there a way I can use echo to print this string and show codes for these characters (e.g., \n for new line, \b for backspace)?

3
  • 2
    you can use printf command, see man printf Commented Sep 9, 2014 at 1:24
  • 13
    e- ohce *rimshot* Commented Sep 9, 2014 at 13:00
  • 3
    printf '%q' "$str" will print the string escaped. I'm not adding this as an answer since it's not the same form of escaping that echo -e uses -- rather, it's intended to be eval-safe -- but (1) it's good enough if your goal is human readability, (2) it's good enough if your goal is safe shell generation, and (3) echo -e is Evil And Wrong anyhow (if you read the POSIX spec, you'll see that printf is the preferred way to do string formatting, and echo -e is not guaranteed by baseline POSIX at all). Commented Sep 9, 2014 at 19:31

6 Answers 6

12

When you remove the -e switch to echo, in bash, and provided the xpg_echo option has not been enabled, it should print the strings as nothing more than their original text. So \n and \t would show up as literally that.

Example

$ echo "hi\t\tbye\n\n"
hi\t\tbye\n\n

$ echo -e "hi\t\tbye\n\n"
hi      bye

You can also use the printf built-in command of ksh93, zsh or bash to finagle this as well.

$ printf "%q\n" "$(echo -e "hi\t\tbye\n\nbye")"
$'hi\t\tbye\n\nbye'

(bash output shown above. There are some variations depending on the shell).

excerpt from help printf in bash

%q quote the argument in a way that can be reused as shell input

6
  • 1
    It's not printf %q that's truncating the trailing \ns, it's the $() output capture that strips them out.
    – ecatmur
    Commented Sep 9, 2014 at 9:14
  • @ecatmur - thanks I wrote that late at night and hadn't tried to debug it further. You'd be correct, it's the subshell that's stripping them off. Here's a reason why: unix.stackexchange.com/questions/17747/…
    – slm
    Commented Sep 9, 2014 at 12:09
  • ...anyhow, running printf inside of a subshell to capture its output is silly, since printf -v varname will put the output directly into a variable, no subshell involved. Commented Sep 9, 2014 at 19:33
  • @CharlesDuffy - I'm not following you, I'm not running printf in a subshell, I was running echo in one, purely to demonstrate that you can take the special character output from echo and convert it back to the escaped notation.
    – slm
    Commented Sep 9, 2014 at 19:40
  • @slm, that was a response to the first comment (well, the code that the first comment responded to), not the code as I found it now. If I inferred something inaccurate about prior edit state, my apologies. Commented Sep 9, 2014 at 19:41
7

There are a lot of ways to do this. The most portable two that I know of are sed and od - they're both POSIX.

printf '\n\r\b\t\033[01;31m' | sed -n l

It does like... read style escapes - C-style.

OUTPUT

$
\r\b\t\033[01;31m$

od is a little more configurable...

printf '\n\r\b\t\033[01;31m' |
od -v -w12 -t c -t a -A n

 \n  \r  \b  \t 033   [   0   1   ;   3   1   m
 nl  cr  bs  ht esc   [   0   1   ;   3   1   m

If you wanna know what all of those options do you can look in man od, but I specify I want two types of escapes - the -t c backslash escapes and the -t a named characters. The -w option used above is not POSIX-specified.

And here's a little shell function that will portably print out the octal values of each byte in its arguments - which, of course, od might handle as well with -t o:

proctal() (LC_ALL=C
    for a do while [ -n "$a" ] 
    do printf %o\\n "'$a"
    a=${a#?}; done; done)  

That's a simple one. This is a little more complicated. It should be able to do what the shell-specific printf -q implementations can, though.

bsq() (set -f; export LC_ALL=C IFS=\'
    for a do q=${a##*\'}; printf \'
    [ -n "${a#"$q"}" ] &&
        printf "%s'\''" ${a%\'*}
    printf "%s'\n'''''\n" "$q"; done |
    sed -n "/'''''"'/!H;1h;//!d;$!n;x;l' |
    sed -e :n -e '/\\$/N;s/.\n//;tn
        s/\([^\\]\\\(\\\\\)*\)\([0-9]\)/\10\3/g
        s/\\\\'"''/\\\\''"'/g;s/$$//'
)

Using out example string from earlier with a little additional:

bsq "$(printf '\n\r\'\''b\t\033[01;31m')"

OUTPUT

'\n\r\\'\''b\t\0033[01;31m'

It's only slightly different. You might notice there's an extra 0 and an extra \backslash. This is to allow for easy translation to a read or a %b printf argument. For example:

i=0
until [ $((i=$i+1)) -gt 5 ]
do touch "\%$i$(printf 'hey\b \t;\n\033 ')"
done   #just for ugly's sake

bsq * | eval "
    printf '<%b>\n' $(tr \\n \ )
" | tee /dev/fd/2 |
sed -n l

OUTPUT

<\%1he  ;
>
<\%2he  ;
>
<\%3he  ;
>
<\%4he  ;
>
<\%5he  ;
>
<\\%1hey\b \t;$
\033 >$
<\\%2hey\b \t;$
\033 >$
<\\%3hey\b \t;$
\033 >$
<\\%4hey\b \t;$
\033 >$
<\\%5hey\b \t;$
\033 >$
7
  • @StéphaneChazelas It will still only do single-byte chars, but hopefully its less likely to screw up now.
    – mikeserv
    Commented Sep 9, 2014 at 12:06
  • 1
    Let me get you to the 10k mark :)
    – Ramesh
    Commented Sep 9, 2014 at 12:23
  • Woohoo!!!!!!!!!
    – slm
    Commented Sep 9, 2014 at 16:41
  • @slm - let me guess.. You really liked the proctal?
    – mikeserv
    Commented Sep 9, 2014 at 16:44
  • @StéphaneChazelas - do you have any thoughts on bsq() - is it missing anything? I ask because I'm not entirely confident about the backslashes.
    – mikeserv
    Commented Sep 10, 2014 at 6:10
5

You could use something like,

$ cat filename
Ramesh is testing
New Line        is

Now, you could do something like,

$ cat -A filename

Output

Ramesh is testing$
New Line ^Iis $

Another general testing without any files is as,

$ echo Hello$'\t'world. | cat -A

The above command yields the output as,

Hello^Iworld.$

References

1
  • 4
    Or, in non-GNU implementations of cat, e.g. MacOS/X, take your cat to the vet ... cat -vet
    – tink
    Commented Sep 9, 2014 at 2:20
3

With zsh, there are the (q), (qq), (qqq), (qqqq) variable expansion flags that can quote the variables in various ways:

$ a=$'a b\nba\bc\u00e9\0'

$ printf '%s\n' $a
a b
bcé

$ printf %s $a | od -vtc
0000000   a       b  \n   b   a  \b   c 303 251  \0
0000013

$ printf '%s\n' ${(q)a}
a\ b$'\n'ba$'\b'cé$'\0'

$ printf '%s\n' ${(qq)a}
'a b
bcé'

$ printf '%s\n' ${(qqq)a}
"a b
bcé"

$ printf '%s\n' ${(qqqq)a}
$'a b\nba\bcé\0'

$ (){local LC_ALL=C; print -r -- ${(qqqq)a}}
$'a b\nba\bc\303\251\0'

In your case, you'd probably want one of the last two.

1

od -c will print normal characters normally, but special characters (tab, newline, etc.) and unprintable characters as their printf escape code.

So:

$ echo -e asdf\\nghjk\\003foo | od -c -An
   a   s   d   f  \n   g   h   j   k 003   f   o   o  \n

The -An tells od to not output the address.

-1

Use the printf command. For example:

printf "Hello\nthis is my line

will output

Hello
this is my line
2
  • 1
    This is the same as echo -e, I'm looking for the reverse.
    – drs
    Commented Sep 9, 2014 at 13:24
  • can you give me specific example? Commented Sep 9, 2014 at 14:52

You must log in to answer this question.

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