It's not the printing, it's the command substitution that does that. It's defined to do that. From the POSIX description:
The shell shall expand the command substitution by executing command in a subshell environment and replacing the command substitution with the standard output of the command, removing sequences of one or more {newline} characters at the end of the substitution.
Note that it removes all trailing newlines, not just one.
In a somewhat common case, you'd use command substitution to capture a one-line output, say osrev=$(uname -r)
. The utilities usually print a trailing newline, for user convenience on the command line. But in a shell script, you might want to use that string as part of another one, say a filename: filename=blahblah-$osrev.dat
. And in that case, the trailing newline would only be a nuisance.
And of course, a plain echo
will add a final newline in any case.
If you want the contents of the file as-is in the variable, then the common workaround is to add an extra character in the command substitution, and remove that later:
printf "foo\nbar\n\n" > file
string=$(cat file; echo x)
string=${string%x}
printf '%q\n' "$string"
that outputs $'foo\nbar\n\n'
, showing both trailing newlines present.
Depending on what you intend to do with the data, there may be other ways. E.g. a while read
loop, or Bash's mapfile
, if you happen to want to process the file line-by-line.
cat <<< foo
. – JoL May 31 '18 at 04:46