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' *

You must log in to answer this question.

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