Testen, ob ein Dateideskriptor gültig ist

12

Ich möchte, dass ein Bash-Skript zusätzliche Informationen an Dateideskriptoren (FDs) ausgibt, die größer oder gleich 3 sind, wenn sie geöffnet sind. Um zu testen, ob ein FD geöffnet ist, habe ich den folgenden Trick entwickelt:

if (printf '' 1>&3) 2>&-; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

Dies ist ausreichend für meine Bedürfnisse, aber ich bin gespannt, ob es eine idiomatischere Möglichkeit gibt, zu testen, ob ein FD gültig ist. Ich bin besonders über interessiert , ob es existiert eine Zuordnung des fcntl(1)syscall zu einem Shell - Befehl, der dem Abrufen von FD - Flags erlauben würde ( O_WRONLYund O_RDWRzu testen , ob die FD beschreibbar ist, und O_RDONLYund O_RDWRzu testen , ob die FD lesbar sind).

Witiko
quelle

Antworten:

12

In ksh(sowohl AT & T- als auch pdksh-Varianten) oder zshkönnen Sie Folgendes tun:

if print -nu3; then
  echo fd 3 is writeable
fi

Sie werden nichts auf diese fd schreiben, aber trotzdem prüfen, ob die fd beschreibbar ist (mit fcntl(3, F_GETFL)) und ansonsten einen Fehler melden:

$ ksh -c 'print -nu3' 3< /dev/null
ksh: print: -u: 3: fd not open for writing

(zu dem Sie umleiten können /dev/null).

Mit bash, ich denke , die einzige Möglichkeit zu überprüfen, wenn ein dup()wie in Ihrem Ansatz erfolgreich ist , obwohl das nicht garantieren, dass die fd beschreibbar ist (oder rufen Sie ein externes Dienstprogramm ( zsh/ perl...) zu tun , die fcntl()).

Beachten Sie, dass in bash(wie bei den meisten Shells), wenn Sie (...)anstelle von verwenden {...;}, dies einen zusätzlichen Prozess auslöst. Sie können verwenden:

if { true >&3; } 2<> /dev/null

Vermeiden Sie stattdessen die Abzweigung (außer in der Bourne-Shell, in der das Umleiten zusammengesetzter Befehle immer eine Subshell verursacht). Verwenden Sie nicht :statt true, da dies eine spezielle integrierte Funktion ist. Dies würde dazu führen, dass die Shell beendet wird, wenn sich bash im POSIX-Konformitätsmodus befindet.

Sie können es jedoch verkürzen auf:

if { >&3; } 2<> /dev/null
Stéphane Chazelas
quelle
@mikeserve, re: deine Bearbeitung, womit ist das <>? Die Shell wird nicht von ihrem stderr lesen. Warum sollten Sie sie in read + write öffnen? Was meinst du mit dem, was mit intrinsic passiert ist? ?
Stéphane Chazelas
7

In der Beschreibung der Verwendung der POSIX- Anwendung finden Sie Folgendes:command

Die gelegentliche Unterdrückung der besonderen Eigenschaften spezieller Einbauten bietet einige Vorteile. Beispielsweise:

command exec > unwritable-file

bewirkt nicht, dass ein nicht interaktives Skript abgebrochen wird, sodass der Ausgabestatus vom Skript überprüft werden kann.

Deshalb können Sie einfach Folgendes tun:

if    command >&3
then  echo 3 is open >&3
else  ! echo 3 is not open
fi    2<>/dev/null

Oder...

{ command >&3
  printf %s\\n%.0d  string "0$(($??8:0))" >&"$(($??1:3))"
} 2<>/dev/null

Dadurch wird eine Zeichenfolge gefolgt von einer \nEwline entweder auf stdout oder 3 geschrieben, und es wird immer noch ein Exit-Status ungleich Null weitergegeben, wenn 3 nicht geöffnet ist, da die beim Aufwickeln durchgeführte Berechnung $?das Oktal 08 nicht in % decimal konvertiert, aber überhaupt nicht abgeschnitten wird das Oktal 00 .

Oder...

command exec >&3 || handle_it

Aber wenn Sie verwenden ksh93, können Sie einfach tun:

fds

Für eine Liste der offenen Dateideskriptoren. Fügen Sie hinzu, um -lzu sehen, wohin sie gehen.

mikeserv
quelle
3

Offene Dateideskriptoren finden Sie in /proc/<pid>/fd. Um beispielsweise die offenen Dateideskriptoren der aktuellen Shell aufzulisten, ls -l /proc/$$/fddie Sie ausgeben können , sollten Sie Folgendes erhalten:

total 0
lrwx------ 1 testuser testuser 64 jun  1 09:11 0 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 1 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 2 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:39 255 -> /dev/pts/3

Wenn Sie eine Datei öffnen mit:

touch /tmp/myfile
exec 7</tmp/myfile

Es sollte durch ein neues aufgeführt werden ls -l /proc/$$/fd:

lr-x------ 1 testuser testuser 64 jun  1 09:11 7 -> /tmp/myfile

Wenn Sie den Dateideskriptor erneut schließen, exec 7>&-wird er ebenfalls nicht /proc/$$/fdmehr aufgeführt.

Lambert
quelle
2
All dies ist ziemlich spezifisch für Linux. FWIW.
lcd047
1
Getestet unter Linux sowie unter Solaris (10 und 11). Der Unterschied besteht darin, dass Sie verwenden müssen, um pfiles <pid>zu sehen, welcher Dateideskriptor mit welcher Datei verbunden ist, während ls -ldie Verbindung unter Linux angezeigt wird.
Lambert
Ich mag die Kompaktheit von [ -e /proc/$$/fd/3 ], aber ich ziehe es vor, mich nicht auf procfs zu verlassen, da es in FreeBSD und möglicherweise auch in anderen Un * ces veraltet ist.
Witiko
1
Bringt mich zu der Alternative, zu verwenden pfiles <pid>oder lsof -p <pid>zu sehen, welche Dateideskriptoren geöffnet sind.
Lambert
1
/procexistiert überhaupt nicht unter OpenBSD. Unter FreeBSD und NetBSD muss es mountexplizit angegeben werden und darf /proc/<PID>kein Unterverzeichnis haben fd.
lcd047
3

Dein Trick sieht süß aus; aber für eine idiomatische Weise frage ich mich, warum Sie nicht verwendet haben:

if ( exec 1>&3 ) 2>&-
Janis
quelle
Dies ist in der Tat ein sauberer Weg.
Witiko
5
Dies erzeugt jedoch eine Unterschale, bei der es sich bei den meisten Schalen um das Verzweigen eines Prozesses handelt. Das garantiert nicht, dass der fd beschreibbar ist. Sie können verwenden { true >&3; } 2> /dev/null, um die Gabel zu vermeiden. Oder { command exec >&3; } 2> /dev/nullwenn Sie stdout dorthin umleiten möchten.
Stéphane Chazelas
@Stephane; Der von @Witiko erfundene Subshell-Trick bestand darin, die Dateideskriptoren der aktuellen Umgebung nicht zu beeinflussen, wenn eine Umleitung verwendet wird, um eine Umleitung zu erhalten. - Könnten Sie das von Ihnen erwähnte "beschreibbare fd" näher erläutern ?
Janis
2
{ true >&3; } 2> /dev/nullwirkt sich auch nicht auf die aktuelle Umgebung aus und teilt sich nicht (außer in der Bourne-Shell). Ich meine, das (exec 1>&3) 2>&-wird true für ein fd zurückgeben, das im schreibgeschützten Modus geöffnet ist.
Stéphane Chazelas
1
execWenn Sie ein spezielles integriertes Element sind, wird die Shell beendet, wenn dies fehlschlägt (für Bash nur im POSIX-Konformitätsmodus). command execverhindert das. trueist kein spezielles eingebautes. Beachten Sie dies execund command execwirken Sie sich auf die aktuelle Umgebung aus (deshalb habe ich gesagt, wenn Sie stdout dorthin umleiten möchten ).
Stéphane Chazelas
0

Das scheint super einfach zu sein (siehe Kommentare):

[ -r /proc/$$/fd/$FD ] && echo "File descriptor $FD is readable"
[ -w /proc/$$/fd/$FD ] && echo "File descriptor $FD is writable"

Als Extra ... Der Test [-r Datei] zeigt nicht an, ob tatsächlich Daten darauf warten, gelesen zu werden (/ dev / null besteht diesen Test (siehe Kommentare)).

[ -r /proc/$$/fd/4 ] \
  && [ read -t 0.0001 -N 0 <&4 ] \
  && echo "Data is waiting to be read from file descriptor 4"

Eine kleine Zahl für das Timeout-Argument (read -t) ist erforderlich, oder Daten, für die eine Berechnung erforderlich ist, werden möglicherweise übersehen. Der lesbare Test ([-r Datei]) ist erforderlich, oder der Lesebefehl wird bombardiert, wenn die Datei nicht lesbar ist. Dadurch werden keine Daten gelesen, da die Byteanzahl Null ist (read -N 0).

Paul
quelle
Wenn Sie von einem Linux-System ausgehen, können Sie sich auch einen Blick darauf werfen /proc/<pid>/fdinfo/<fd>, in dem alle Modi für geöffnete Dateien aufgeführt sind flags:- siehe hier . Warum Ihr 2. Teil (auch nach Behebung des grellen Fehlers): read -t .1 -N0 <&4wird nicht sagen, ob auf fd 4 Daten zu lesen sind: versuchen Sie es einfach mit 4</dev/null.
Mosvy
Und [ -r /proc/$$/fd/$FD ]sagt Ihnen natürlich nicht, ob der Dateideskriptor $FDlesbar ist, aber ob die Datei, aus der er geöffnet wurde , mit einem anderen Dateideskriptor zum Öffnen wieder geöffnet werden könnte :exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy
-1

Wenn Sie an einer Lösung mit geringer Gabelung interessiert sind, um sie wiederholt zu verwenden, würde ich diese Funktion vorschlagen:

checkfd () {
    exec 2> / dev / null
    wenn exec> & 3; dann
        exec 1> / dev / tty
        Echo "fd3 OK"
    sonst
        Echo "fd3 KO"
    fi
    exec 2> / dev / tty
}}

Und hier ist, was es mit einem produziert zsh:

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3 OK
$
Dan
quelle
In den meisten Muscheln exec >&3wird die Muschel getötet, wenn 3 nicht geöffnet ist.
Mikeserv
Zumindest arbeitet es an zshund bash. Könnten Sie die Shell bereitstellen, auf der der Fehler eine execverursacht hat exit?
Dan
Ja. In bashdo set -o posixund versuchen Sie es erneut. In zsh... ich denke, es geht darum, die env var POSIX_BUILTINSauf einen Wert ungleich Null zu setzen - aber ich vergesse es sofort. In jedem Fall zshhandelt es sich nicht um eine Shell, die versucht, die POSIX-Konformität zu gewährleisten, und daher definitiv nicht dem Standard entspricht. Beide Schalen vermeiden Kompatibilität, was manche für Bequemlichkeit halten.
Mikeserv
Es arbeitet auch an einer einfachen Bourne-Shell.
Dan
In Bash ist set -o posixein Versuch erfolgreich.
Dan