2

Is there a way to append the stdout output of one command to another's and pipe the combined output to another command? I used to use the following approach(taking ack-grep as an example)

# List all python, js files in different directories
ack-grep -f --py apps/ > temp
ack-grep -f --js -f media/js >> temp
cat temp | xargs somecommand

Is there a way to do this in a single command?

Blagovest Buyukliev
  • 42,498
  • 14
  • 94
  • 130
dheerosaur
  • 14,736
  • 6
  • 30
  • 31

4 Answers4

6

Just run the two ack-grep commands as a compound command; then pipe the results of the compund command. The first compound command defined in man bash is the parentheses:

   (list) list is executed in a subshell environment (see  COMMAND  EXECU-
              TION  ENVIRONMENT below).  Variable assignments and builtin com-
              mands that affect the  shell's  environment  do  not  remain  in
              effect  after  the  command completes.  The return status is the
              exit status of list.

So:

james@bodacious-wired:tmp$echo one > one.txt
james@bodacious-wired:tmp$echo two > two.txt
james@bodacious-wired:tmp$(cat one.txt; cat two.txt) | xargs echo
one two

You can use curly braces to similar effect, but curly braces have a few syntactical differences (they're more finicky about needing spaces between the brace and other words, for instance). The biggest difference is that the commands inside braces are run in the current shell environment, so they can impact on your environment. For instance:

james@bodacious-wired:tmp$HELLO=world; (HELLO=MyFriend); echo $HELLO
world
james@bodacious-wired:tmp$HELLO=world; { HELLO=MyFriend; }; echo $HELLO
MyFriend

If you want to get really fancy you can define a function and execute that:

james@bodacious-wired:tmp$myfunc () (
> cat one.txt
> cat two.txt
> )
james@bodacious-wired:tmp$myfunc | xargs echo
one two
james@bodacious-wired:tmp$
James Polley
  • 7,977
  • 2
  • 29
  • 33
  • +1: but `{ ... } | somecommand` is run in a sub-shell and doesn't affect the parent shell. Demo: `X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X` (with output PQR, ABC, PQR on three lines). – Jonathan Leffler Feb 02 '12 at 07:33
  • 1
    @JonathanLeffler To directly quote `man bash`, the commands inside the braces are `simply executed in the current shell environment`; whereas the commands inside parentheses are `executed in a subshell environment`. – James Polley Feb 02 '12 at 08:28
  • On the other hand, each element of a pipeline runs in its own subshell. I *think* that's what's causing the behaviour you're seeing here. – James Polley Feb 02 '12 at 08:40
  • I don't understand what's happening here, so I've posted it as a seperate question - http://stackoverflow.com/questions/9109362/bash-subshell-pipelines-which-parts-are-executing-in-subshells – James Polley Feb 02 '12 at 08:47
  • And I do understand now; both sides of a pipeline are executed in subshells. In this case, that means that `{ .. }` pops into a subshell, while `cat` is in another subshell. This is the *pipeline* creating the subshell, not the braces. – James Polley Feb 02 '12 at 09:45
  • 1
    Yes, it is the pipeline that is poking the activity into a sub-shell, not the braced construct. But once you have a sub-shell, then the activity in the sub-shell can't affect the parent shell. This is why you have to be careful with loops that count something from a pipe; in fact, the brace notation can be helpful then to contain the loop and the post-loop processing. – Jonathan Leffler Feb 02 '12 at 15:04
  • But the output has it's quotes stripped by xargs! Instead try using the delimiter options of xargs to only squash and break on the newline '\n' character. `xargs -d'\n' echo` https://unix.stackexchange.com/questions/38148/why-does-xargs-strip-quotes-from-input#answer-408983 – Scott Feb 19 '18 at 20:12
2

Group the two commands in curly braces and pipe them:

{ ack-grep -f --py apps/; ack-grep -f --js -f media/js; } | xargs somecommand

This way you omit the creation of any files.

Blagovest Buyukliev
  • 42,498
  • 14
  • 94
  • 130
  • 1
    This is the way I'd do it. Just beware that the semi-colon before the close brace is not there for decoration! You can also use parentheses to enclose the commands in a sub-shell; the semi-colon would not be needed before the close parenthesis. Yes, there's a reason why. No; it isn't particularly easy to explain. – Jonathan Leffler Feb 02 '12 at 07:05
  • The difference in using braces as in `{ cmd1; cmd2; } | cmd3`, is that it's done in the main shell along with being able to use all the variables defined therin. The other option would be to use parentheses `( cmd1; cmd2; ) | cmd3` which will run separate shell process. And according to [David the H.](http://www.linuxquestions.org/questions/linux-software-2/bash-concatenating-the-output-of-multiple-commands-without-using-temp-files-934604/#post4627698) there is a performance difference too. – Anthony Hatzopoulos Oct 22 '14 at 14:26
0

May be something like this:

ack-grep -f --py apps/ > temp && ack-grep -f --js -f media/js >> temp && cat temp | xargs somecommand
anubhava
  • 761,203
  • 64
  • 569
  • 643
0

Yes, use find. According to the ack-grep man page those options just look for .py and .js files

find apps/ media/js -type f -name "*.py" -o -name "*.js" -exec somecommand {} +

The + option to -exec makes it work just like xargs but with the benefit that it doesn't die a horrible death if you have files with spaces or newlines or other nasties in their name.

SiegeX
  • 135,741
  • 24
  • 144
  • 154