Using grep
Why can't you just use the -r
switch to grep
to recurse the filesystem instead of making use of find
? There are 2 additional switches I'd use too, instead of the -n
switch.
$ grep -rHn PATTERN <DIR> | cut -d":" -f1-2
Example #1
$ grep -rHn PATH ~/.bashrc | cut -d":" -f1-2
/home/saml/.bashrc:25
Details
-r
- recursively search through files + directories
-H
- prints the name of the file if it matches (less restrictive than -l
) i.e. it works with grep
's other switches
-n
- display the line number of the match
Example #2
$ grep -rHn PATH ~/.bash* | cut -d":" -f1-2
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25
Using find
$ find . -exec sh -c 'grep -Hn PATTERN "$@" | cut -d":" -f1-2' {} +
Example
$ find ~/.bash* -exec sh -c 'grep -Hn PATH "$@" | cut -d":" -f1-2' {} +
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25
If you truly want to use find
you can do something like this to exec grep
upon finding the files using find
.
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq
The above finds all files below the current directory (.
) that are regular files (-type f
) and have f
somewhere in their name (-name '*f*'
). Next, sed
removes the file name, leaving just the directory name. Then, the list of directories is sorted (sort
) and duplicates removed (uniq
).
The sed
command consists of a single substitute. It looks for matches to the regular expression /[^/]+$
and replaces anything matching that with nothing. The dollar sign means the end of the line. [^/]+'
means one or more characters that are not slashes. Thus, /[^/]+$
means all characters from the final slash to the end of the line. In other words, this matches the file name at the end of the full path. Thus, the sed command removes the file name, leaving unchanged the name of directory that the file was in.
Simplifications
Many modern sort
commands support a -u
flag which makes uniq
unnecessary. For GNU sed:
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u
And, for MacOS sed:
find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u
Also, if your find
command supports it, it is possible to have find
print the directory names directly. This avoids the need for sed
:
find . -type f -name '*f*' -printf '%h\n' | sort -u
More robust version (Requires GNU tools)
The above versions will be confused by file names that include newlines. A more robust solution is to do the sorting on NUL-terminated strings:
find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
Best Answer
The pattern given to
-name
has to match the entire base filename. The behaviour of the-name
pattern is defined as:This means it's true when the whole of the basename matches the pattern you gave. You can think of a pattern as being basically like a shell glob: you can use
*
,?
, and[...]
patterns inside it, with the start and end of the pattern aligned with the start and end of the string.So your command:
finds files named "bookmarks" because that is the entire filename, but:
would only find files named 'bookmark', because there are no wildcard characters in the pattern.
To match files called both
bookmark
andbookmarks
, you could use:So if you want to find
you can use use:
to match files whose names have any number of characters, then
bookmark
, then any number of characters.