Bash: anonim fifo oluştur


38

Hepimiz biliyoruz mkfifove boru hatları. Birincisi adlandırılmış bir boru oluşturur , bu nedenle bir isim seçmek zorundadır, büyük olasılıkla mktempve sonra bağlantısını kesmeyi unutmayın. Diğeri isimsiz bir boru oluşturur, isimleri ve çıkarmaları ile uğraşmaz, ancak borunun uçları boru hattındaki komutlara bağlanır, bir şekilde dosya tanımlayıcılarını kavramak ve geri kalanında kullanmak gerçekten uygun değildir. Komut dosyasının Derlenmiş bir programda ben sadece yapardım ret=pipe(filedes); Bash'ta exec 5<>fileöyle bir şey beklenir ki "exec 5<> -"ya da böyle bir şey var Bash'de öyle bir şey "pipe <5 >6"?

Yanıtlar:


42

Adlandırılmış bir borunun bağlantısını, hemen hemen anonim bir boru ile sonuçlanan geçerli işleme ekledikten hemen sonra bağlayabilirsiniz:

# 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>&-

Eğer adlandırılmış yöneltmelerden kaçınmak istiyorsanız (örneğin, dosya sistemi salt okunurdur), “dosya tanımlayıcılarını yakalayın” fikriniz de işe yarar. Bunun, procfs kullanımı nedeniyle Linux'a özgü olduğunu unutmayın.

# 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<&-

Bunu, kullanılmamış dosya tanımlayıcılarını otomatik bulma ile birleştirebilirsiniz: stackoverflow.com/questions/8297415/…
CMCDragonkai

23

Bildiğim mermilerden hiçbiri çatalsız boru yapamazken, bazılarının temel kabuk boru hattından daha iyisi yok.

Bash, ksh ve zsh'da, sisteminizin desteklediğini varsayarsak /dev/fd(bugünlerde çoğu zaman), bir komutun girişini veya çıktısını bir dosya adına bağlayabilirsiniz: <(command)çıktısına bağlı bir boru belirleyen bir dosya adına genişler commandve >(command)genişler. girişine bağlı bir boruyu belirleyen bir dosya adına command. Bu özelliğe işlem değiştirme adı verilir . Birincil amacı, birden fazla komutu diğerinin içine veya dışına yönlendirmek, örneğin;

diff <(transform <file1) <(transform <file2)
tee >(transform1 >out1) >(transform2 >out2)

Bu ayrıca, temel kabuk boruların bazı eksiklikleriyle mücadele etmek için de kullanışlıdır. Örneğin, durumu dışında olması dışında command2 < <(command1)eşdeğerdir . Diğer bir kullanım senaryosu, komut dosyasının geri kalanının tamamını içine koymakla eşdeğerdir, ancak daha okunaklıdır .command1 | command2command2exec > >(postprocessing){ ... } | postprocessing


Bunu diff ile denedim ve işe yaradı ama kdiff3 veya emacs ile işe yaramadı. Tahminim, geçici / dev / fd dosyasının kdiff3'ün okumaya başlamasından önce kaldırılıyor olmasıdır. Ya da belki kdiff3 dosyayı iki kez okumaya çalışıyor ve boru sadece bir kez gönderiyor?
Eyal

@Eyal Süreç denetimi ile dosya adı bir boruya (ya da bu sihirli değişkenleri desteklemeyen Unix değişkenlerinde geçici bir dosyaya) “sihirli” bir referanstır. Sihrin nasıl uygulandığı işletim sistemine bağlıdır. Linux, hedefleri geçerli bir dosya adı olmayan “sihirli” sembolik bağlantılar olarak uygular (bir şey gibi pipe:[123456]). Emacs, sembolik bağlantının hedefinin varolan bir dosya adı olmadığını ve dosyayı okumadığı için yeterince kafasını karıştırdığını görür (Emacs bir pipoyu bir boru gibi açmayı sevmemesine rağmen, onu yine de okumak için bir seçenek olabilir). yine de dosya).
Gilles 'SO- kötü olmayı'

10

Bash 4 sahiptir yardımcı işlemci .

Bir coprocess, komut '&' kontrol operatörü ile sonlandırılmış gibi, bir alt kabukta asenkron olarak yürütülür, bu durumda çalıştırma kabuğu ve coprocess arasında iki yönlü bir boru kurulur.

Bir işlem için format:

coproc [NAME] command [redirections] 

3

Ekim 2012'den itibaren bu işlevsellik, Bash'te hala görünmüyor, ancak coproc, adsız / anonim borular için tek ihtiyacınız olan bir çocuk işlemiyle konuşmaksa kullanılabilir. Bu noktada coproc ile ilgili sorun, görünüşe göre bir seferde yalnızca birinin desteklenmesidir. Coproc'un neden bu sınırlamayı getirdiğini anlayamıyorum. Mevcut görev arka plan kodunun (& op) bir geliştirmesi olmalıydı, ama bu bash yazarları için bir soru.


Sadece bir coprocess desteklenmiyor. Basit bir komut vermediğiniz sürece bunları adlandırabilirsiniz. Yerine bunu bir komut listesi verin: coproc THING { dothing; }Şimdi FDS içindedir ${THING[*]}ve çalıştırabileceğiniz coproc OTHERTHING { dothing; }ve göndermek ve ikisinden de bir şeyler alırlar.
clacke

2
man bash(BUGS başlığı altında clacke , şunu söylüyorlar: Bir seferde sadece bir aktif işlem olabilir) . Ve ikinci bir coproc başlatırsanız bir uyarı alırsınız. İşe benziyor ama arka planda neyin patladığını bilmiyorum.
Radu C

Tamam, şu anda sadece şans eseri çalışıyor, kasıtlı olduğu için değil. Adil uyarı, teşekkürler. :-)
clacke

2

@ DavidAnderson'ın cevabı tüm temelleri kapsar ve bazı güzel güvenlik önlemleri sunarken, ortaya çıkardığı en önemli şey, ellerinizi isimsiz bir boruya sokmanın <(:), Linux'ta kaldığınız sürece kolay olmasıdır .

Yani sorunuza en kısa ve en basit cevap:

exec 5<> <(:)

MacOS'ta işe yaramazsa, yeniden yönlendirilinceye kadar adlandırılmış fifoyu yerleştirmek için geçici bir dizin oluşturmanız gerekir. Diğer BSD'ler hakkında bilmiyorum.


Cevabınızın sadece linux'taki bir hata nedeniyle işe yaradığını biliyorsunuz. Bu hata macOS'ta mevcut değildir, bu nedenle daha karmaşık bir çözüm gerektirir. Gönderdiğim son sürüm, Linux'taki hata düzeltilse bile, Linux'ta çalışacaktır.
David Anderson,

@DavidAnderson Bu konuda benden daha derin bir bilgiye sahipsin. Linux davranışı neden bir hata?
clacke

1
Eğer execbu sadece okuma için açılır Fifo geçti ve anonim, o zaman execbu anonim fifo okuma ve özel bir dosya tanıtıcı kullanarak yazmak için açılmasına izin verilmemelidir. -bash: /dev/fd/5: Permission deniedMacOS'un verdiği bir mesaj almayı beklemelisiniz . Böceğin Ubuntu'nun aynı mesajı üretmediğine inanıyorum. Birinin exec 5<> <(:)izin verilmesine izin verilen bir belge yazdırabilirse fikrimi değiştirmeye istekli olurum .
David Anderson,

@DavidAnderson Wow, bu büyüleyici. Bash'ın dahili olarak bir şeyler yaptığını varsaydım, ancak bu open(..., O_RDWR)ikame edicinin sağladığı tek yönlü boru ucunda basit bir şekilde yapmayı sağlayan ve onu tek bir FD'de çift yönlü boruya dönüştüren Linux olduğu ortaya çıktı . Muhtemelen haklısın, buna güvenmemelisin. :-D Boruyu oluşturmak için execline'ın piperw komutunu kullandıktan sonra bash ile yeniden konumlandırarak<>
çıktı

Önemli değil, fakat Ubuntu'nun altında neyin geçtiğini görmek istiyorsanız exec 5<>, o zaman girin fun() { ls -l $1; ls -lH $1; }; fun <(:).
David Anderson,

1

Aşağıdaki işlev kullanılarak test edildi GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu). İşletim sistemi Ubuntu 18 idi. Bu fonksiyon anonim FIFO için istenen dosya tanımlayıcısı olan tek bir parametre alır.

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
}

Aşağıdaki işlev kullanılarak test edildi GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17). İşletim sistemi macOS High Sierra idi. Bu işlev, yalnızca onu oluşturan işlem tarafından bilinen geçici bir dizinde FIFO adlı bir dosya oluşturularak başlar . Ardından, dosya tanıtıcısı FIFO'ya yönlendirilir. Son olarak, FIFO, geçici dizini silerek dosya adından çıkarılır. Bu, FIFO'yu adsız kılar.

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"; }
}

Yukarıdaki işlevler, her iki işletim sisteminde de çalışacak tek bir işlevle birleştirilebilir. Aşağıda böyle bir fonksiyonun bir örneğidir. Burada, gerçekten isimsiz bir FIFO oluşturma girişiminde bulunuldu. Başarısız olursa, o zaman adlandırılmış bir FIFO oluşturulur ve adsız bir FIFO'ya dönüştürülür.

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"; }
}

İşte anonim bir FIFO oluşturmanın ve ardından aynı FIFO'ya bir metin yazmanın bir örneği.

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

Aşağıda anonim FIFO içeriğinin tamamını okumak için bir örnek.

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

Bu aşağıdaki çıktıyı üretir.

Now is the
time for all
good men

Aşağıdaki komut anonim FIFO'yu kapatır.

eval exec "$fd>&-"

Referanslar:
Daha sonra kullanmak üzere adsız bir boru oluşturma
Genel Olarak Yazılabilir Dizinlerdeki Dosyalar Tehlikeli
Kabuk Betiği Güvenliğidir


0

Htamalardan gelen büyük ve parlak cevabı kullanarak, tek bir astarda kullanmak için onu biraz değiştirdim, işte burada:

# create a temporary named pipe
PIPE=(`(exec 0</dev/null 1</dev/null; (( read -d \  e < /proc/self/stat ; echo $e >&2 ; exec tail -f /dev/null 2> /dev/null ) | ( read -d \  e < /proc/self/stat ; echo $e  >&2 ; exec tail -f /dev/null 2> /dev/null )) &) 2>&1 | for ((i=0; i<2; i++)); do read e; printf "$e "; done`)
# attach it to file descriptors 3 and 4
exec 3>/proc/${PIPE[0]}/fd/1 4</proc/${PIPE[1]}/fd/0
...
# kill the temporary pids
kill ${PIPE[@]}
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptor when we are finished (optional)
exec 3>&- 4<&-

7
Tek gömlekinizin birden fazla çizgisi olduğunu fark etmeden edemem .
Dmitry Grigoryev
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.