16

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!

Community
  • 1
  • 1
user987654
  • 5,461
  • 5
  • 23
  • 26

5 Answers5

31

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

necromancer
  • 23,916
  • 22
  • 68
  • 115
14

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.

necromancer
  • 23,916
  • 22
  • 68
  • 115
rici
  • 234,347
  • 28
  • 237
  • 341
14

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

dshepherd
  • 4,989
  • 4
  • 39
  • 46
2

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

tavvit
  • 259
  • 3
  • 6
0

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
Aleks-Daniel Jakimenko-A.
  • 10,335
  • 3
  • 41
  • 39