56

I want to search for a string of text in all files in a directory (and not its subdirectories; I know the -r option does that, but that is not what I want).

  1. Running

    grep "string" /path/to/dir
    

    is supposed to be able to do this, I've read, but it gives me the error:

    grep: dir: Is a directory

  2. Next, I tried running grep on multiple files.

    grep "string" .bashrc .bash_aliases works perfectly.

    grep "string" .bash* works as intended too.

    grep "string" * gives me the errors:

    grep: data: Is a directory
    grep: Desktop: Is a directory
    grep: Documents: Is a directory
    grep: Downloads: Is a directory
    ...
    

Only the errors are printed, I don't get the matching lines. I tried using the -s option, but to no avail.

So, my questions:

  1. Why am I not being able to use grep on a directory, as in (1), when I should be able to? I've seen that done in plenty examples on the Internet.
    Edit: When I say "using grep on a directory", I mean "search in all the files in that directory excluding its subdirectories". I believe that this is what grep does when you pass a directory to it in place of a file. Am I incorrect?

  2. Please give me an explanation on the workings of grep that would explain the behavior of commands in (2).
    Edit: Let me be more specific. Why does using wildcards to specify multiple files to search in for work with .bash* and not with * or even ./*?

  3. How can I search all the files in a directory (and not its subdirectories) using grep?

4
  • Also you're relying on the shell expanding wildcards such as *, known as globbing. Globbing does not include filenames starting with a dot such as .bashrc as standard. You can set shell options so that it will include these files, but you can get yourself in a bit of a mess if you don't know what you're doing. A good guide to understanding globing can be found here mywiki.wooledge.org/glob
    – Arronical
    May 25, 2016 at 16:23
  • I dunno why, but I've always done globbing on hidden files, and it has always worked. I haven't change any setting or something. As I pointed out in (2), it works with grep "string" .bash* too.
    – John Red
    May 25, 2016 at 16:32
  • Sorry, my last example was incorrect. You can search in hidden files as well, and suppressing the "is a directory" because Linux technically sees directories as a different type of file. The command would be then: grep "string" * .* 2>/dev/null or grep -s "string" * .*
    – Terrance
    May 25, 2016 at 16:55
  • Also this stackoverflow.com/q/9217185/3701431 Jun 7, 2018 at 20:49

6 Answers 6

57

In Bash, a glob will not expand into hidden files, so if you want to search all the files in a directory, you need to specify hidden files .* and non-hidden *.

To avoid the "Is a directory" errors, you could use -d skip, but on my system I also get an error grep: .gvfs: Permission denied, so I suggest using -s, which hides all error messages.

So the command you are looking for is:

grep -s "string" * .*

If you are searching files in another dir:

grep -s "string" /path/to/dir/{*,.*}

Another option is to use the dotglob shell option, which will make a glob include hidden files.

shopt -s dotglob
grep -s "string" *

For files in another dir:

grep -s "string" /path/to/dir/*

† Someone mentioned that I shouldn't get this error. They may be right - I did some reading but couldn't make heads or tails of it myself.

5
  • 1
    Is there any reason for the space between * and .*? Sep 23, 2018 at 0:35
  • 2
    @Hashim Compare the output of echo * .* and echo *.* run in your home directory, and the difference should be obvious. Otherwise LMK and I'll explain it.
    – wjandrea
    Sep 23, 2018 at 1:03
  • Interesting, so echo * shows non-hidden files and folders, echo *.* shows non-hidden files, echo .* shows all files, and echo * .* shows all files and directories. But why the reason for the space between the two in the latter case? It feels messy to me. Is there not a way to combine the two to get the same results? Or otherwise is there a syntax explanation of why the two need to be separated here, or is * .* an exceptional case? Sep 23, 2018 at 2:00
  • 1
    @Hashim I'm not sure how you came to those conclusions, so let me explain. First, directories are files in this context. In globs, * represents all non-hidden files (i.e. filenames which don't start with a dot); .* represents all hidden files (i.e. filenames that do start with a dot); and *.* represents all non-hidden files which contain a dot. In echo * .*, the two globs must be separate because they are different globs: one for non-hidden, one for hidden. Though as I wrote in my answer, you can make * include hidden files by turning on the dotglob shell option.
    – wjandrea
    Sep 23, 2018 at 2:23
  • 1
    Using *.* is common on Windows (DOS) as a way to list all files but on *nix will only include files with a dot in them, so it doesn't make sense on *nix. Instead you use * to list all files except hidden files, and .* to list hidden files. Mar 6, 2019 at 1:05
12

You need the -d skip option added on.

  1. Grep is searching inside of files. You can search recursively, as you said, if you want to search files inside of a directory.

  2. By default, grep will read all files, and it detects the directories. Because by default you have not defined what to do with the directories with the -d option, it give error output.

  3. Searching just within the parent directory would be grep -d skip "string" ./*

2
  • For more information on grep, see man grep.
    – anonymous2
    May 25, 2016 at 16:14
  • (a) Please see the edit. (b) Using -d skip does not work; it's basically the same as -s; also, see the edit. (c) Nope, grep -d skip "string" ./* does not work either.
    – John Red
    May 25, 2016 at 16:40
8

Old timers would probably do this:

find . -type f -print0 | xargs -0 grep "string"
4
  • 3
    Why not find . -type f -exec grep string {} +?
    – wchargin
    May 26, 2016 at 1:07
  • 6
    You also want -maxdepth 1.
    – wchargin
    May 26, 2016 at 5:48
  • @wchargin: if you want the file name in the output when there's only one file I think you want find . -type f -maxdepth 1 -exec grep string /dev/null {} + Mar 14, 2019 at 6:27
  • 1
    @GregoryNisbet: Just pass -H to grep.
    – wchargin
    Mar 14, 2019 at 17:23
2

Rephrasing - you want to grep the files in one level of subdirectory, but not recurse though all sub-sub directories?

grep forthis  *  */*

Or if you don't want the files in the current directory

grep forthis  */*

Note this won't find directories starting with a dot.

grep forthis  .*/*    */*   

should do that job.

There's also -maxdepth and -mindepth restriction parameters available to the find command too.

2
  • Wouldn't grep forthis */* search files both in the current directory and one directory down? Sep 23, 2018 at 0:37
  • @Hashim nope mostly - cos */* only matches things with one slash. If you had a file named a/b in the current directory then `*/* would match that.
    – Criggie
    Sep 23, 2018 at 4:07
2

You can think like this, for example using grep.

grep -l PATH ~/.[^.]*

So this search for string "PATH" listing name of the files below the user's home directory, only for files that start with a dot .

/root/.bash_history
/root/.bash_profile
grep: /root/.cache: Is a directory
grep: /root/.config: Is a directory
grep: /root/.dbus: Is a directory

Using grep PATH ~/.[^.]* you'll see all occurrence, including line with searching keyword.

/root/.bash_history:echo $PATH
/root/.bash_history:echo $PATH
/root/.bash_history:PATH=$PATH:~/bin
/root/.bash_history:echo $PATH
/root/.bash_profile:PATH=$PATH:$HOME/bin
/root/.bash_profile:export PATH
grep: /root/.cache: Is a directory
grep: /root/.config: Is a directory
grep: /root/.dbus: Is a directory

To get rid of error redirect to /dev/null for example

grep PATH ~/.[^.]* 2>/dev/null

/root/.bash_history:echo $PATH
/root/.bash_history:echo $PATH
/root/.bash_history:PATH=$PATH:~/bin
/root/.bash_history:echo $PATH
/root/.bash_profile:PATH=$PATH:$HOME/bin
/root/.bash_profile:export PATH

So you can apply this pattern for searching "Apache" string in files from /etc directory-looking only in files below this main directory. You see that this don't return from /etc/httpd/conf/httpd.conf

grep Apache /etc/[^.]* 2>/dev/null
/etc/passwd:apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
/etc/services:derby-repli     4851/tcp                # Apache Derby Replication
/etc/services:derby-repli     4851/udp                # Apache Derby Replication
1

Here is an example to properly skip directories without hiding all errors (without -s param on chosen answer):

grep --directories='skip' 'searchString' *

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.