32

Usually, we only need to pass one argument:

echo abc | cat
echo abc | cat some_file -
echo abc | cat - some_file

Is there a way to pass two arguments? Something like

{echo abc , echo xyz} | cat
cat `echo abc` `echo xyz`

I could just store both results in a file first

echo abc > file1
echo xyz > file2
cat file1 file2

But then I might accidentally overwrite a file, which is not ok. This is going into a non-interactive script. Basically, I need a way to pass the results of two arbitrary commands to cat without writing to a file.


UPDATE: Sorry, the example masks the problem. While { echo abc ; echo xyz ; } | cat does seem to work, the output is due to the echos, not the cat.

A better example would be { cut -f2 -d, file1; cut -f1 -d, file2; } | paste -d, which does not work as expected.

With

file1:
a,b
c,d

file2:
1,2
3,4

Expected output is:

b,1
d,3

RESOLVED:

Use process substitution: cat <(command1) <(command2)

Alternatively, make named pipes using mkfifo:

mkfifo temp1
mkfifo temp2
command1 > temp1 &
command2 > temp2 &
cat temp1 temp2

Less elegant and more verbose, but works fine, as long as you make sure temp1 and temp2 don't exist before hand.

5
  • 1
    This makes me scratch my head. You want to pipe two different commands to a single cat?
    – user606723
    Aug 1, 2011 at 21:02
  • 2
    You do know that "piping" and "arguments" are two completely unrelated concepts, right? Aug 1, 2011 at 21:25
  • @Ignacio err... I'm not that familiar with Linux so I don't really know the difference. I always thought piping meant it takes the previous result as an argument. Care to enlighten me?
    – goweon
    Aug 1, 2011 at 21:32
  • @firebat no, pipe means to take the stdout of the previous command as standard input of the next command. You can still have arguments, which are a different concept. Aug 1, 2011 at 22:40
  • also, as far as your Update text, the output does come from cat, just you want the statements seemed as separate to cat, which it can not do since they are serialized into one stream. Aug 1, 2011 at 22:43

5 Answers 5

23

I think you want 'Process Substitution' http://tldp.org/LDP/abs/html/process-sub.html. It also works on zsh, though it has more options and the syntax may be different.

It creates a pseudo file (/dev/fd/something) for each substitution. It's pretty useful. The command can only read as a stream, meaning it can not go back and forth with fseek. It needs to read it as a stream of bytes, like a pipe.

Your examples work as process substitution:

cat <(echo abc) <(echo xyz)
paste -d, <(cut -f2 -d, file1) <(cut -f1 -d, file2)

Think of it as a tool to use a process and have it pretend to be a file without any temp files.

(from my earlier answer )

1
  • Wow, really cool! Just what I needed :)
    – WPWoodJr
    Apr 30, 2022 at 2:15
3

I like xargs As an example I want to find the size of all the mysql data dir under /usr/local

osx:local user$ pwd
/usr/local
osx:local user$  ls | grep mysql 
mysql
mysql-5.0.51a-osx10.5-x86_64
mysql-5.0.51b-osx10.5-x86_64
mysql-5.1.39-osx10.5-x86_64
mysql-5.6.17-osx10.7-x86_64
os x:local user$  ls | grep mysql |sudo  xargs du -sh
4.0K    mysql
2.8G    mysql-5.0.51a-osx10.5-x86_64
 10G    mysql-5.0.51b-osx10.5-x86_64
 25G    mysql-5.1.39-osx10.5-x86_64
753M    mysql-5.6.17-osx10.7-x86_64
osx:local user$ 

xargs

2

The curly braces thingy actually worked because the pipe operator will connect the standard outputs of the grouped commands into the standard input of the third.

You can tell the printout is not from the echos by running any command that does not use standard input, e.g. { echo 1; echo 2;} | echo 3 will print 3 as opposed to 1\n2\n3.

However, this is not the thing you wanted because the two commands in the curly braces are writing to the same output which is like cat-ing their results. To generate two different results to use as the two arguments for commands like paste, you'll have to use a temporary file or a named pipe.

You can use mktemp to generate a unique temporary file or mkfifo to create a FIFO pipe file.

0
0

If your values are not super large, you can, as this post explains, just use sub-commands to get the content:

mysupercommand -arg1="$(mycommand1)" -arg2="$(mycommand2)"

or, for your case:

cat "$(cat abc)" "$(cat xyz)"
0

I found an amazing generalized way to parallelize input streams and use stream fusion:

while IFS= read -r -u3 three \
   && IFS= read -r -u4 four  \
   && IFS= read -r -u5 five
   do echo "three=$three, four=$four, five=$five"
 done 3< <( firstCommand  ) \
      4< <( secondCommand ) \
      5< <( thirdCommand  )

This can be expanded as far as there are stream and file descriptors available. The slashes at the end are just to escape the line break to extend a single line over multiple lines to make it easier on the eyes.

It works like this:

  1. The commands are put inside process substitution (<( … )) so they appear as files, using implicit FIFOs.
  2. Those files are sent to the command using new streams (… N< …, where N is the number of the stream).
  3. Now here, I used a while loop as the command.
  4. Which loops, as long as all (&&) of the read commands haven’t reached EOF and bailed yet.
  5. Those reads are told to not do any special processing of escapes (-r) not cut off anything (IFS=), read one line from a specific stream (-uN), and put it in a specific variable (e.g. three).
  6. Finally, the actual command (an echo in this case) can do its job with multiple streams, by using those multiple variables however it likes.

I feel like some steps could possibly be skipped, so if anyone knows how without reducing any of the flexibility and power of this example, you’re welcome to edit this answer or comment below.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .