8

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.

4
  • what is the exact command you used? try grep 'somestring' -- * the -- tells a command that no further options are used
    – Sundeep
    Commented May 14, 2017 at 2:52
  • 1
    the find version you tried would work because file names would be ./------.js
    – Sundeep
    Commented May 14, 2017 at 2:55
  • Sundeep - Lol yes, I'm aware, hence me asking for a more robust solution :p Commented May 14, 2017 at 3:15
  • You can do the find (which unlike the glob * includes any subdirectories and/or hidden files) more simply as find . -exec grep somestring + or you can just do grep somestring ./* -- also dupe unix.stackexchange.com/questions/189251/how-to-read-dash-files and neardupe unix.stackexchange.com/questions/1519/… Commented May 14, 2017 at 4:41

5 Answers 5

11

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.

7

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
  • 1
    That answer in embarrassingly obvious. I feel like a noob. Commented May 14, 2017 at 3:15
4

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.

1
  • 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
    Commented May 14, 2017 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.

4
  • 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 {} + Commented May 14, 2017 at 7:13
  • @StéphaneChazelas Thanks! Still early morning here... I meant to use +.
    – Kusalananda
    Commented May 14, 2017 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
    Commented May 14, 2017 at 9:56
  • @tripleee I'd agree with that.
    – Kusalananda
    Commented May 14, 2017 at 10:04
0
find . -name '-*' -exec rename 's/[- ]//' {} \;
1
  • 1
    It seems to be trying to show how to remove the - from the start of file names, but it has several issues: -name '-*' may miss filenames not included in the locale's charset (2) it finds files whose name starts with with - but tries to remove the first - or space in the full path. So for instance, it would rename ./foo-bar/-baz to ./foobar/-baz (3) it removes only one -. So would rename ./--foo to ./-foo (4) it calls one rename per file. If your rename supports -d, you could do LC_ALL find . -name '-*' -exec rename -d 's/^-+//' {} + instead which addresses all those. Commented Feb 4, 2022 at 17:32

You must log in to answer this question.

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