Tester si un descripteur de fichier est valide

12

Je voudrais faire en sorte qu'un script bash produise des informations supplémentaires aux descripteurs de fichiers (FD) supérieurs ou égaux à 3, lorsqu'ils sont ouverts. Pour tester si un FD est ouvert, j'ai imaginé l'astuce suivante:

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

C'est suffisant pour mes besoins, mais je suis curieux de savoir s'il existe un moyen plus idiomatique de tester si un FD est valide. Je suis particulièrement intéressé à savoir s'il existe un mappage de l' fcntl(1)appel système à une commande shell, qui permettrait la récupération des drapeaux FD ( O_WRONLYet O_RDWRpour tester si le FD est accessible en écriture et O_RDONLYet O_RDWRpour tester si le FD est lisible).

Witiko
la source

Réponses:

12

Dans ksh(les variantes AT&T et pdksh) ou zsh, vous pouvez faire:

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

Ils n'écriront rien sur ce fd, mais vérifient toujours si le fd est accessible en écriture (en utilisant fcntl(3, F_GETFL)) et signalent une erreur sinon:

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

(vers lequel vous pouvez rediriger /dev/null).

Avec bash, je pense que votre seule option est de vérifier si un dup()réussit comme dans votre approche, bien que cela ne garantisse pas que le fd est accessible en écriture (ou appeler un utilitaire externe ( zsh/ perl...) pour le faire fcntl()).

Notez que dans bash(comme la plupart des shells), si vous utilisez à la (...)place de {...;}, cela entraînera un processus supplémentaire. Vous pouvez utiliser:

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

à la place pour éviter le fork (sauf dans le shell Bourne où la redirection des commandes composées provoque toujours un sous-shell). Ne pas utiliser à la :place de truecar il s'agit d'une fonction intégrée spéciale , ce qui entraînerait la fermeture du shell lorsque bash est en mode de conformité POSIX.

Vous pouvez cependant le raccourcir pour:

if { >&3; } 2<> /dev/null
Stéphane Chazelas
la source
@mikeserve, re: votre édition, qu'est-ce que c'est <>? Le shell ne va pas lire depuis son stderr, pourquoi voudriez-vous l'ouvrir en lecture + écriture? Que voulez-vous dire par ce qui est arrivé à intrinsèque? ?
Stéphane Chazelas
7

Dans la description de l' utilisation de l'application POSIX, vous trouverez les éléments suivants:command

Il existe certains avantages à supprimer occasionnellement les caractéristiques spéciales des modules intégrés spéciaux. Par exemple:

command exec > unwritable-file

n'entraîne pas l'interruption d'un script non interactif, de sorte que l'état de sortie peut être vérifié par le script.

C'est pourquoi vous pouvez simplement faire:

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

Ou...

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

Qui écrira une chaîne suivie d'une ligne \nélectronique soit sur stdout soit sur 3 et transmettra toujours un état de sortie non nul lorsque 3 n'est pas ouvert car les calculs effectués à la $?fin échouent à convertir l'octal 08 en % décimal mais tronqué à rien du tout l'octal 00 .

Ou...

command exec >&3 || handle_it

Mais si vous utilisez ksh93, vous pouvez simplement faire:

fds

Pour une liste des descripteurs de fichiers ouverts. Ajoutez -lpour voir où ils vont.

mikeserv
la source
3

Les descripteurs de fichiers ouverts se trouvent dans /proc/<pid>/fd. Pour répertorier, par exemple, les descripteurs de fichiers ouverts du shell actuel, vous pouvez émettre ls -l /proc/$$/fdqui devrait vous donner quelque chose comme:

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

Lorsque vous ouvrez un fichier à l'aide de:

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

Il doit être répertorié par un nouveau ls -l /proc/$$/fd:

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

Si vous fermez à nouveau le descripteur de fichier à l'aide, exec 7>&-il n'est plus répertorié dans /proc/$$/fd.

Lambert
la source
2
Tout cela est assez spécifique à Linux. FWIW.
lcd047
1
Testé sur Linux ainsi que sur Solaris (10 et 11). La différence est que vous devez utiliser pfiles <pid>pour voir quel descripteur de fichier est connecté à quel fichier tout en ls -laffichant la connexion sous Linux.
Lambert
J'aime la compacité de [ -e /proc/$$/fd/3 ], mais je préfère ne pas me fier à procfs, car il est déprécié dans FreeBSD et peut-être aussi dans d'autres unités.
Witiko
1
Cela m'amène à utiliser pfiles <pid>ou lsof -p <pid>à voir quels descripteurs de fichiers sont ouverts.
Lambert
1
/procn'existe pas du tout sur OpenBSD. Sur FreeBSD et NetBSD, il doit être mountexplicitement supprimé et /proc/<PID>ne pas avoir de sous-répertoire fd.
lcd047
3

Votre astuce est mignonne; mais pour une manière idiomatique, je me demande pourquoi vous n'avez pas utilisé:

if ( exec 1>&3 ) 2>&-
Janis
la source
C'est, en effet, une façon plus propre.
Witiko
5
Cela crée cependant un sous-shell qui est la plupart des shells signifie forking un processus. Cela ne garantit pas que le fd est accessible en écriture. Vous pouvez utiliser { true >&3; } 2> /dev/nullpour éviter la fourche. Ou { command exec >&3; } 2> /dev/nullsi vous souhaitez rediriger stdout vers celui-ci.
Stéphane Chazelas
@Stephane; L'astuce de sous-shell que @Witiko a inventée était de ne pas affecter les descripteurs de fichiers de l'environnement actuel lors de l'utilisation d'une redirection pour obtenir une redirection. - Pourriez-vous élaborer sur le "fd inscriptible" que vous mentionnez?
Janis
2
{ true >&3; } 2> /dev/nulln'affectera pas l'environnement actuel non plus et ne se bifurquera pas (sauf dans le shell Bourne). Je veux dire que (exec 1>&3) 2>&-cela retournera vrai pour un fd ouvert en mode lecture seule.
Stéphane Chazelas
1
execêtre une fonction intégrée spéciale quittera le shell en cas d'échec (pour bash, uniquement en mode de conformité POSIX). command execempêche cela. truen'est pas une fonction spéciale. Notez cela execet command execaffectez l'environnement actuel (c'est pourquoi j'ai dit si vous voulez rediriger stdout vers lui ).
Stéphane Chazelas
-1

Si vous êtes intéressé par une solution à faible fourche afin de l'utiliser de manière répétée, je suggère cette fonction:

checkfd () {
    exec 2> / dev / null
    si exec> & 3; puis
        exec 1> / dev / tty
        écho "fd3 OK"
    autre
        écho "fd3 KO"
    Fi
    exec 2> / dev / tty
}

Et voici ce qu'il produit avec un zsh:

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3 OK
$
dan
la source
Dans la plupart des obus exec >&3tuera l'obus lorsque 3 n'est pas ouvert.
mikeserv
Au moins, il travaille sur zshet bash. Pourriez-vous fournir le shell sur lequel l'échec a execprovoqué un exit?
dan
Ouais. À bashfaire set -o posixet réessayer. Dans zsh... je pense qu'il s'agit de régler la variable env POSIX_BUILTINSsur une valeur non nulle - mais j'oublie tout de suite. Dans tous les cas, ce zshn'est pas un shell qui tente de se conformer à POSIX, et il est donc définitivement non standard. Ces deux coques évitent la compatibilité pour ce que certains croient être pratique.
mikeserv
Il travaille également sur le shell Bourne ordinaire.
dan
En bash, avec set -o posixun essai réussi.
dan
-1

Cela semble super facile (voir commentaires):

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

En plus ... Le test [-r file] n'indique pas si des données sont en attente de lecture (/ dev / null passe ce test (voir commentaires)).

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

Un petit nombre pour l'argument de délai d'attente (lecture -t) est requis ou les données qui nécessitent un calcul peuvent être manquées. Le test lisible ([-r fichier]) est requis ou la commande read se bombardera si le fichier n'est pas lisible. Cela ne lira en fait aucune donnée car le nombre d'octets est nul (lire -N 0).

Paul
la source
si vous envisagez d'utiliser un système Linux, vous pouvez tout aussi bien y jeter un œil /proc/<pid>/fdinfo/<fd>, qui répertorie tous les modes de fichiers ouverts sous flags:- voir ici . Pour pourquoi votre 2ème partie (même après avoir corrigé l'erreur flagrante): read -t .1 -N0 <&4ne dira pas s'il y a des données à lire sur fd 4: essayez simplement avec 4</dev/null.
mosvy
Et bien sûr, [ -r /proc/$$/fd/$FD ]ne vous dit pas si le descripteur de fichier $FDest lisible, mais si le fichier à partir exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
duquel
-1

La question est assez ancienne - mais de toute façon - pourquoi ne pas simplement utiliser les fonctions intégrées?

for i in {0..5} ; do if [ -t $i ]; then echo "$i is a valid FD"; else echo "$i is INVALID FD"; fi; done

Production:

0 is a valid FD
1 is a valid FD
2 is a valid FD
3 is INVALID FD
4 is INVALID FD
5 is INVALID FD

Donc, pour répondre à la question - suggérerait:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi
Dimas
la source
-tne teste pas si un descripteur de fichier est valide, mais s'il est connecté à un tty. Ajoutez un echo yup |à votre script, et dira que 0 is INVALID FD, alors qu'en fait c'est un fd très valide, un pipe.
mosvy