If I have a file which name starting with single or several minus sign, for example --1 it can’t be used as a parameter of many commands. Even if I run
cat --1
instead of file content I get unrecognised option error message:
cat: unrecognized option '--1'
Same effect appears when I type
cat "--1" cat '--1' cat --1
nothing works. Files starting with ‘-‘ is not illegal in file system, but it is quite hard to work with them in cli and scripts. OK, in case of cat or grep I can use
cat <--1
and this will work, but
rm --1
won’t work either and it is quite hard to substitute this command with something else. Very uncomfortable after all. Is there any universal workaround different from not using such file names?
BTW, if name file is single - and all most of commands will understand it as stdin, wouldn’t they?
And it would be hard not to use such file names since I can’t rename them all in script using mv command either.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
The convention is that everything that starts with a - is an option. This collides with filenames that start with -. To work around this most commands recognize -- as an end-of-options sentinel. Everything after the -- will not be recognized as options, instead will be taken literally as filenames.
For example
cat -- --1
or
rm -rf -- --1
Another method is to qualify the filename with a path.
for example
cat ./--1
or even
cat /home/username/foo/--1
Best is to never create filenames that start with -. Filenames may contain all kinds of characters, including newline, but only because it is possbile doesn’t mean you have to do it.
To answer your question: Why cat, grep and other commands can’t understand files starting with minus sign? Because the command line is just a string. The shell does some filename expansion and some word splitting and some variable replacement but in the end the program receives an array of strings.
How do you separate the strings which mean options from the strings which mean filenames? By some sentinel characters. The convention in the unix/linux world is to use the - as the sentinel character. Everything that starts with a - is an option. Well, almost everything. Arguments to options might also start with -, but that is detail of the individual program.
Not every program conforms to this convention. Arguably the most popular example is dd:
dd if=/dev/random of=whatfreespace
dd recognizes filenames because they appear after the = sign.
Most, if not all, GNU programs conform to the GNU getopt convention: short options start with - and are only one character, long options start with -- and are at least two characters. -- on its own marks the end of options and all strings after that mark are not recognized as options. For more details read Program Argument Syntax Conventions in the GNU libc manual.
One example of a program that only partially conforms to the GNU convention is find:
find -not -name porn -delete
While find uses - to differ options from non options, it does not differ long options from short options. It does recognize -- as end of options. Every program can define it’s own way how to recognize options and filenames and whatever else.
The convention to interpret a single - as stdin is also just that, a convention of how to interpret the single - character when it is encountered in the array of strings.
Method 2
Anything starting with a dash is considered an option. You can prepend path to the filename to make it not look like an option:
cat ./--filename
Method 3
Using cat -- --filename indicates to stop looking for any option. There is a potential problems with this approach since third party applications do not know how to handle dashed file names. However POSIX binaries like, cp, rm, cat etc know how to handle these dashes
Method 4
More generally, it’s:
cat $options_and_or_file_patterns_or_stdin_if_dash cat "$option_or_file_or_stdin_if_dash" cat -- "$file_or_stdin_if_dash" cat < "$file" cat - < "$file" # same as above
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0