255

I am doing a find to get a list of files.

How do I pipe it to another utility like cat so that cat displays the contents of all those files?

Afterwards, I'd use grep on that to search some text in those files.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Devang Kamdar
  • 5,617
  • 8
  • 24
  • 16

15 Answers15

408
  1. Piping to another process (although this won't accomplish what you said you are trying to do):

     command1 | command2
    

    This will send the output of command1 as the input of command2.

  2. -exec on a find (this will do what you want to do, but it's specific to find):

     find . -name '*.foo' -exec cat {} \;
    

    Everything between find and -exec are the find predicates you were already using. {} will substitute the particular file you found into the command (cat {} in this case); the \; is to end the -exec command.

  3. Send output of one process as command line arguments to another process:

     command2 `command1`
    

    For example:

     cat `find . -name '*.foo' -print`
    

    Note these are backquotes not regular quotes (they are under the tilde ~ on my keyboard).

    This will send the output of command1 into command2 as command line arguments. It's called command substitution. Note that file names containing spaces (newlines, etc) will be broken into separate arguments, though.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
kenj0418
  • 6,688
  • 2
  • 27
  • 23
  • 3
    cat `find -name '*.foo' -print` worked great for me ... Thanks – Devang Kamdar May 15 '09 at 13:24
  • The backquotes work great and is more generalized, you can use this to cat a list of files from a file as well. – Hazok Sep 02 '11 at 18:31
  • 11
    Note that modern versions of `find` allow you to write: `find . -name '*.foo' -exec cat {} +`, where the `+` indicates that `find` should group as many file names as convenient into a single command invocation. This is quite useful (it deals with spaces etc in file names without resorting to `-print0` and `xargs -0`). – Jonathan Leffler Nov 24 '11 at 19:29
  • the exec() also works for grep (find . -name '*.foo' -exec grep bar {} \;) – Mike Pone Sep 26 '12 at 19:39
  • 27
    Unmentioned: `find . -name '*.foo' | xargs cat` – stewSquared Aug 24 '16 at 20:16
  • cat `find . -name '*.foo' -print` . This variant hang when no file exist. – vvkatwss vvkatwss Apr 02 '17 at 17:00
  • 5
    Just to add on @stewSquared s answer: To find all lines in files that contain a certain string, do `find . -name '*.foo' | xargs cat | grep string` – Bim Apr 27 '17 at 09:06
  • @Bim i was looking for that, but when i add `grep mystring` its not finding my string, returning empty (centos6). – blamb Oct 17 '19 at 19:58
  • @blamb It worked for me on Debian / Ubuntu. Maybe try "xargs grep foo", or the modern version below... – Bim Oct 20 '19 at 10:23
101

Modern version

POSIX 2008 added the + marker to find which means it now automatically groups as many files as are reasonable into a single command execution, very much like xargs does, but with a number of advantages:

  1. You don't have to worry about odd characters in the file names.
  2. You don't have to worry about the command being invoked with zero file names.

The file name issue is a problem with xargs without the -0 option, and the 'run even with zero file names' issue is a problem with or without the -0 option — but GNU xargs has the -r or --no-run-if-empty option to prevent that happening. Also, this notation cuts down on the number of processes, not that you're likely to measure the difference in performance. Hence, you could sensibly write:

find . -exec grep something {} +

Classic version

find . -print | xargs grep something

If you're on Linux or have the GNU find and xargs commands, then use -print0 with find and -0 with xargs to handle file names containing spaces and other odd-ball characters.

find . -print0 | xargs -0 grep something

Tweaking the results from grep

If you don't want the file names (just the text) then add an appropriate option to grep (usually -h to suppressing 'headings'). To absolutely guarantee the file name is printed by grep (even if only one file is found, or the last invocation of grep is only given 1 file name), then add /dev/null to the xargs command line, so that there will always be at least two file names.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • For those confused like I am, note that this way will first give all the output of find, and then give the output of `xargs grep something`. – Eric Hu Feb 13 '13 at 05:23
  • 3
    @EricHu: I can see you are confused, but it does not do what you say it does, at least not on any Unix-based system I know of. The output of `find` is piped to the standard input of `xargs`. The `xargs` program reads its standard input, splitting the input at white space (blanks, newlines, tabs, etc) and appends a number of the words to the command `grep something` and executes the command line. `xargs` then continues reading input and executing commands until it runs out of input. `xargs` runs the `grep` command as often as necessary for the input it is given (from `find` in this example). – Jonathan Leffler Feb 13 '13 at 05:32
  • Ah my mistake, this is using grep to search within each file matched. I was looking to simply filter the output of find with grep – Eric Hu Feb 13 '13 at 05:47
  • That you can do by dropping the `xargs` from the pipeline, sending the output of `find` direct to `grep`. – Jonathan Leffler Feb 13 '13 at 05:51
  • Hmm, that's actually what I was trying before I searched for this. I guess `find` outputs errors to something other than standard out? The `2>/dev/null` param for flag was what I needed – Eric Hu Feb 13 '13 at 05:55
  • 1
    Errors go to standard error (file descriptor 2) on all well-behaved commands. Redirecting stderr to `/dev/null` loses the error messages. – Jonathan Leffler Feb 13 '13 at 06:07
  • 1
    This is also has the benefit that it works better with spaces in the file path. Even 'sed'ing " " -> "\ " breaks it with the ` but with xargs it works perfectly – JZL003 Nov 24 '14 at 20:43
  • @JZL003 when i try it with sed, i get "illegal byte sequence" from sed. – Michael Mar 23 '18 at 03:01
45

There are a few ways to pass the list of files returned by the find command to the cat command, though technically not all use piping, and none actually pipe directly to cat.

  1. The simplest is to use backticks (`):

    cat `find [whatever]`
    

    This takes the output of find and effectively places it on the command line of cat. This doesn't work well if find has too much output (more than can fit on a command-line) or if the output has special characters (like spaces).

  2. In some shells, including bash, one can use $() instead of backticks :

    cat $(find [whatever])
    

    This is less portable, but is nestable. Aside from that, it has pretty much the same caveats as backticks.

  3. Because running other commands on what was found is a common use for find, find has an -exec action which executes a command for each file it finds:

    find [whatever] -exec cat {} \;
    

    The {} is a placeholder for the filename, and the \; marks the end of the command (It's possible to have other actions after -exec.)

    This will run cat once for every single file rather than running a single instance of cat passing it multiple filenames which can be inefficient and might not have the behavior you want for some commands (though it's fine for cat). The syntax is also a awkward to type -- you need to escape the semicolon because semicolon is special to the shell!

  4. Some versions of find (most notably the GNU version) let you replace ; with + to use -exec's append mode to run fewer instances of cat:

    find [whatever] -exec cat {} +
    

    This will pass multiple filenames to each invocation of cat, which can be more efficient.

    Note that this is not guaranteed to use a single invocation, however. If the command line would be too long then the arguments are spread across multiple invocations of cat. For cat this is probably not a big deal, but for some other commands this may change the behavior in undesirable ways. On Linux systems, the command line length limit is quite large, so splitting into multiple invocations is quite rare compared to some other OSes.

  5. The classic/portable approach is to use xargs:

    find [whatever] | xargs cat
    

    xargs runs the command specified (cat, in this case), and adds arguments based on what it reads from stdin. Just like -exec with +, this will break up the command-line if necessary. That is, if find produces too much output, it'll run cat multiple times. As mentioned in the section about -exec earlier, there are some commands where this splitting may result in different behavior. Note that using xargs like this has issues with spaces in filenames, as xargs just uses whitespace as a delimiter.

  6. The most robust, portable, and efficient method also uses xargs:

    find [whatever] -print0 | xargs -0 cat
    

    The -print0 flag tells find to use \0 (null character) delimiters between filenames, and the -0 flag tells xargs to expect these \0 delimiters. This has pretty much identical behavior to the -exec...+ approach, though is more portable (but unfortunately more verbose).

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
11

To achieve this (using bash) I would do as follows:

cat $(find . -name '*.foo')

This is known as the "command substitution" and it strips line feed by default which is really convinient !

more infos here

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Stphane
  • 3,368
  • 5
  • 32
  • 47
7

Sounds like a job for a shell script to me:

for file in 'find -name *.xml'
do
   grep 'hello' file
done

or something like that

Gandalf
  • 9,648
  • 8
  • 53
  • 88
5

Here's my way to find file names that contain some content that I'm interested in, just a single bash line that nicely handles spaces in filenames too:

find . -name \*.xml | while read i; do grep '<?xml' "$i" >/dev/null; [ $? == 0 ] && echo $i; done
Greg
  • 51
  • 1
  • 1
3

Here is my shot for general use:

grep YOURSTRING `find .`

It will print the file name

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
zakki
  • 31
  • 1
3

The find command has an -exec argument that you can use for things like this, you could just do the grep directly using that.

For example (from here, other good examples at this page):

find . -exec grep "www.athabasca" '{}' \; -print 
Chad Birch
  • 73,098
  • 23
  • 151
  • 149
2

I use something like this:

find . -name <filename> -print0 | xargs -0 cat | grep <word2search4>

"-print0" argument for "find" and "-0" argument for "xargs" are needed to handle whitespace in file paths/names correctly.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
ekinak
  • 41
  • 5
2

In bash, the following would be appropriate:

find /dir -type f -print0 | xargs -0i cat {} | grep whatever

This will find all files in the /dir directory, and safely pipe the filenames into xargs, which will safely drive grep.

Skipping xargs is not a good idea if you have many thousands of files in /dir; cat will break due to excessive argument list length. xargs will sort that all out for you.

The -print0 argument to find meshes with the -0 argument to xargs to handle filenames with spaces properly. The -i argument to xargs allows you to insert the filename where required in the cat command line. The brackets are replaced by the filename piped into the cat command from find.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
0

This works for me

find _CACHE_* | while read line; do
    cat "$line" | grep "something"
done
Zombo
  • 1
  • 62
  • 391
  • 407
0

Use ggrep.

ggrep -H -R -I "mysearchstring" *

to search for a file in unix containing text located in the current directory or a subdirectory

Underverse
  • 1,271
  • 21
  • 32
0

This will print the name and contents of files-only recursively..

find . -type f -printf '\n\n%p:\n' -exec cat {} \;

Edit (Improved version): This will print the name and contents of text (ascii) files-only recursively..

find . -type f -exec grep -Iq . {} \; -print | xargs awk 'FNR==1{print FILENAME ":" $0; }'

One more attempt

find . -type f -exec grep -Iq . {} \; -printf "\n%p:" -exec cat {} \;
-1

To list and see contents of all abc.def files on a server in the directories /ghi and /jkl

find /ghi /jkl -type f -name abc.def 2> /dev/null -exec ls {} \; -exec cat {} \;

To list the abc.def files which have commented entries and display see those entries in the directories /ghi and /jkl

find /ghi /jkl -type f -name abc.def 2> /dev/null -exec grep -H ^# {} \;
Sharjeel
  • 290
  • 1
  • 7
  • 17
-1

Are you trying to find text in files? You can simply use grep for that...

grep searchterm *
Scott Arrington
  • 12,325
  • 3
  • 42
  • 54