There are many ways to skin a cat, an echo, a printf
Bash offers several options to organize your strings for printing or further processing.
Lots of examples. Ready?
Directly
With echo
Single quotes:
echo 'This text spans multiple lines.'
Double quotes:
echo "This text spans multiple lines."
Double quotes with escaped newline:
echo "\ This text spans multiple lines."
With printf
Let's add a trailing newline.
Single quotes:
printf 'This text spans multiple lines. '
Double quotes:
printf "This text spans multiple lines. "
Same, but with escaped newline:
printf 'This text spans multiple lines.\n'
printf "This text spans multiple lines.\n"
Same, but using %s:
printf "%s\n" "This text spans multiple lines."
printf "%s\n%s\n" "This text spans" "multiple lines."
Using local variables
If we're inside some function, we can use local variables instead.
local x="This text spans multiple lines." echo "$x"
local x="\ This text spans multiple lines." echo "$x"
Likewise with printf "$x\n" or cat <<< "$x", etc.
Using the last argument
But we don't need $x, because:
- the variable 
$_is automatically assigned to the last argument every time you run a simple command, and - the command 
:does nothing and always succeed. 
So:
: 'This text spans multiple lines.' echo "$_"
: "This text spans multiple lines." echo "$_"
: "\ This text spans multiple lines." echo "$_"
Likewise with printf "$_\n" or cat <<< "$_", etc.
Further processing
Sometimes we want to further process the text. For example:
echo "This text spans multiple lines." | sed 's/multiple/a few/' | tr . !
This text spans a few lines!
Here the input data (the text) is being given after the echo command.
No big problem here, but this can look a bit awkward with longer texts. One option is to put the text last. This won't work with echo, but will work with cat.
The disadvantage is that you have to create a brace block around the commands:
{ cat | sed 's/multiple/a few/' | tr . ! ;} <<< "This text spans multiple lines."
But we don't even need cat here, do we?
{ sed 's/multiple/a few/' | tr . ! ;} <<< "This text spans multiple lines."
And we could also make the text start in the beginning of the next line:
{ sed 's/multiple/a few/' | tr . ! ;} <<<"\ This text spans multiple lines."
And we could use parentheses instead, which is neater, if a bit slower:
(sed 's/multiple/a few/' | tr . !) <<<"\ This text spans multiple lines."
And we could use Here Documents instead of Here Strings, to do away with the quotes:
(sed 's/multiple/a few/' | tr . !) <<EOF This text spans multiple lines. EOF
Defining the text before the commands also works here. For example, with a variable:
local x="\ This text spans multiple lines." { sed 's/multiple/a few/' | tr . ! ;} <<< "$x"
But this is simpler:
: "\ This text spans multiple lines." { sed 's/multiple/a few/' | tr . ! ;} <<< "$_"
Likewise:
: "\ This text spans multiple lines." (sed 's/multiple/a few/' | tr . !) <<< "$_"
And if we replace the Here String with a Here Document, we get this odd one:
: "\ This text spans multiple lines." (sed 's/multiple/a few/' | tr . !) <<EOF $_ EOF
Multiple commands
Now, in these cases there's another way to put the text before the command without having to use braces or parentheses.
These tend to be my favorite ones. It so happens that you can use Here Strings at the beginning!
In the same way that you can do this with a file:
< somefile  sed 's/this/that/' | tr x X
you can also do this with a string:
<<< "some string" sed 's/this/that/' | tr x X
Therefore, either this:
<<< "\ This text spans multiple lines." \ sed 's/multiple/a few/' | tr . !
or this:
: "\ This text spans multiple lines." <<<"$_" sed 's/multiple/a few/' | tr . !
will work just fine, and you'll have the input at the beginning.
And did you know this also works with Here Documents? Behold:
<<EOF  sed 's/multiple/a few/' | tr . !
This text spans
multiple lines.
EOF
So this odd one would work as well:
: "\ This text spans multiple lines." <<EOF sed 's/multiple/a few/' | tr . ! $_ EOF
Process substitution
Now, going back to that (almost) initial example:
{ sed 's/multiple/a few/' | tr . ! ;} <<< "This text spans multiple lines."
You could also regroup from:
( sed | tr ) <<< STRING
to:
( sed <<< STRING ) | tr
like this:
sed 's/multiple/a few/' <<< "\ This text spans multiple lines." | tr . !
or using process substitution:
tr < <(sed <<< STRING)
like this:
tr . ! < <(sed 's/multiple/a few/' <<< "\ This text spans multiple lines.")
or bringing the Here String to the front:
tr < <(<<< STRING sed)
like this:
tr . ! < <(<<< "\ This text spans multiple lines." sed 's/multiple/a few/')
which is really useful if you enjoy seeing a lot of < characters together.
And did you know that you can also put the process substitution at the beginning?
< <(<<< STRING sed) tr
like this:
< <(<<< "This text spans multiple lines." sed 's/multiple/a few/') tr . !
which looks like this with line continuations all over it:
< <(<<< "\ This text spans multiple lines." \ sed 's/multiple/a few/') \ tr . !
Definitely not weird.
Not that it can't get weirder:
what if we now replaced the Here String with a Here Doc?
< <(<<EOF sed 's/multiple/a few/' This text spans multiple lines. EOF ) tr . !
It'd still work.
As would this:
: "This text spans multiple lines." < <(<<EOF sed 's/multiple/a few/' $_ EOF ) tr . !
Not that I'd recommend it, mind you.
But it's useful to understand it, so that you can use what makes sense given the code's constraints and your personal taste.
Redirecting it
You can also add a redirect at the end. You start with input, do stuff, end with output.
: "\ This text spans multiple lines." <<<"$_" sed 's/multiple/a few/' | tr . ! >somefile
or:
: "\ This text spans multiple lines." <<<"$_" \ sed 's/multiple/a few/' | tr . ! >somefile
or:
<<<"\ This text spans multiple lines." \ sed 's/multiple/a few/' | tr . ! >somefile
or:
<<EOF  sed 's/multiple/a few/' | tr . !  >somefile
This text spans
multiple lines.
EOF
or:
: "\ This text spans multiple lines." <<EOF sed 's/multiple/a few/' | tr . ! >somefile $_ EOF
Useless cats
People talk about "useless cat". Fair enough.
Why not "useless echo"? It's the same idea.
From this:
cat fromfile | sed "s/pi/he/" | tr s S > tofile echo "spill" | sed "s/pi/he/" | tr s S > tofile
To this:
<fromfile sed "s/pi/he/" | tr s S >tofile <<<"spill" sed "s/pi/he/" | tr s S >tofile # ^^ ^^ # I also like this extra spacing here, to convey: # <start (do | something | here) >end
📆 2025-11-02