Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I want to output all lines containing the word OK recursively from a directory. But there are a few extensions which I need to exclude from the result:

*~
*.map
*.js except *.debug.js

I tried:

grep -r --exclude={*~,*.map} "OK" /some/dir

Except that I don't know how to remove from the result all those non-debug .js files. Any help is much appreciated.

share|improve this question

4 Answers 4

up vote 5 down vote accepted

I would just pass that through a second grep to remove them:

grep -r --exclude={\*~,\*.map} "OK" bar/ | grep -vP '(?<!debug)\.js'

The -v reverses the match, printing lines that don't match the pattern and the -P enables Perl Compatible Regular Expressions which let us use negative lookbehinds. This particular regex, will match .js that is not prececeded by debug which means (since we are inverting the matches) that only those .js files will be printed.

However, as @QuestionOverflow pointed out int the comments, that could have the unintended side effect of filtering out lines that contain OK and js since the grep -v is applied to the entire output, not only the file name. To avoid that, just add a colon (that's what grep uses to separate file names from file contents):

grep -r --exclude={*~,*.map} "OK" bar/ | grep -vP '(?<!debug).js:'

That will still fail if your input line contains foo.js: or if your file name contains :. So, to be sure, use a different approach:

grep -Tr --exclude={*~,*.map} "OK" bar/ | grep -vP '(?<!debug).js\t'

The -T causes grep to print a tab between the file name and the file contents. So, if we simply add a \t to the end of the regex, it will only match against file names, and not the contents of the line.

Still, using find might make more sense regardless.

share|improve this answer
1  
Would I be inadvertently excluding lines in those files that I want, but containing both OK and .js on the same line? –  Question Overflow Apr 16 at 9:06
    
@QuestionOverflow ah, yes indeed, good catch. See updated answer. –  terdon Apr 16 at 9:20
    
Fantastic answer. Have to accept yours since I ask specifically for grep. Thanks. –  Question Overflow Apr 16 at 9:26
    
@QuestionOverflow you're very welcome. In general though, find is probably better for this kind of thing. Getting the right grep can be tricky as you pointed out :). –  terdon Apr 16 at 9:27
    
Your solutions fail if one has the failglob option set in the shell: bash: no match: --exclude=*~ You need to quote your GLOB pattern arguments to --exclude to hide them from shell expansion, e.g. --exclude={\*~,\*.map} –  IDAllen Apr 16 at 15:28

I'd use find to locate the files and pipe the result through xargs:

$ find . -type f \! -name "*~" \
                 \! -name "*.map" \
                 \! \( -name "*.js" -and \! -name "*.debug.js" \) \
         -print0 | xargs -0 grep "OK"

This searches for every file not matching "*~", "*.map" or "*.js but not *.debug.js".

Using find you can easily search for rather complex rules and this approach saves you from accidentally removing false positives as could happen with double grep.

share|improve this answer
    
Nice answer too :) –  Question Overflow Apr 16 at 9:27
3  
Yes, this is probably the best way, +1. You could also use -exec grep OK {} + instead of xargs and avoid an extra program. –  terdon Apr 16 at 9:33
    
No, the use of -exec will cause more programs to run, not fewer, since find will run one grep for every single file name found instead of bundling hundreds of file names into xargs that get passed all at once to grep. If you have many files to search, use the xargs solution, not the -exec solution. –  IDAllen Apr 16 at 15:45
2  
@IDAllen no, note that I suggested -exec + not -exec \;, that will run as few commands as possible, much like xargs. –  terdon Apr 16 at 17:20

With zsh you can do:

grep OK some/dir/**/^(*~|*.map|(^*debug).js)

Provided of course the argument list isn't too long, in which case you can always do:

printf '%s\0' some/dir/**/^(*~|*.map|(^*debug).js) | xargs -0 grep ok
share|improve this answer

If you don't mind seeing the output slightly out of order (if you do, you can sort it):

grep -r --exclude={*~,*.map,*.js} "OK" /some/dir **/*.debug.js

This requires that your shell supports ** for recursive globbing: zsh does out of the box, bash does after you run shopt -s globstar, ksh93 does after you run set -o globstar.

Without ** support in the shell, you can use two grep commands:

grep -r --exclude={*~,*.map,*.js} "OK" /some/dir
grep -r --include=*.debug.js "OK" /some/dir
share|improve this answer
    
My shell supports **, but there seem to be something wrong with the extra argument **/*.debug.js, causing grep to interpret OK as a directory. Have you tried running it? –  Question Overflow Apr 17 at 3:50
    
@QuestionOverflow My mistake, I'd swapped the order of the arguments. –  Gilles Apr 17 at 7:36

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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