user987654
user987654

Reputation: 6011

xargs with multiple commands

In the current directory, I'd like to print the filename and contents in it. I can print filenames or contents separately by

find . | grep "file_for_print" | xargs echo
find . | grep "file_for_print" | xargs cat

but what I want is printing them together like this:

file1
line1 inside file1
line2 inside file1
file2
line1 inside file2
line2 inside file2

I read xargs with multiple commands as argument and tried

find . | grep "file_for_print" | xargs -I % sh -c 'echo; cat;'

but doesn't work. I'm not familiar with xargs, so don't know what exactly "-I % sh -c" means. could anyone help me? thank you!

Upvotes: 16

Views: 28378

Answers (5)

necromancer
necromancer

Reputation: 24641

find . | grep "file_for_print" | xargs -I % sh -c 'echo %; cat %;' (OP was missing %s)

Upvotes: 32

rici
rici

Reputation: 241701

To start with, there is virtually no difference between:

find . | grep "file_for_print" | xargs echo

and

find . -name "file_for_print*"

except that the second one will not match filenames like this_is_not_the_file_for_print, and it will print the filenames one per line. It will also be a lot faster, because it doesn't need to generate and print the entire recursive directory structure just in order for grep to toss most of it away.

find . -name "file_for_print*"

is actually exactly the same as

find . -name "file_for_print*" -print

where the -print action prints each matched filename followed by a newline. If you don't provide find with any actions, it assumes you wanted -print. But it has more tricks up its sleeve than that. For example:

find . -name "file_for_print*" -exec cat {} \;

The -exec action causes find to execute the following command, up to the \;, replacing {} with each matching file name.

find does not limit itself to a single action. You can tell it to do however many you want. So:

find . -name "file_for_print*" -print -exec cat {} \;

will probably do pretty well what you want.

For lots more information on this very useful utility, type:

man find

or

info find

and read all about It.

Upvotes: 14

tavvit
tavvit

Reputation: 279

In this specific case, each command is executed for each individual file anyway, so there's no advantage in using xargs. You may just append -exec twice to your 'find':

find . -name "*file_for_print*" -exec echo {} \; -exec cat {} \;

In this case-print could be used instead of the first echo as pointed out by rici, but this example shows the ability to execute two arbitrary commands with a single find

Upvotes: 2

dshepherd
dshepherd

Reputation: 5407

Since it's not been said yet: -I % tells xargs to replace '%' with the arguments in the command you give it. The sh -c '...' just means run the commands '...' in a new shell.

So

xargs -I % sh -c 'echo %; cat %;'

will run echo [filename] followed by cat [filename] for every filename given to xargs. The echo and cat commands will be executed inside a different shell process but this usually doesn't matter. Your version didn't work because it was missing the % signs inside the command passed to xargs.


For what it's worth I would use this command to achieve the same thing:

find -name "*file_for_print*" | parallel 'echo {}; cat {};'

because it's simpler (parallel automatically uses {} as the substitution character and can take multiple commands by default).

Upvotes: 14

Aleks-Daniel Jakimenko-A.
Aleks-Daniel Jakimenko-A.

Reputation: 10653

What about writing your own bash function?

#!/bin/bash

myFunction() {
    while read -r file; do
        echo "$file"
        cat "$file"
    done
}

find . -name "file_for_print*" | myFunction

Upvotes: 1

Related Questions