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?

Braiam
  • 66,947
  • 30
  • 177
  • 264
John Red
  • 859
  • 1
  • 7
  • 14
  • 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 http://mywiki.wooledge.org/glob – Arronical May 25 '16 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 '16 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 '16 at 16:55
  • Also this https://stackoverflow.com/q/9217185/3701431 – Sergiy Kolodyazhnyy Jun 07 '18 at 20:49

6 Answers6

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.

wjandrea
  • 14,109
  • 4
  • 48
  • 98
  • 1
    Is there any reason for the space between `*` and `.*`? – Hashim Aziz Sep 23 '18 at 00: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 '18 at 01: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? – Hashim Aziz Sep 23 '18 at 02: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 '18 at 02: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. – thomasrutter Mar 06 '19 at 01: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" ./*

anonymous2
  • 4,268
  • 7
  • 33
  • 61
  • For more information on grep, see `man grep`. – anonymous2 May 25 '16 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 '16 at 16:40
8

Old timers would probably do this:

find . -type f -print0 | xargs -0 grep "string"
Digital Trauma
  • 2,415
  • 15
  • 24
dathompson
  • 81
  • 1
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
Dragos Alexe
  • 121
  • 1
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.

muru
  • 193,181
  • 53
  • 473
  • 722
Criggie
  • 671
  • 5
  • 12
  • Wouldn't `grep forthis */*` search files both in the current directory and one directory down? – Hashim Aziz Sep 23 '18 at 00: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 '18 at 04:07
1

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

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