135

Let's say I have a script that I want to pipe to another command or redirect to a file (piping to sh for the examples). Assume that I'm using bash.

I could do it using echo:

echo "touch somefile
echo foo > somefile" | sh

I could also do almost the same thing using cat:

cat << EOF
touch somefile
echo foo > somefile
EOF

But if I replace "EOF" with "EOF | sh" it just thinks that it's a part of the heredoc.

How can I make it so that cat outputs text from stdin, and then pipes it to an arbitrary location?

αғsнιη
  • 41,407
strugee
  • 14,951
  • touch is unless there , what exactly you want ? is just read input file and redirect to another with stdout? – Rahul Patil Aug 28 '13 at 05:14
  • 3
    @RahulPatil of course it's useless. I just wanted an example with more than one line, to better illustrate the heredoc. And look at the example using echo - I was wondering what the equivalent of that would be using cat. – strugee Aug 28 '13 at 05:20
  • Your example is creating an sh script out of multiple strings and passing it directly to sh . I found your Q looking for passing the text output of multiple commands, including a HERE document, directly to a command. Which is what Ash's answer's 2nd example does. – Dave X Sep 22 '19 at 14:29

3 Answers3

232

There are multiple ways to do this. The simplest is probably this:

cat <<EOF | sh
touch somefile
echo foo > somefile
EOF

Another, which is nicer syntax in my opinion:

(
cat <<EOF
touch somefile
echo foo > somefile
EOF
) | sh

This works as well, but without the subshell:

{
cat <<EOF
touch somefile
echo foo > somefile
EOF
} | sh

More variations:

cat <<EOF |
touch somefile
echo foo > somefile
EOF
  sh

Or:

{ cat | sh; } << EOF
touch somefile
echo foo > somefile
EOF

By the way, I expect the use of cat in your question is a placeholder for something else. If not, take it out, like this:

sh <<EOF
touch somefile
echo foo > somefile
EOF

Which could be simplified to this:

sh -c 'touch somefile; echo foo > somefile'

or:

sh -c 'touch somefile
echo foo > somefile'

Redirecting output instead of piping

sh >out <<EOF
touch somefile
echo foo > somefile
EOF

Using cat to get the equivalent of echo test > out:

cat >out <<EOF
test
EOF

Multiple Here Documents

( cat; echo ---; cat <&3 ) <<EOF 3<<EOF2
hi
EOF
there
EOF2

This produces the output:

hi
---
there

Here's what's going on:

  • The shell sees the ( ... ) and runs the enclosed commands in a subshell.
  • The cat and echo are simple enough. The cat <&3 says to run cat with file descriptor (fd) 0 (stdin) redirected from fd 3; in other words, cat out the input from fd 3.
  • Before the (...) is started, the shell sees the two here document redirects and substitutes fd 0 (<<EOF) and fd 3 (3<<EOF2) with the read-side of pipes
  • Once the initial command is started, the shell reads its stdin until EOF is reached and sends that to the write-side of the first pipe
  • Next, it does the same with EOF2 and the write-side of the second pipe
ash
  • 7,260
  • given the second-to-last example, can you give an example with stdout redirection instead of piping? – strugee Aug 28 '13 at 05:07
  • I editted the answer with a redirect example; is this the form you wanted editted? If not, can you just give the first line of that part to clarify? – ash Aug 28 '13 at 05:10
  • 2
    It may help to understand how the shell processes this. When it's evaluating the command-line and finds the <<EOF, it then reads from standard input until either a line with only EOF is found, or end-of-input. Everything else on the original command-line that has yet been unprocessed then continues to process. So, for example, it's possible to do something like this: (cat; echo ---; exec <&3; cat) <<EOF 3<<EOF2 >out and then give that command input with two here-blocks in a row. – ash Aug 28 '13 at 05:14
  • Your explanation of shell processing was interesting but over my head :) What I wanted to know was what would be the cat equivalent of echo test > out would be, using a heredoc. Whereas your given example added a redirection at the end of the pipe. – strugee Aug 28 '13 at 05:18
  • Sorry about that. I know the bash manpage is very long, but it's useful to read and understand. I'll give you the equivalent for that command in another edit. – ash Aug 28 '13 at 05:38
  • No problem. I understood the concepts, I just couldn't follow your example. – strugee Aug 28 '13 at 05:41
  • Would you like to see a full example that you can try? – ash Aug 28 '13 at 05:42
  • @ash: i'd be interrested in seing an example with multiple heredocs (I never heard of this before today)... how do you stack them? – Olivier Dulac Aug 28 '13 at 08:58
  • 1
    @OlivierDulac - I added the multiple heredoc example to the answer. – ash Aug 29 '13 at 03:31
  • @don_crissti - thanks; I commented on that question. Looks like the Op may have a simpler approach without using here docs. – ash Nov 14 '16 at 04:28
  • 1
  • Agreed - where tee does the job, it's a good approach. – ash Jan 02 '17 at 05:11
  • @ash Is there a way to make the 2 heredocs work as an input to a command like diff? ie to accomplish diff <heredoc1> <heredoc2> – Cole May 17 '21 at 08:47
  • Tricky. You could do something like this: `exec 98<<! 1 2 3 !

    exec 99<<! 1 3 !

    diff /dev/fd/98 /dev/fd/99 `

    – ash May 17 '21 at 21:16
  • That gets squashed when inlined. – ash May 17 '21 at 21:17
  • @Cole: Here, do this: diff /dev/fd/98 /dev/fd/99 98<<! 99<<! then follow with the two here-docs terminated by ! each – ash May 17 '21 at 21:19
  • @ash Hmm, no dice for me (using bash). I get /tmp/sh-thd.{-larZPk-}{+gD04GI+} (deleted) I've also tried it with process substitution and got: pipe:[6255284{-2-}{+4+}] – Cole May 18 '21 at 03:26
  • @ash Actually I lied, this appears to work with regular diff but not git diff I was hoping to use git diff --color-words for it's word level diff but it looks like I might be out of luck. – Cole May 18 '21 at 03:28
  • @Cole there are word-level diff tools. It's been a while - maybe wdiff? – ash May 18 '21 at 15:43
5

I just want to point out that using meow is just as valid as EOF.

This script will append Meow! to a file named cat:

#!/bin/sh
cat <<meow>> cat
Meow!
meow

Save as cats and make it executable with chmod +x cats, then run it with ./cats:

$ ./cats
$ cat cat
Meow!
$ ./cats
$ cat cat
Meow!
Meow!

Explanation:

  • cat <<meow is the here document syntax. It instructs the script to pick up the block of text that follows this line, until it encounters the string meow. The contents will then be outputted (or piped along).
  • >> cat pipes to the file named cat.
  • Using > instead of >> would overwrite the file instead of appending to it.
  • Meow! is the contents of the here document.
  • meow is the end marker for the here document. The contents are, with the use of >>, appended to cat.

Piping both to stdout and to a file:

To do the piping you request, no here document is required.

cat can not both output text and pass the text along, but tee is a perfect match for what you are asking for:

echo echo | tee tee

This will both output the string echo and write echo to a file named tee.

You could also pass the output through cat, if that is part of the requirement, either with:

echo echo | tee tee | cat </dev/stdin

Or just:

echo echo | tee tee | cat

Contents of file:

$ cat tee
echo
Alexander
  • 9,850
  • 2
    @strugee, > can create pipes in zsh when the same fd is redirected several times as in echo foo >&1 > tee or echo foo > tee | cat, where zsh implements an internal tee to feed the output of echo to both cat and the tee file. – Stéphane Chazelas Jan 09 '18 at 11:06
  • Good point. The most common marker I've seen is ! – ash May 18 '21 at 15:46
2

I'd like to add a variant to this, which was useful to me for handling a case where I needed to run a list of elements through a pipe to a while loop:

cat <<EOF | while read -r FOO; do
bar
baz
quux
EOF

echo $FOO

done

Could also be written without the cat

while read -r FOO; do
  echo $FOO
done <<EOF
bar
baz
quux
EOF