7

I wanted a command line command to search all shell scripts in the filesystem for a particular word, so I asked around at work and got the following solutions:

grep word `find / -name \*.sh 2>/dev/null`
find / -name "*.sh" 2>/dev/null | xargs grep word

However, I'm not that familiar with the command line, so both of these solutions seem opaque to me. I'd prefer to do something that looks like:

ls -r *.sh | cat | grep -H word

But it seems that you can't pipe filenames into cat (at least I think that's what the problem is).

What is the most legible solution? And secondly, what is the most efficient solution?

Edit: I needed to know which file the word was found in, so I could modify the script.

3 Answers 3

8

Edit: If you have GNU utilities, see Gilles' answer for a method using GNU grep's recursion abilities that is much simpler than the find approach. If you only want to display filenames, you'll still want to add the -l option as I describe below.


Use grep -l word to only print names of files containing a match.

If you want to find all files in the file system ending in .sh, starting at the root /, then find is the most appropriate tool.

The most portable and efficient recommendation is:

find / -type f -name '*.sh' -exec grep -l word {} + 2>/dev/null

This is about as readable as it gets, and is not hard to parse if you understand the semantics behind each of the components.

  • find /: run find starting at the file system root, /
  • -type f: only match regular files
  • -name '*.sh': ... and only match files whose names end in .sh
  • -exec ... {} +: run command specified in ... on matched files in groups, where {} is replaced by the file names in the group. The idea is to run the command on as many files at once as possible within the limits of the system (ARG_MAX). The efficiency of the {} + form comes from minimizing the number of times the ... command must be called by maximizing the number of files passed to each invocation of ....
  • grep -l word {}: where the {} is the same {} repeated from above and is replaced by file names. As previously explained, grep -l prints the names of files containing a match for word.
  • 2>/dev/null: hide error messages (technically, redirect standard error to the black hole that is /dev/null). This is for aesthetic and practical reasons, since running find on / will likely result in reams of "permission denied" messages you may not care about for files which you do not have permission to read and directories you do not have permission to traverse.

There are some problems with the suggestions you received and posted in your question. Both

grep word `find / -name \*.sh 2>/dev/null

and

find / -name "*.sh" 2>/dev/null | xargs grep word

fail on files with whitespace in their name. It's best to avoid putting filenames in command substitution altogether. The first one has the additional problem of potentially running into the ARG_MAX limit. The second one is close to what I suggest, but there is no good reason to use xargs here, not to mention that safe and correct usage of xargs requires sacrificing portability for some GNU-only options (find -print0 | xargs -0).

1
  • Thank you for the understandable and thorough explanation! Commented Apr 26, 2012 at 20:57
8

On non-embedded Linux, Cygwin or other system with GNU grep, on FreeBSD, on NetBSD and OSX:

grep -r --include='*.sh' word .

Do not parse the output of ls. And do not use command substitution on the output of find, as jw013 has explained.

1
  • 1
    Doh, I feel silly for forgetting about recursive grep.
    – jw013
    Commented Apr 27, 2012 at 3:04
2

The combination of grep and find is in many cases ack (betterthangrep.com):

ack [OPTION]... PATTERN [FILE]

For your example, consider using

ack --shell word /

Notes

ack

  • searches (by default) recursively, but
  • ignores (by default) directories from common version control systems, e.g. .git, .hg, .svn, ...
  • can easily narrow down your results by using filters for common file types (see below for distinct file name patterns)
  • has a grep-like syntax and the same/similar arguments like -i for "ignore case" etc.
  • may be called ack-grep on your system (on Debian based distros, if I remember correctly)

File name patterns

The option --shell is short for --type=shell and includes several file types: currently .sh .bash .csh .tcsh .ksh .zsh according to

ack --help-types

If you want only .sh files, you have to define (add) your own type sh and use this filter (--sh) like this:

ack word --type-add=sh=.sh --sh /

This sounds a bit complicated, but allows recursive search for .sh files below /. For a local search (without specifying the starting directory, e.g. \) it would be easier:

ack word *.sh
1
  • Thanks for showing me ack, never knew it existed, very handy for a dev!
    – legends2k
    Commented Jan 19, 2013 at 20:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .