blog · git · desktop · images · contact
2011-08-26
Kürzlich hab’ ich die Empfehlung aufgeschnappt, zur Ausgabe von Fehlern
in Shellskripten „>/dev/stderr
“ statt „>&2
“ zu benutzen. Der Grund
war ganz einfach, dass es lesbarer ist – daran besteht auch wenig
Zweifel. So ganz wohl war mir dabei aber nicht, da ich nicht genau
wusste, wie /dev/stderr
eigentlich zustande kommt und ob es immer
verfügbar sind.
Es geht um diese drei symbolischen Links:
lrwxrwxrwx 1 root root 15 Aug 26 20:49 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Aug 26 20:49 /dev/stdout -> /proc/self/fd/1
lrwxrwxrwx 1 root root 15 Aug 26 20:49 /dev/stderr -> /proc/self/fd/2
Zuerst einmal ist /dev
heutzutage meistens als devtmpfs
gemountet.
Das heißt, nach dem Ausschalten des Rechners ist sein Inhalt weg. Das
lässt sich leicht nachvollziehen, indem man ein Live-System bootet, die
Partition mountet und einfach nachschaut. Bei meinem Arch sehe ich dann
in /dev
gerade einmal console
, null
und zero
. Sonst nichts.
Das ist relevant, da es bedeutet, dass meine Kandidaten bei jedem Boot neu erstellt werden müssen. Früher™, in der Zeit noch vor devfs hätten sie noch einfach während der Installation angelegt worden sein können und fertig.
Udev liegt heute natürlich nahe, wenn es um /dev
geht. Und
tatsächlich wird man auch fündig, denn der udevd
erstellt die
fraglichen Links bei seinem Start:
static void static_dev_create_links(struct udev *udev, DIR *dir)
{
/* ... */
static const struct stdlinks stdlinks[] = {
{ "core", "/proc/kcore" },
{ "fd", "/proc/self/fd" },
{ "stdin", "/proc/self/fd/0" },
{ "stdout", "/proc/self/fd/1" },
{ "stderr", "/proc/self/fd/2" },
};
/* ... */
}
Betonung liegt auf heute. Die Git-History verrät, dass das noch im Mai 2010 nicht so war. Da das noch gar nicht so lange her ist, hat mich interessiert, wie das denn vorher war.
Bei den Initscripts von Arch gibt es einen passenden Commit aus diesem
Zeitraum. Dort ist nur eine einfache cp
-Operation zu sehen, denn
die Links wurden lediglich kopiert – zu finden sind sie ursprünglich im
udev-Paket (vor Version 155). Davon kann man sich zum Beispiel bei der
Arch Rollback Machine überzeugen:
$ tar -tvf udev-151-3-i686.pkg.tar.gz | egrep 'std(in|out|err)'
lrwxrwxrwx root/root 0 2010-02-11 08:07 lib/udev/devices/stdin -> /proc/self/fd/0
lrwxrwxrwx root/root 0 2010-02-11 08:07 lib/udev/devices/stdout -> /proc/self/fd/1
lrwxrwxrwx root/root 0 2010-02-11 08:07 lib/udev/devices/stderr -> /proc/self/fd/2
Weiter wollte ich in der Geschichte dann auch nicht zurückgehen. ;) An der Stelle sei aber angemerkt, dass ich es erstaunlich und großartig finde, wie gut sich diese Umstände dank Git und großen Festplatten (bei der ARM) noch rekonstruieren lassen.
Da die Herkunft nun erschöpfend geklärt ist, bleibt die Frage nach der Verfügbarkeit. Im FHS 2.3 findet sich nichts in der Richtung, aber bei POSIX.1-2008:
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)
Die Links können also existieren. Sie müssen aber nicht. Ganz davon
abgesehen, dass sich sowieso nicht jeder an POSIX hält. FreeBSD 8 hat
sie beispielsweise und auch das leicht ranzige SuSE auf unseren
Uni-Rechnern (dort läuft immerhin noch Vim 6.4). Das kleine Embedded
Linux auf meiner FritzBox 7170 aber nicht. Nun kann man freilich
argumentieren, ob das geeignete Maßstäbe sind. So oder so zeigt es aber,
dass man nicht unbedingt blind davon ausgehen kann, dass /dev/stderr
existiert.
Und es gibt noch einen echten Unterschied bei der Ausführung. „>&2
“
nimmt sich File-Deskriptor 2 und schreibt dort hinein. „>/dev/stderr
“
hingegen öffnet die „Datei“ /dev/stderr
und benötigt daher auch im
Hintergrund ein vollständiges Filesystem-Lookup dieses Namens. Was
Syscalls angeht, sieht das (auf’s Wesentliche reduziert) so aus:
$ strace -tt dash -c 'echo hi >&2'
...
22:55:54.640636 close(1) = 0
22:55:54.641599 dup2(2, 1) = 1
22:55:54.641711 write(1, "hi\n", 3) = 3
...
Und beim Link:
$ strace -tt dash -c 'echo hi >/dev/stderr'
...
22:57:57.832187 open("/dev/stderr", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
22:57:57.832354 close(1) = 0
22:57:57.832462 dup2(3, 1) = 1
22:57:57.832519 close(3) = 0
22:57:57.832588 write(1, "hi\n", 3) = 3
...
Die Performance-Nachteile sollten heute nicht im messbaren Bereich
liegen und auch ein möglicher „Angriff“ (der Link /dev/stderr
könnte
auf etwas ganz anderes zeigen, aber Deskriptor 2 ist immer Deskriptor 2)
ist wohl vernachlässigbar. Interessant ist es aber trotzdem.