7

As an experiment, I tried running set -o noclobber and then running echo foo > /dev/null. I was able to do so; no errors were raised:

bash-3.2$ set -o noclobber
bash-3.2$ echo "foo" > /dev/null
bash-3.2$ echo "bar" > /dev/null
bash-3.2$ echo "baz" > /dev/null

However, when I tried the same thing with a file I had previously created, I saw the expected errors:

bash-3.2$ touch bar.txt
bash-3.2$ echo "bar" > bar.txt
bash: bar.txt: cannot overwrite existing file

Does noclobber treat the /dev/null file differently than it does other files?

NOTE: I understand why it might behave this way (i.e. because of the nature of /dev/null and what its purpose is), but I don't see any documentation to that effect here. I suppose it's possible that this is an undocumented feature, or (probably more likely) I just missed the documentation?

3
  • 4
    Section 3.6.2 says "the redirection will fail if the file whose name results from the expansion of word exists and is a regular file", but /dev/null and family are not regular files. IIRC they're "character special files"
    – muru
    Commented Oct 6, 2022 at 15:30
  • That makes sense. I did read that section when I was looking for my answer, and I also grep'ed for /dev/null on this page and didn't find it, so I assumed it was undocumented or something. In a perfect world, the doc's author(s) could add an example of what a non-"regular file", but you're right that this phrase would explain this behavior. Thanks! Commented Oct 6, 2022 at 15:38
  • 5
    Non-regular files are all files which are not regular files. "Non-regular" is not a standard term, files which are not regular files are called special files. Special files are directories (yes, directories are files), symbolic links, named pipes (aka FIFOs), sockets, character devices (e.g. mice), and block devices (e.g. disks, partitions). Commented Oct 6, 2022 at 18:24

1 Answer 1

12

Section 3.6.2 Redirecting Output says:

If the redirection operator is ‘>’, and the noclobber option to the set builtin has been enabled, the redirection will fail if the file whose name results from the expansion of word exists and is a regular file.

But /dev/null and family are not regular files. They're "character special files":

$ [ -c /dev/null ] ; echo $?
0
$ [ -f /dev/null ] ; echo $?
1

The same applies to other non-regular files, like FIFO pipes:

$ mkfifo foo; set -o noclobber
$ echo > .bashrc
bash: .bashrc: cannot overwrite existing file
$ cat foo & echo a > foo
[1] 18857
a

(And, presumably, sockets, block devices, etc.)

8
  • 6
    And actually, the fact that the shell needs to check the type of the file first means there's a TOCTOU race and noclobber is not safe in the face of an attacker replacing a symlink to /dev/null with a symlink to another file at the right moment for instance. Commented Oct 6, 2022 at 15:43
  • 2
    I suppose the shell could work around that by opening the file first, checking the type then, and bailing out if it got a regular file, without clobbering the file. If it got a file not found, it'd try again with O_CREAT|O_EXCL to create it. If the file was created in between, the latter call would fail unexpected, but it'd at least be harmless (and the shell could try again from the start to see if the created file was a regular file.)
    – ilkkachu
    Commented Oct 6, 2022 at 18:58
  • 2
    @StéphaneChazelas: IMHO the shell should not be used to implement a security boundary in the first place, but users are probably not going to care about what I think.
    – Kevin
    Commented Oct 7, 2022 at 1:38
  • 2
    Personally I don't think of noclobber as a security feature, but as a guardrail. Sure I can jump over the guardrail if I really want to or someone can push me over it, but those are not the situations this guardrail is meant to be guarding against.
    – muru
    Commented Oct 7, 2022 at 4:18
  • 2
    @ilkkachu: If you were doing this for security reasons, not as muru says just as a "guard rail", another approach is open(O_CREAT|O_WRONLY) (without O_TRUNC or O_APPEND). Then, if fstat tells you it was a regular file, bail out and close. You might or might not stat first as an early out, to perhaps avoid updating timestamps. In a related case if you wanted to truncate a regular file on after some check, you could open without truncation and then ftruncate(fd, 0) (POSIX.2001, man7.org/linux/man-pages/man2/ftruncate.2.html). Your way works, too, but has to handle races. Commented Oct 7, 2022 at 8:41

You must log in to answer this question.

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