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."

With cat

Here Strings

cat <<< 'This text spans
multiple lines.'
cat <<< "This text spans
multiple lines."
cat <<<"\
This text spans
multiple lines."

Here Documents

cat <<EOF
This text spans
multiple lines.
EOF

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