3

I'm having a problem where grep gets confused when the directory contains a file starting with dashes.

For example, I have a file named "------.js" . When I do something like grep somestring * I get the error:

grep: unrecognized option '------.js'
Usage: grep [OPTION]... PATTERN [FILE]...
Try 'grep --help' for more information.

This seems like the kind of question that would be asked all over the internet, but I can't find anything.

I can manually resolve the problem with something like
find . | while read f; do grep MYSTRING "$f"; done
but I'm wondering if there's a simpler / more robust solution.

I'm running Arch Linux.

6

As an addition to Romeo's answer, note that

grep pattern --whatever

is required by POSIX to look for pattern in the --whatever file. That's because no options should be recognised after non-option arguments (here pattern).

GNU grep in that instance is not POSIX compliant. It can be made compliant by passing the POSIXLY_CORRECT environment variable (with any value) into its environment.

That's the case of most GNU utilities and utilities using a GNU or compatible implementation of getopt()/getopt_long() to parse command-line arguments.

There are obvious exceptions like env, where env VAR=x grep --version gets you the version of grep, not env. Another notable exception is the GNU shell (bash) where neither the interpreter nor any of its builtins accept options after non-option arguments. Even its getopts cannot parse options the GNU way.

Anyway, POSIXLY_CORRECT won't save you if you do

grep -e pattern *.js

(there, pattern is not a non-option argument, it is passed as an argument to the -e option, so more options are allowed after that).

So it's always a good idea to mark the end of options with -- when you can't guarantee that what comes after won't start with a - (or + with some tools):

grep -e pattern -- *.js
grep -- pattern *.js

or use:

grep -e pattern ./*.js

(note that grep -- pattern * won't help you if there's a file called -, while grep pattern ./* would work. grep -e "$pattern" should be used instead of grep "$pattern" in case $pattern itself may start with -).

There was an attempt in the mid-90s to have bash be able to tell getopt() which arguments (typically the ones resulting from a glob expansion) were not to be treated as options (via a _<pid>_GNU_nonoption_argv_flags_ environment variable), but that was removed as it was causing more problems than it solved.

6

You can try the option of grep which tell "end of parameters"

grep -- <string> <filename>

This will inform grep to ignore next dashes as parameters and thread them as next elements in command line

  • 1
    That answer in embarrassingly obvious. I feel like a noob. – aggregate1166877 May 14 '17 at 3:15
3

Just to post a TLDR answer with the other obvious fix,

grep -e pattern ./*.js

This works for commands which don't feature the -- option; though it is rather broadly supported, not all commands handle it.

(Notice also the -e which similarly works to disambiguate a pattern which starts with a dash. You can of course also work around that with something like [-]pattern but that won't work with grep -F; and if the pattern is user-supplied, it's just better not to mess with it.)

After "(almost) always quote your variables," this is probably the second most common gotcha in shell programming - wildcard matches and user-supplied data can and will occasionally contain a leading dash, so you'd better be prepared.

  • This is all included in Stéphane's answer which of course I have upvoted; but I'm hoping this simplified exposition will help guide future visitors to quickly identify the beef in it. – tripleee May 14 '17 at 10:25
2

You solution with find,

find . | while read f; do grep MYSTRING "$f"; done

may be improved:

find . -maxdepth 1 -type f -exec grep -H "MYSTRING" {} +

There is nothing "not robust" about this.

It solves the issues with the dashes in the filename since find will invoke grep with the filename prepended with the path ./.

The other two solutions are to separate the options from the filename using -- on the command line (which will indicate to the command line parsing routine that there are no more options to parse), or to specify the absolute or relative path to the file.

  • You'd probably want -exec grep "MYSTRING" {} + or otherwise, as only one file is passed to grep, grep won't print the filename. Even with +, grep might end-up being called with one argument, so you could use the -H option if using GNU grep, or use -exec grep MYSTRING /dev/null {} + – Stéphane Chazelas May 14 '17 at 7:13
  • @StéphaneChazelas Thanks! Still early morning here... I meant to use +. – Kusalananda May 14 '17 at 7:14
  • 1
    The robustness is fine, but using find to work around a simple syntax issue is severe overkill, thermonuclear flyswatter style. – tripleee May 14 '17 at 9:56
  • @tripleee I'd agree with that. – Kusalananda May 14 '17 at 10:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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