78

Occasionally I need to specify a "path-equivalent" of one of the standard IO streams (stdin, stdout, stderr). Since 99% of the time I work with Linux, I just prepend /dev/ to get /dev/stdin, etc., and this "seems to do the right thing". But, for one thing, I've always been uneasy about such a rationale (because, of course, "it seems to work" until it doesn't). Furthermore, I have no good sense for how portable this maneuver is.

So I have a few questions:

  1. In the context of Linux, is it safe (yes/no) to equate stdin, stdout, and stderr with /dev/stdin, /dev/stdout, and /dev/stderr?

  2. More generally, is this equivalence "adequately portable"?

I could not find any POSIX references.

2

6 Answers 6

47

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or command line parameter, not as explicit argument to e.g. open()). That said, it should be available on most commercial Unix systems, one way or another (sometimes it's spelled /dev/fd/N for various integers N, but most systems with that will provide symlinks as Linux and *BSD do).

4
  • 16
    Indeed, /dev/std{in,out,err} are specifically listed as not part of the POSIX.1-2008 standard.
    – jw013
    Commented Apr 13, 2012 at 23:05
  • 2
    It appears that ash doesn't support /dev/stdout in initrd (git.razvi.ro/…) Commented Feb 13, 2016 at 5:48
  • @CMCDragonkai: That's not a /dev/stdout that can be handled by the shell, and what did you expect of initrd? It's missing most nicities in order to make it as small as practical.
    – Joshua
    Commented Nov 1, 2016 at 16:04
  • 2
    In 2021 still explicitly listed as not part of POSIX in "P1003.1™-202x, Draft 2.1, August 2021" (the current POSIX draft). Commented Nov 29, 2021 at 9:44
27

the /dev/std{in,out,err} files are normally just symlinks to /proc/self/fd/{0,1,2} (respectively). As such theres nothing gained over using methods that are POSIX defined.

If you want to be POSIX compliant, the best way to do this is to use output redirection. Shell output redirection is defined in the POSIX standard. Additionally the STDIN, STDOUT, STDERR file descriptor numbers are also part of POSIX.
In short, things like >&2 are guaranteed to work.

One important thing to note though is that usage of STDIN, STDOUT, and STDERR is subjective to how the program was started. If the program was started with file descriptor 1 being an open handle to a file, then your program just has to accept it. Even if you were to have the program open up /dev/stdout, all it would do is open up file descriptor 1 which is still going to point to that file.
If this is what youre trying to get around, you need to open the TTY directly. Normally, without any redirection going on, STDIN, STDOUT, and STDERR are all just open file descriptors pointing to the same TTY. There is absolutely nothing more to it than that.

4
  • 7
    Can you clarify if /proc/self/fd/1 or /dev/fd/1 are part of POSIX?
    – Zombo
    Commented Jun 15, 2014 at 2:32
  • /dev/std??? are only symlinks to /proc/self/fd on Linux. Commented Jan 26, 2017 at 12:36
  • The fact that they are symlinks, may introduce strange bugs when used in conjunction with >> redirection, see my answer.
    – mvds
    Commented Jun 4 at 19:44
  • This is wrong, you don't get file descriptor 1 on opening /dev/stdout, see my updated answer.
    – mvds
    Commented Jul 3 at 23:44
7

/dev/{stdout,stdin,stderr} work in Bash on these platforms:

Linux debian-ppc 3.16.0-4-powerpc #1 Debian 3.16.7-ckt25-1 (2016-03-06) ppc GNU/Linux
HP-UX hpux-ia6 B.11.31 U ia64 0107668277 unlimited-user license
AIX aix7 1 7 000ACFDE4C00
FreeBSD freebsd.polarhome.com 10.0-RELEASE-p7 FreeBSD 10.0-RELEASE-p7 #0: Tue Jul  8 06:37:44 UTC 2014     [email protected]:/usr/obj/usr/src/sys/GENERIC  amd64
HP-UX hpux64 B.11.11 U 9000/785 2000587908 unlimited-user license
Darwin macosx 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:26:45 PDT 2012; root:xnu-1699.32.7~1/RELEASE_I386 i386
GNU hurd 0.7 GNU-Mach 1.6-486/Hurd-0.7 i686-AT386 GNU
Linux mandriva.polarhome.com 2.6.33.7-desktop-2mnb #1 SMP Mon Sep 20 18:19:20 UTC 2010 x86_64 x86_64 x86_64 GNU/Linux
SunOS openindiana 5.11 oi_148 i86pc i386 i86pc
MirBSD miros.polarhome.com 10 Kv#10uAF-20110818 GENERIC#1330 i386
Linux pidora 3.12.23-2.20140626git25673c3.rpfr20.armv6hl.bcm2708 #1 PREEMPT Fri Jul 4 16:06:10 EDT 2014 armv6l armv6l armv6l GNU/Linux
QNX qnx 6.5.0 2010/07/09-14:44:03EDT x86pc x86
NetBSD netbsd.polarhome.com 6.1.3 NetBSD 6.1.3 (GENERIC) i386
OpenBSD openbsd.polarhome.com 4.9 GENERIC#671 i386
Linux raspbian 3.18.7+ #755 PREEMPT Thu Feb 12 17:14:31 GMT 2015 armv6l GNU/Linux
SCO_SV scosysv 5 6.0.0 i386
Linux redhat.polarhome.com 3.17.4-301.fc21.x86_64 #1 SMP Thu Nov 27 19:09:10 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
SunOS solaris-x86 5.11 11.3 i86pc i386 i86pc
Linux suse 3.4.63-2.44-desktop #1 SMP PREEMPT Wed Oct 2 11:18:32 UTC 2013 (d91a619) x86_64 x86_64 x86_64 GNU/Linux
SunOS solaris 5.10 Generic_147147-26 sun4u sparc SUNW,Sun-Fire-V210
Linux ubuntu 3.13.0-85-generic #129-Ubuntu SMP Thu Mar 17 20:50:15 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
UnixWare unixware 5 7.1.4 i386 x86at SCO UNIX_SVR5
OSF1 tru64.polarhome.com V5.1 2650 alpha
Linux debian 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u2 (2016-01-02) x86_64 GNU/Linux

But fails in csh on these:

HP-UX hpux-ia6 B.11.31 U ia64 0107668277 unlimited-user license
Linux centos.polarhome.com 2.6.18-409.el5 #1 SMP Tue Mar 15 18:13:50 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux
HP-UX hpux64 B.11.11 U 9000/785 2000587908 unlimited-user license
AIX aix7 1 7 000ACFDE4C00
SCO_SV scosysv 5 6.0.0 i386
SunOS solaris-x86 5.11 11.3 i86pc i386 i86pc
SunOS openindiana 5.11 oi_148 i86pc i386 i86pc
SunOS solaris 5.10 Generic_147147-26 sun4u sparc SUNW,Sun-Fire-V210
UnixWare unixware 5 7.1.4 i386 x86at SCO UNIX_SVR5
OSF1 tru64.polarhome.com V5.1 2650 alpha
1
  • 10
    What was your test case? bash is special as it can be compiled to handle /dev/fd/x by itself for redirections on systems that don't have /dev/fd Commented Apr 22, 2016 at 16:14
7

POSIX 7 says they are extensions.

Base Definitions, Section 2.1.1 Requirements:

The system may provide non-standard extensions. These are features not required by POSIX.1-2008 and may include, but are not limited to:

[...]

  • Additional character special files with special properties (for example,  /dev/stdin, /dev/stdout,  and  /dev/stderr)

Found by grepping the POSIX HTML: Where is the list of the POSIX C API functions?

Also quite weirdly, the uuencode tool gives /dev/stdout a magic effect:

Specifying a decode_pathname operand of /dev/stdout shall indicate that uudecode is to use standard output.

The Linux kernel documentation says all systems should have it.

https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.rst

Compulsory links
These links should exist on all systems:
/dev/fd       /proc/self/fd   symbolic   File descriptors
/dev/stdin    fd/0            symbolic   stdin file descriptor
/dev/stdout   fd/1            symbolic   stdout file descriptor
/dev/stderr   fd/2            symbolic   stderr file descriptor

I could not, however, find where those symlinks are created in the kernel (distro provided?).

3

One issue with /dev/stdout and friends is that you may not have permission to write to them in certain circumstances. For example, I've encountered this when invoking scripts from Nix, and I imagine similar tools which run scripts in jails/sandboxes/containers/VMs/etc. may encounter similar issues.

Using syntax like 1>&2 worked in these cases and, since I knew I'd be running in Bash, I could use process substitution for commands which expect filenames.

1
  • This may be due to the fact that /dev/stdout is a symlink to /proc/self/fd/1 which is a symlink to some output file, that is not writable after dropping privileges or chrooting perhaps. See my answer to this question about different aspects of these symlinks.
    – mvds
    Commented Jul 3 at 23:40
-2

Actually it is a bit tricky how /dev/stdout works in practice, on Linux at least. At first glance I thought /dev/stdout would be a drop-in replacement for -, for utilities not supporting - as a way to specify stdout. However, these are not identical:

for i in $(seq 10)
do
    echo test | dd of=/dev/stdout >> /tmp/ten_times_dev_stdout
    echo test | dd of=/dev/stdout | cat >> /tmp/ten_times_dev_stdout_cat
    echo test | dd >> /tmp/ten_times_stdout
done
wc -l /tmp/ten_times_*

As it outputs (ignoring the dd reporting)

 1 /tmp/ten_times_dev_stdout
10 /tmp/ten_times_dev_stdout_cat
10 /tmp/ten_times_stdout

Where one would naively expect to see the number 10 three times. So it breaks >> in a very obscure way, but it can be fixed by piping through cat.

(As observed on Linux systems, e.g. using GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu) and dd (GNU coreutils) 8.32 on Linux 5.15.0-102-generic #112-Ubuntu SMP Tue Mar 5 16:50:32 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux, and also using zsh and tcsh, but not on MacOS using GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin23) and Darwin 23.3.0 Darwin Kernel Version 23.3.0)

update

This boils down to the system call open("/dev/stdout",O_TRUNC) where the O_TRUNC asks to truncate /dev/stdout, which is a symlink to /proc/self/fd/1, which is a symlink to the file the output is redirected to. This is due to a call to dup2() by the shell. These magic symlinks seem nice but introduce strange bugs.

update 2

For clarity, on MacOS the above outputs:

10 /tmp/ten_times_dev_stdout
10 /tmp/ten_times_dev_stdout_cat
10 /tmp/ten_times_stdout

As expected for a shell command explicitly asking to append using >>.

Some more proof to convince downvoters of this strange behavior:

cat << EOF > do_nothing.c
#include <fcntl.h>
int main() { }
EOF

cat << EOF > magic_truncate.c
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
        char linkbuf[256];
        ssize_t len = readlink("/dev/stdout",linkbuf,sizeof(linkbuf));
        if ( len >= 0 ) {
                linkbuf[len] = 0;
                fprintf(stderr,"opening /dev/stdout -> %s",linkbuf);
        }
        len = readlink(linkbuf,linkbuf,sizeof(linkbuf));
        if ( len >= 0 ) {
                linkbuf[len] = 0;
                fprintf(stderr," -> %s",linkbuf);
        }
        int fd = open("/dev/stdout",O_TRUNC|O_WRONLY);
        fprintf(stderr,", fd=%d\n",fd);
}
EOF

gcc do_nothing.c -o do_nothing
gcc magic_truncate.c -o magic_truncate

echo test > /tmp/test
wc -l /tmp/test
./do_nothing >> /tmp/test
wc -l /tmp/test
./magic_truncate >> /tmp/test
wc -l /tmp/test

This outputs:

1 /tmp/test
1 /tmp/test
opening /dev/stdout -> /proc/self/fd/1 -> /tmp/test, fd=3
0 /tmp/test

on Linux, and:

1 /tmp/test
1 /tmp/test
opening /dev/stdout -> fd/1, fd=3
1 /tmp/test

on MacOS. Please just try it before downvoting, and consider that it is highly unexpected for >> to truncate a file, probably in violation of POSIX standards. This is due to the way /dev/stdout and /proc/self/fd/1 are implemented on Linux, using symlinks rather than as a stream that can be opened like a file. I suspect that other issues could arise from this symlink based implementation, e.g. considering file permissions in conjunction with processes that drop their privileges.

2
  • Your behavior has nothing to do with symlinks, /dev/stdout, or anything of the sort. It's instead due to the behavior of dd. Your 1st and 3rd examples give dd a file handle that directly references the file. When using the of= option, dd performs a truncation. Your 2nd example dd is truncating the pipe between it and cat (which effectively does nothing), not the file itself. Sorry, but I do have to -1, because the behavior of an arbitrary program is unrelated to the original question, as well as incorrectly describing the behavior. P.S: dd has a notrunc option for this.
    – phemmer
    Commented Jun 5 at 0:32
  • @Phemmer I just used dd as a simple tool to serve as an example for any tool that can take an output filename as an argument. The behavior is also seen with any other tool - but some (portable) tool is needed to provide examples of non-portability. To clarify: the point I'm making is that /dev/stdout and >> don't work well together on Linux, rendering /dev/stdout not portable. For testing this I actually compiled the simplest C code in order to eliminate all other options. I added some code to the answer, please see for yourself and check different OSes. The code is not portable; why?
    – mvds
    Commented Jul 3 at 23:36

You must log in to answer this question.

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