7

How do I use GNU touch to update a file called -?

How do I use GNU cat to display a file called -?


I'm running:

% cat --version | head -n1
cat (GNU coreutils) 8.29
% touch --version | head -n1
touch (GNU coreutils) 8.29

Firstly, touch:

% touch -
% ls -l
total 0
% touch -- -
% ls -l -- -
ls: cannot access '-': No such file or directory

Ok, I'll give up on creating a file with touch. Let's create it with date instead:

% date > -
% ls -l -
-rw-r--r-- 1 ravi ravi 29 Sep  8 19:54 -
%

Now, let's try to cat it:

% cat -
% # I pressed ^D
% cat -- -
% # Same again - I pressed ^D

I know I can work around with:

% > -

and

% cat < -

But why don't these GNU utils support the convention that -- means that everything following is treated as a non-option?

How do I use these tools in the general case, for example I have a variable with the contents -?

Tom Hale
  • 30,455
  • @Barmar This is a more specific case where the filename is -. A lone - is not an option, so the issue is different from the issues in the questions that you have proposed as duplicates. – Kusalananda Sep 08 '18 at 14:04
  • @Kusalananda A lone - is going to be treated as an invalid option or a placeholder for stdin or stdout. In any case, the solution is the same. – Barmar Sep 08 '18 at 14:05
  • @Barmar Ok, but - is not an invalid option. – Kusalananda Sep 08 '18 at 14:10
  • @Kusalananda If it's not treated as a synonym for stdin/stdout (as touch and cat do), and it's not a filename, and it's not an actual option, what else could it be? But this is immaterial, my point is that you use the same solution to deal with any filename beginning with -, it doesn't matter why it doesn't work normally. – Barmar Sep 08 '18 at 14:14
  • Forget off the top of my head, but ti was covered in this "game" that teaches you escaping stuff, etc. Really like this series they have for learning a higher level of command line use... and I've been using Linux for 20 years... http://overthewire.org/wargames/bandit/bandit0.html – ivanivan Sep 08 '18 at 14:45
  • @Barmar It's not an invalid option. If the utility does not recognise it as a special file name operand (as cat) does, and if there is no file with the filename - in the current directory, then the utility should complain about "no such file or directory". I'm totally OK with the dup votes though. – Kusalananda Sep 08 '18 at 15:06
  • @Kusalananda touch doesn't complain no such file or directory, since it creates new files. – Barmar Sep 08 '18 at 15:07
  • @Barmar Correct, but GNU touch also recognise - as special, and I said that if the utility didn't do that, then it may complain, especially if it treats it as the name of a file to read from. Unrelated to that, I'm noticing that BSD sort -o - treats - as stdout, while GNU sort does not (while both implementations treat sort - as reading from stdin). Interesting. – Kusalananda Sep 08 '18 at 15:20

1 Answers1

16

Use an explicit path to the file:

touch ./-
cat ./-

GNU touch treats a file operand of - specially:

A FILE argument string of - is handled specially and causes touch to change the times of the file associated with standard output.

For cat, the POSIX standard specifies that a file operand - should be interpreted as a request to read from standard input.

The double-dash convention is still in effect, but it's not for signalling the end of arguments but the end of options. In neither of these cases would - be taken as an option (a lone - can not be an option) but as an operand ("file name argument").


Regarding your last question:

To protect the contents of a variable against being interpreted as a set of options when using it as

utility "$variable"

use

utility -- "$variable"

Note that if the utility is cat, sed, awk, paste, sort and possibly a few others (or GNU touch), and $variable is -, this will still cause the utility to do its special processing since, as said above, - is not an option. Instead, make provisions so that filenames, if they may start with or are equal to -, are preceded by a path, for example ./ for files in the current working directory.

A good habit is to use

for filename in ./*; do

rather than

for filename in *; do

for example.

Kusalananda
  • 333,661
  • It seems the way for absolute belts and braces would be to do cat "$(readlink -f "$file")" – Tom Hale Sep 08 '18 at 13:35
  • What about the touch case though? – Tom Hale Sep 08 '18 at 13:36
  • @TomHale no. readlink -f is not portable. What about the touch case? – Kusalananda Sep 08 '18 at 13:38
  • So [ "$file" = - ] && file=./- is needed before each invocation of GNU cat and touch for the truly paranoid? Any nicer ways? – Tom Hale Sep 08 '18 at 13:40
  • 4
    @TomHale If you're looping over files in a directory, make a habit of using for file in ./* or similar, instead of for file in *. – Kusalananda Sep 08 '18 at 13:43
  • @TomHale, Also "$(readlink -f -- "$file")" (here adding the missing --) strips trailing newline characters so wouldn't work for files that end in newline. Also note that with GNU readlink at least, readlink -f foo/bar where foo doesn't exist returns an empty output, exit status 1 and no error message. – Stéphane Chazelas Sep 10 '18 at 11:12