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.

  • what is the exact command you used? try grep 'somestring' -- * the -- tells a command that no further options are used – Sundeep May 14 '17 at 02:52
  • 1
    the find version you tried would work because file names would be ./------.js – Sundeep May 14 '17 at 02:55
  • Sundeep - Lol yes, I'm aware, hence me asking for a more robust solution :p – aggregate1166877 May 14 '17 at 03: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 https://unix.stackexchange.com/questions/189251/how-to-read-dash-files and neardupe https://unix.stackexchange.com/questions/1519/how-do-i-delete-a-file-whose-name-begins-with-hyphen-a-k-a-dash-or-minus – dave_thompson_085 May 14 '17 at 04:41

5 Answers5

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

Romeo Ninov
  • 17,484
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.

tripleee
  • 7,699
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.

Kusalananda
  • 333,661
  • 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 07:13
  • @StéphaneChazelas Thanks! Still early morning here... I meant to use +. – Kusalananda May 14 '17 at 07: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 09:56
  • @tripleee I'd agree with that. – Kusalananda May 14 '17 at 10:04
0
find . -name '-*' -exec rename 's/[- ]//' {} \;
mtk
  • 27,530
  • 35
  • 94
  • 130
gbolcer
  • 101
  • 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. – Stéphane Chazelas Feb 04 '22 at 17:32