Recursively search a pattern/text only in the specified file name of a directory?
In the parent directory, you could use find
and then run grep
on only those files:
find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
You could also use globstar.
Building grep
commands with find
, as in Zanna's answer, is a highly robust, versatile, and portable way to do this (see also sudodus's answer). And muru has posted an excellent approach of using grep
's --include
option. But if you want to use just the grep
command and your shell, there is another way to do it -- you can make the shell itself perform the necessary recursion:
shopt -s globstar # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt
The -H
flag makes grep
show the filename even if only one matching file is found. You can pass the -a
, -i
, and -n
flags (from your example) to grep
as well, if that's what you need. But don't pass -r
or -R
when using this method. It is the shell that recurses directories in expanding the glob pattern containing **
, and not grep
.
These instructions are specific to the Bash shell. Bash is the default user shell in Ubuntu (and most other GNU/Linux operating systems), so if you're on Ubuntu and don't know what your shell is, it's almost certainly Bash. Although popular shells usually support directory-traversing **
globs, they don't always work the same way. For more information, see Stéphane Chazelas's excellent answer to The result of ls * , ls ** and ls *** on Unix.SE.
How It Works
Turning on the globstar bash shell option makes **
match paths containing the directory separator (/
). It is thus a directory-recursing glob. Specifically, as man bash
explains:
When the globstar shell option is enabled, and * is used in a pathname expansion context, two adjacent *s used as a single pattern will match all files and zero or more directories and subdirectories. If followed by a /, two adjacent *s will match only directories and subdirectories.
You should be careful with this, since you can run commands that modify or delete far more files than you intend, especially if you write **
when you meant to write *
. (It's safe in this command, which doesn't change any iles.) shopt -u globstar
turns the globstar shell option back off.
There are a few practical differences between globstar and find
.
find
is far more versatile than globstar. Anything you can do with globstar, you can do with the find
command too. I like globstar, and sometimes it's more convenient, but globstar is not a general alternative to find
.
The method above does not look inside directories whose names start with a .
. Sometimes you don't want to recurse such folders, but sometimes you do.
As with an ordinary glob, the shell builds a list of all matching paths and passes them as arguments to your command (grep
) in place of the glob itself. If you have so many files called file.txt
that the resulting command would be too long for the system to execute, then the method above will fail. In practice you'd need (at least) thousands of such files, but it could happen.
The methods that use find
are not subject to this restriction, because:
Zanna's way builds and runs a
grep
command with potentially many path arguments. But if more files are found than can be listed in a single path, the+
-terminated-exec
action runs the command with some of the paths, then runs it again with some more paths, and so forth. In the case ofgrep
ing for a string in multiple files, this produces the correct behavior.Like the globstar method covered here, this prints all matching lines, with paths prepended to each.
sudodus's way runs
grep
separately for eachfile.txt
found. If there are many files, it might be slower than some other methods, but it works.That method finds files and prints their paths, followed by matching lines if any. This is a different output format from the format produced by my method, Zanna's, and muru's.
Getting color with find
One of the immediate benefits of using globstar is, by default on Ubuntu, grep
will produce colorized output. But you can easily get this with find
, too.
User accounts in Ubuntu are created with an alias that makes grep
really run grep --color=auto
(run alias grep
to see). It's a good thing that aliases are pretty much only expanded when you issue them interactively, but it means that if you want find
to invoke grep
with the --color
flag, you'll have to write it explicitly. For example:
find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
You don't need find
for this; grep
can handle this perfectly fine on its own:
grep "pattern" . -airn --include="file.txt"
From man grep
:
--exclude=GLOB
Skip files whose base name matches GLOB (using wildcard
matching). A file-name glob can use *, ?, and [...] as
wildcards, and \ to quote a wildcard or backslash character
literally.
--exclude-from=FILE
Skip files whose base name matches any of the file-name globs
read from FILE (using wildcard matching as described under
--exclude).
--exclude-dir=DIR
Exclude directories matching the pattern DIR from recursive
searches.
--include=GLOB
Search only files whose base name matches GLOB (using wildcard
matching as described under --exclude).