Bash: สร้าง Fifo นิรนาม


38

เราทุกคนรู้mkfifoและไปป์ไลน์ คนแรกสร้างไปป์ที่มีชื่อดังนั้นหนึ่งจะต้องเลือกชื่อน่าจะด้วยmktempและในภายหลังจำที่จะยกเลิกการเชื่อมโยง อื่น ๆ สร้างไพพ์ที่ไม่ระบุชื่อไม่ต้องยุ่งยากกับชื่อและการลบ แต่ปลายท่อจะเชื่อมโยงกับคำสั่งในไพพ์ไลน์มันไม่สะดวกเลยที่จะใช้ตัวอธิบายไฟล์และใช้ส่วนที่เหลือ ของสคริปต์ ในโปรแกรมรวบรวมฉันจะทำret=pipe(filedes); ใน Bash มีexec 5<>fileใครคาดหวังอะไรแบบนี้"exec 5<> -"หรือ"pipe <5 >6"มีอะไรแบบนั้นใน Bash?

คำตอบ:


42

คุณสามารถยกเลิกการเชื่อมโยงไปป์ที่ระบุชื่อได้ทันทีหลังจากแนบไปกับกระบวนการปัจจุบันซึ่งส่งผลให้เกิดไปป์ที่ไม่ระบุชื่อ:

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

หากคุณต้องการหลีกเลี่ยงการตั้งชื่อไปป์ (เช่นระบบไฟล์เป็นแบบอ่านอย่างเดียว) ความคิด "รับความหมายของไฟล์อธิบาย" ก็ใช้งานได้เช่นกัน โปรดทราบว่านี่เป็น Linux เฉพาะเนื่องจากการใช้ 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<&-

คุณสามารถรวมสิ่งนี้กับการค้นหาคำอธิบายไฟล์ที่ไม่ได้ใช้โดยอัตโนมัติ: stackoverflow.com/questions/8297415/ …
CMCDragonkai

23

ในขณะที่ไม่มีกระสุนที่ฉันรู้ว่าสามารถสร้างท่อได้โดยไม่ต้องฟอร์ก แต่บางคนมีท่อที่ดีกว่าเปลือกหอยพื้นฐาน

ใน bash, ksh และ zsh สมมติว่าระบบของคุณรองรับ/dev/fd(ส่วนใหญ่ทำทุกวันนี้) คุณสามารถผูกอินพุตหรือเอาต์พุตของคำสั่งกับชื่อไฟล์: <(command)ขยายเป็นชื่อไฟล์ที่กำหนดไพพ์ที่เชื่อมต่อกับเอาท์พุตcommandและ>(command)ขยาย commandชื่อไฟล์ที่กำหนดท่อที่เชื่อมต่อกับการป้อนข้อมูลของ คุณลักษณะนี้เรียกว่าขั้นตอนการเปลี่ยนตัว วัตถุประสงค์หลักของมันคือการไพพ์มากกว่าหนึ่งคำสั่งเข้าหรือออกจากอีกคำสั่งหนึ่งเช่น

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

นอกจากนี้ยังเป็นประโยชน์ในการต่อสู้กับข้อบกพร่องบางอย่างของท่อเปลือกพื้นฐาน ยกตัวอย่างเช่นcommand2 < <(command1)จะเทียบเท่ากับการยกเว้นว่าสถานะของการเป็นที่ของcommand1 | command2 command2กรณีที่ใช้ก็คือexec > >(postprocessing)ซึ่งเทียบเท่า { ... } | postprocessingแต่อ่านได้มากขึ้นกว่าการวางส่วนที่เหลือทั้งหมดภายในสคริปต์


ฉันลองสิ่งนี้ด้วย diff และมันใช้งานได้ แต่กับ kdiff3 หรือกับ emacs มันไม่ทำงาน ฉันเดาว่าไฟล์ชั่วคราว / dev / fd จะถูกลบออกก่อนที่ kdiff3 จะอ่าน หรือบางที kdiff3 พยายามอ่านไฟล์สองครั้งและไพพ์กำลังส่งเพียงครั้งเดียว?
Eyal

@Eyal ด้วยการสนับสนุนกระบวนการอย่างต่อเนื่องชื่อไฟล์คือการอ้างอิง“ เวทมนต์” ไปยังไพพ์ (หรือไฟล์ชั่วคราวบนตัวแปร Unix ที่ไม่สนับสนุนตัวแปรเวทเหล่านี้) วิธีการใช้เวทมนตร์นั้นขึ้นอยู่กับระบบปฏิบัติการ การดำเนินการลินุกซ์ขณะที่“วิเศษ” การเชื่อมโยงสัญลักษณ์ที่มีเป้าหมายไม่ได้เป็นชื่อไฟล์ที่ถูกต้อง (มันสิ่งที่ต้องการpipe:[123456]) Emacs เห็นว่าเป้าหมายของ symlink ไม่ใช่ชื่อไฟล์ที่มีอยู่และทำให้สับสนพอที่จะไม่อ่านไฟล์ (อาจมีตัวเลือกให้อ่านมันได้แม้ว่า Emacs จะไม่ชอบเปิดไปป์เหมือน ไฟล์ต่อไป)
Gilles 'หยุดความชั่วร้าย' ใน

10

Bash 4 มีตัวประมวลผลร่วม

ตัวประมวลผลร่วมถูกดำเนินการแบบอะซิงโครนัสในเชลล์ย่อยราวกับว่าคำสั่งถูกยกเลิกด้วยตัวดำเนินการควบคุม '&' โดยมีไพพ์สองทางที่สร้างขึ้นระหว่างเชลล์ดำเนินการและตัวประมวลผลร่วม

รูปแบบสำหรับกระบวนการร่วมคือ:

coproc [NAME] command [redirections] 

3

เมื่อวันที่ตุลาคม 2012 ฟังก์ชั่นนี้ยังคงไม่มีอยู่ใน Bash แต่สามารถใช้ coproc ได้หากสิ่งที่คุณต้องการไพพ์ที่ไม่ระบุชื่อ / ไม่ระบุชื่อคือการพูดคุยกับกระบวนการลูก ปัญหาของ coproc ณ จุดนี้ดูเหมือนว่าจะรองรับเพียงครั้งเดียวเท่านั้น ฉันไม่สามารถเข้าใจได้ว่าทำไม coproc ถึงมีข้อ จำกัด นี้ พวกเขาควรจะได้รับการปรับปรุงโค้ดพื้นหลังของงานที่มีอยู่ (the & op) แต่นั่นเป็นคำถามสำหรับผู้แต่ง bash


สนับสนุนกระบวนการตัวประมวลผลเดียวเท่านั้น คุณสามารถตั้งชื่อพวกเขาตราบใดที่คุณไม่ได้ให้คำสั่งง่ายๆ ให้รายการคำสั่งแทน: coproc THING { dothing; }ตอนนี้ FDs ของคุณอยู่ใน${THING[*]}และคุณสามารถเรียกใช้coproc OTHERTHING { dothing; }และส่งและรับสิ่งของเข้าและออกจากทั้งสอง
clacke

2
@clacke ในman bashภายใต้ชื่อ BUGS พวกเขากล่าวว่านี้อาจจะมีเพียงหนึ่ง coprocess ใช้งานได้ตลอดเวลา และคุณจะได้รับคำเตือนถ้าคุณเริ่ม coproc อันที่สอง ดูเหมือนว่าจะใช้งานได้ แต่ฉันไม่ทราบว่าเกิดอะไรขึ้นในพื้นหลัง
Radu C

ตกลงดังนั้นในปัจจุบันมันใช้งานได้โดยโชคดีเท่านั้นไม่ใช่เพราะมันตั้งใจ คำเตือนที่เป็นธรรมขอบคุณ :-)
clacke

2

ในขณะที่คำตอบของ @AndersonAnderson ครอบคลุมฐานทั้งหมดและเสนอการป้องกันที่ดีสิ่งที่สำคัญที่สุดที่เปิดเผยคือการเอามือไปป์ที่ไม่ระบุชื่อนั้นง่ายเหมือน<(:)ตราบใดที่คุณอยู่บน Linux

ดังนั้นคำตอบที่สั้นและง่ายที่สุดสำหรับคำถามของคุณคือ:

exec 5<> <(:)

ใน macOS มันจะไม่ทำงานจากนั้นคุณจะต้องสร้างไดเรกทอรีชั่วคราวเพื่อระบุชื่อฟีฟ่าจนกว่าคุณจะเปลี่ยนเส้นทางไป ฉันไม่รู้เกี่ยวกับ BSD อื่น


คุณตระหนักถึงคำตอบของคุณเพียงเพราะข้อผิดพลาดใน linux จุดบกพร่องนี้ไม่มีอยู่ใน macOS ดังนั้นจึงต้องใช้วิธีแก้ปัญหาที่ซับซ้อนมากขึ้น รุ่นสุดท้ายที่ฉันโพสต์จะทำงานใน linux แม้ว่าข้อผิดพลาดใน linux ได้รับการแก้ไข
David Anderson

@DavidAnderson ดูเหมือนว่าคุณมีความรู้ในเรื่องนี้มากกว่าฉัน เหตุใดพฤติกรรมของ Linux จึงเป็นข้อบกพร่อง
clacke

1
หากexecผ่านไปแล้วและ Fifo แบบไม่ระบุชื่อที่เปิดสำหรับการอ่านเท่านั้นexecไม่ควรอนุญาตให้ Fifo แบบไม่ระบุชื่อนี้เปิดอ่านและเขียนโดยใช้ file descriptor แบบกำหนดเอง คุณควรคาดหวังว่าจะได้รับ-bash: /dev/fd/5: Permission deniedข้อความซึ่งเป็นสิ่งที่ปัญหาของ MacOS ฉันเชื่อว่าข้อผิดพลาดคือ Ubuntu ไม่ได้ผลิตข้อความเดียวกัน ฉันยินดีที่จะเปลี่ยนใจหากใครบางคนสามารถผลิตเอกสารที่ระบุว่าexec 5<> <(:)ได้รับอนุญาต
David Anderson

@DavidAnderson ว้าวช่างน่าหลงใหล ฉันคิดว่าทุบตีทำอะไรบางอย่างภายใน แต่มันกลับกลายเป็นว่าลินุกซ์ที่อนุญาตให้ทำเพียงแค่open(..., O_RDWR)ปลายท่อทิศทางเดียวที่ได้จากการทดแทนและกลายเป็นท่อสองทิศทางในหนึ่ง FD คุณอาจพูดถูกที่ไม่ควรเชื่อถือ :-D เอาต์พุตจากการใช้ไพเพอร์ของ execlineเพื่อสร้างไปป์แล้วเปลี่ยนตำแหน่งใหม่ด้วย bash <>: libranet.de/display/0b6b25a8-195c-84af-6ac7-ee6696661765
clacke

ไม่ว่ามันเป็นเรื่องสำคัญ แต่ถ้าคุณต้องการที่จะเห็นภายใต้ Ubuntu สิ่งที่ผ่านไปแล้วใส่exec 5<> fun() { ls -l $1; ls -lH $1; }; fun <(:)
David Anderson

1

GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)ฟังก์ชั่นดังต่อไปนี้ได้รับการทดสอบโดยใช้ ระบบปฏิบัติการคือ Ubuntu 18 ฟังก์ชั่นนี้ใช้พารามิเตอร์เดียวซึ่งเป็นตัวบ่งชี้ไฟล์ที่ต้องการสำหรับ FIFO ที่ไม่ระบุชื่อ

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
}

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)ฟังก์ชั่นดังต่อไปนี้ได้รับการทดสอบโดยใช้ ระบบปฏิบัติการคือ macOS High Sierra ฟังก์ชั่นนี้จะเริ่มออกโดยการสร้างชื่อ FIFO ในไดเรกทอรีชั่วคราวที่รู้จักกันเฉพาะกระบวนการที่สร้างมันขึ้นมา จากนั้นตัวอธิบายไฟล์จะถูกเปลี่ยนเส้นทางไปยัง FIFO ท้ายที่สุด FIFO จะถูกยกเลิกการเชื่อมโยงจากชื่อไฟล์โดยการลบไดเรกทอรีชั่วคราว สิ่งนี้ทำให้ไม่ระบุชื่อ FIFO

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

ฟังก์ชั่นด้านบนสามารถรวมเข้ากับฟังก์ชั่นเดียวที่จะทำงานกับทั้งสองระบบปฏิบัติการ ด้านล่างเป็นตัวอย่างของฟังก์ชั่นดังกล่าว ที่นี่มีความพยายามในการสร้าง FIFO ที่ไม่ระบุชื่ออย่างแท้จริง หากไม่สำเร็จระบบจะสร้างและแปลง FIFO ที่มีชื่อเป็น FIFO แบบไม่ระบุชื่อ

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

นี่คือตัวอย่างของการสร้าง FIFO แบบไม่ระบุชื่อจากนั้นเขียนข้อความบางส่วนไปที่ FIFO เดียวกัน

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

ด้านล่างเป็นตัวอย่างของการอ่านเนื้อหาทั้งหมดของ FIFO นิรนาม

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

สิ่งนี้สร้างผลลัพธ์ต่อไปนี้

Now is the
time for all
good men

คำสั่งด้านล่างปิด FIFO นิรนาม

eval exec "$fd>&-"

การอ้างอิง:
การสร้างไปป์ที่ไม่ระบุชื่อเพื่อใช้
ในภายหลังไฟล์ในไดเรกทอรีที่เขียนได้สาธารณะมีความปลอดภัยของเชลล์สคริปต์อันตราย


0

ใช้คำตอบที่ยอดเยี่ยมและสดใสจาก htamas ฉันแก้ไขมันเล็กน้อยเพื่อใช้ในหนึ่งซับนี่คือ:

# 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
ฉันอดไม่ได้ที่จะสังเกตว่าหนึ่งซับของคุณมีมากกว่าหนึ่งบรรทัด
Dmitry Grigoryev
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.