46 votes

Bash : créer un fifo anonyme

Nous savons tous que mkfifo et des pipelines. Le premier crée un nommée Il faut donc choisir un nom, très probablement avec le mot mktemp et se rappeler plus tard de le dissocier. L'autre crée un tuyau anonyme, sans avoir à se soucier des noms et des suppressions, mais les extrémités du tuyau sont liées aux commandes du pipeline, et il n'est pas vraiment pratique de s'emparer des descripteurs de fichiers et de les utiliser dans le reste du script. Dans un programme compilé, je ferais simplement ret=pipe(filedes) ; dans Bash, il y a exec 5<>file On pourrait donc s'attendre à quelque chose comme "exec 5<> -" o "pipe <5 >6" Existe-t-il quelque chose de ce genre dans Bash ?

54voto

htamas Points 641

Vous pouvez dissocier un tuyau nommé immédiatement après l'avoir attaché au processus en cours, ce qui donne pratiquement un tuyau anonyme :

# create a temporary named pipe
PIPE=$(mktemp -u)
mkfifo $PIPE
# attach it to file descriptor 3
exec 3<>$PIPE
# unlink the named pipe
rm $PIPE
...
# anything we write to fd 3 can be read back from it
echo 'Hello world!' >&3
head -n1 <&3
...
# close the file descriptor when we are finished (optional)
exec 3>&-

Si vous voulez vraiment éviter les tuyaux nommés (par exemple, le système de fichiers est en lecture seule), votre idée de "s'emparer des descripteurs de fichiers" fonctionne également. Notez que cela est spécifique à Linux en raison de l'utilisation de procfs.

# start a background pipeline with two processes running forever
tail -f /dev/null | tail -f /dev/null &
# save the process ids
PID2=$!
PID1=$(jobs -p %+)
# hijack the pipe's file descriptors using procfs
exec 3>/proc/$PID1/fd/1 4</proc/$PID2/fd/0
# kill the background processes we no longer need
# (using disown suppresses the 'Terminated' message)
disown $PID2
kill $PID1 $PID2
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptors when we are finished (optional)
exec 3>&- 4<&-

12voto

Damian Powell Points 315

Bash 4 a coprocessus .

Un coprocessus est exécuté de manière asynchrone dans un sous-shell, comme si la commande avait été terminée par l'opérateur de contrôle '&', avec un tuyau bidirectionnel établi entre le Shell en cours d'exécution et le coprocessus.

Le format d'un coprocessus est le suivant :

coproc [NAME] command [redirections]

5voto

Kamil Drakari Points 7059

Tandis que l'article de @DavidAnderson sur les répondre couvre toutes les bases et offre quelques garanties intéressantes, la chose la plus importante qu'il révèle est qu'il est aussi facile de mettre la main sur un tuyau anonyme que de le faire à l'aide d'un ordinateur. <(:) tant que vous restez sous Linux.

La réponse la plus courte et la plus simple à votre question est donc la suivante :

exec 5<> <(:)

Sous macOS, cela ne fonctionnera pas, vous devrez alors créer un répertoire temporaire pour y placer le fifo nommé jusqu'à ce que vous ayez redirigé vers lui. Je ne sais pas ce qu'il en est pour les autres systèmes BSD.

3voto

Radu C Points 131

En octobre 2012, cette fonctionnalité ne semble toujours pas exister dans Bash, mais coproc peut être utilisé si tout ce dont vous avez besoin des tuyaux non nommés/anonymes est de parler à un processus enfant. Le problème avec coproc à ce stade est qu'apparemment un seul est supporté à la fois. Je n'arrive pas à comprendre pourquoi les coproc ont eu cette limitation. Ils auraient dû être une amélioration du code d'arrière-plan de tâche existant (le & op), mais c'est une question pour les auteurs de bash.

1voto

Tim Points 733

La fonction suivante a été testée en utilisant GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu) . Le système d'exploitation était Ubuntu 18. Cette fonction prend un seul paramètre qui est le descripteur de fichier désiré pour la FIFO anonyme.

MakeFIFO() {
    local "MakeFIFO_upper=$(ulimit -n)" 
    if [[ $# -ne 1 || ${#1} -gt ${#MakeFIFO_upper} || -n ${1%%[0-9]*} || 10#$1 -le 2
        || 10#$1 -ge MakeFIFO_upper ]] || eval ! exec "$1<> " <(:) 2>"/dev/null"; then
        echo "$FUNCNAME: $1: Could not create FIFO" >&2
        return "1"
    fi
}

La fonction suivante a été testée en utilisant GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17) . Le système d'exploitation était macOS High Sierra. Cette fonction commence par créer un FIFO nommé dans un répertoire temporaire connu uniquement du processus qui l'a créé . Ensuite, le descripteur de fichier est redirigé vers la FIFO. Enfin, la FIFO est dissociée du nom de fichier en supprimant le répertoire temporaire. Cela rend la FIFO anonyme.

MakeFIFO() {
    MakeFIFO.SetStatus() {
        return "${1:-$?}"
    }
    MakeFIFO.CleanUp() {
        local "MakeFIFO_status=$?"
        rm -rf "${MakeFIFO_directory:-}"    
        unset "MakeFIFO_directory"
        MakeFIFO.SetStatus "$MakeFIFO_status" && true
        eval eval "${MakeFIFO_handler:-:}'; true'" 
    }
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file=" 
    MakeFIFO_handler="$(trap -p EXIT)"
    MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
    MakeFIFO_handler="${MakeFIFO_handler% *}"
    trap -- 'MakeFIFO.CleanUp' EXIT
    until "$MakeFIFO_success"; do
        [[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
        && 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
        MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
        MakeFIFO_file="$MakeFIFO_directory/pipe"
        mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
        ! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
        MakeFIFO_success="true"
    done
    rm -rf "${MakeFIFO_directory:-}"
    unset  "MakeFIFO_directory"
    eval trap -- "$MakeFIFO_handler" EXIT
    unset  "MakeFIFO_handler"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

Les fonctions ci-dessus peuvent être combinées en une seule fonction qui fonctionnera sur les deux systèmes d'exploitation. Vous trouverez ci-dessous un exemple d'une telle fonction. Ici, on tente de créer une FIFO véritablement anonyme. En cas d'échec, une FIFO nommée est créée et convertie en FIFO anonyme.

MakeFIFO() {
    MakeFIFO.SetStatus() {
        return "${1:-$?}"
    }
    MakeFIFO.CleanUp() {
        local "MakeFIFO_status=$?"
        rm -rf "${MakeFIFO_directory:-}"    
        unset "MakeFIFO_directory"
        MakeFIFO.SetStatus "$MakeFIFO_status" && true
        eval eval "${MakeFIFO_handler:-:}'; true'" 
    }
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file=" 
    MakeFIFO_handler="$(trap -p EXIT)"
    MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
    MakeFIFO_handler="${MakeFIFO_handler% *}"
    trap -- 'MakeFIFO.CleanUp' EXIT
    until "$MakeFIFO_success"; do
        [[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
        && 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
        if eval ! exec "$1<> " <(:) 2>"/dev/null"; then
            MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
            MakeFIFO_file="$MakeFIFO_directory/pipe"
            mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
            ! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
        fi
        MakeFIFO_success="true"
    done
    rm -rf "${MakeFIFO_directory:-}"
    unset  "MakeFIFO_directory"
    eval trap -- "$MakeFIFO_handler" EXIT
    unset  "MakeFIFO_handler"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

Voici un exemple de création d'une FIFO anonyme, puis d'écriture d'un texte dans cette même FIFO.

fd="6"
MakeFIFO "$fd"
echo "Now is the" >&"$fd"
echo "time for all" >&"$fd"
echo "good men" >&"$fd"

Voici un exemple de lecture de l'intégralité du contenu de la FIFO anonyme.

echo "EOF" >&"$fd"
while read -u "$fd" message; do
    [[ $message != *EOF ]] || break
    echo "$message"
done

Le résultat est le suivant.

Now is the
time for all
good men

La commande ci-dessous ferme la FIFO anonyme.

eval exec "$fd>&-"

Références :
Création d'un tuyau anonyme pour une utilisation ultérieure
Les fichiers se trouvant dans des répertoires accessibles au public sont dangereux
Shell Shell Sécurité

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X