3

How can I disable word-splitting during command substitution? Here's a simplified example of the problem:

 4:00PM /Users/paymahn/Downloads
 ❯❯❯ cat test.txt
hello\nworld
 4:00PM /Users/paymahn/Downloads
 ❯❯❯ echo $(cat test.txt )
hello
world
 4:00PM /Users/paymahn/Downloads
 ❯❯❯ echo "$(cat test.txt )"
hello
world
 4:01PM /Users/paymahn/Downloads
 ❯❯❯ echo "$(cat "test.txt" )"
hello
world

What I want is for echo $(cat test.txt) (or some variant of that which includes command subsitution) to output hello\nworld.

I found https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html which says at the bottom If the substitution appears within double quotes, word splitting and filename expansion are not performed on the results. but I can't seem to make sense of that. I would have thought that one of the examples I already tried conformed to that rule but I guess not.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

2 Answers2

10

Having a literal \n get changed to a newline isn't about word splitting, but echo processing the backslash. Some versions of echo do that, some don't... Bash's echo doesn't process backslash-escapes by default (without the -e flag or xpg_echo option), but e.g. dash's and Zsh's versions of echo do.

$ cat test.txt 
hello\nworld
$ bash -c 'echo "$(cat test.txt)"'
hello\nworld
$ zsh -c 'echo "$(cat test.txt)"'
hello
world

Use printf instead:

$ bash -c 'printf "%s\n" "$(cat test.txt)"'
hello\nworld
$ zsh -c 'printf "%s\n" "$(cat test.txt)"'
hello\nworld

See also: Why is printf better than echo?

Regardless of that, you should put the quotes around the command substitution to prevent word splitting and globbing in sh-like shells. (zsh only does word splitting (not globbing) upon command substitution (not upon parameter or arithmetic expansions) except in sh-mode.)

ilkkachu
  • 138,973
0

The echo implemented by zsh interprets escape sequences by default. The simplest solution is:

$ echo -E "$(cat test.txt)"
hello\nworld

or

$ print -r "$(cat test.txt)"

The correct solution is to use printf:

$ printf '%s\n' "$(<test.txt)"
  • Note that in zsh one may prefer echo -E - "$(<test.txt)" or print -r -- "$(<test.txt)" in case the content of test.txt starts with -f, which ends up not being much shorter than printf '%s\n' "$(<test.txt)". – Stéphane Chazelas Aug 23 '18 at 06:56