A here-string <<<
adds a newline at the end of the string before passing it to the command, and a command substitution removes any trailing newlines after reading the output of the command inside.
Also, echo
adds a trailing newline to what it prints, but that's not a major issue here.
So, let's say you have one complete line in the variable, with the newline at the end, so the string is file1: ok<nl>
Now, you run sed '/ok/d' <<< "$LIST"
, and here, sed
gets the input file1: ok<nl><nl>
. It removes any lines that contain ok
, and outputs the empty line <nl>
.
You capture that with a command substitution, which removes the trailing newlines, giving the empty string. That's then assigned to NEWLIST
.
Then, echo "$NEWLIST"
prints a single newline (because echo
adds one), and wc -l <<< "$NEWLIST"
gives a single newline as input to wc
(because the here-string adds one).
You'd get the same end result if the variable was initially just file1: ok
, without the trailing newline, just the command substitution would have no newlines to remove from the end of the output of sed.
All of that means both command substitutions and here-strings mostly work well for single-line values, where you might not want the newline to appear in a variable but often do want to provide any commands with a complete line as input. As you saw, they do work for multiline-strings too (if the final newlines is again missing from the variable), but the odd dropping and adding of the newline still happens. It breaks down in the case where there is no newline to remove.
To see why that juggling is done, note that if command substitution didn't remove the newline from the output of date
here, this would print a newline just before the period at the end, breaking the line in the middle.
$ weekday=$(date +%A)
$ echo "today is a $weekday."
today is a Thursday.
The simplest workaround is likely to just store multiline data like that in files instead. With inputfile
containing the five lines (with the appropriate newlines):
file1: ok
file2: ok
file3: ok
file4: ok
file5: ok
then:
tmpfile=$(mktemp)
sed -e '/ok/d' < inputfile > "$tmpfile"
wc -l < "$tmpfile"
rm -f "$tmpfile"
outputs 0
.
(Note that neither \s
or \+
is standard POSIX basic or extended regex syntax, so they don't work on all systems, e.g. with the sed
on macOS. That's why I used just /ok/
above.)
$LIST
variable. They are what makes each file's text appear on its own line when you doecho "$LIST"
. – Sotto Voce Jul 26 '23 at 20:24