बैश: अनाम फीफो बनाएँ


38

हम सभी जानते हैं mkfifoऔर पाइपलाइन। पहले वाला एक नामित पाइप बनाता है , इस प्रकार एक नाम का चयन करना है, सबसे अधिक संभावना है mktempऔर बाद में अनलिंक करना याद है। दूसरा एक अनाम पाइप बनाता है, नाम और हटाने के साथ कोई परेशानी नहीं है, लेकिन पाइप के छोर पाइप लाइन में कमांड से बंधे हुए हैं, यह किसी भी तरह से फ़ाइल डिस्क्रिप्टर की पकड़ पाने और बाकी हिस्सों में उनका उपयोग करने के लिए वास्तव में सुविधाजनक नहीं है। स्क्रिप्ट का। संकलित कार्यक्रम में, मैं बस करूँगा ret=pipe(filedes); बैश में exec 5<>fileतो कोई है जो उम्मीद करेगा जैसे- "exec 5<> -"या "pipe <5 >6"बैश में ऐसा कुछ है?

जवाबों:


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

यदि आप वास्तव में नामित पाइप से बचना चाहते हैं (जैसे कि फ़ाइल सिस्टम केवल-पढ़ने के लिए है), तो आपके "फ़ाइल डिस्क्रिप्टर की एक पकड़ प्राप्त करें" विचार भी काम करता है। ध्यान दें कि यह खरीद के उपयोग के कारण लिनक्स-विशिष्ट है।

# 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


मैंने इसे अलग करने की कोशिश की और यह काम किया लेकिन kdiff3 के साथ या emacs के साथ, यह काम नहीं किया। मेरा अनुमान है कि kdiff3 को पढ़ने से पहले अस्थायी / dev / fd फ़ाइल निकाली जा रही है। या शायद kdiff3 फ़ाइल को दो बार पढ़ने की कोशिश कर रहा है और पाइप केवल एक बार भेज रहा है?
ईयाल

@ ईयाल प्रक्रिया के साथ, फ़ाइल नाम एक पाइप के लिए एक "जादू" संदर्भ है (या यूनिक्स वेरिएंट पर एक अस्थायी फ़ाइल जो इन जादू वेरिएंट का समर्थन नहीं करते हैं)। जादू कैसे लागू किया जाता है यह ओएस पर निर्भर करता है। लिनक्स उन्हें "जादू" प्रतीकात्मक लिंक के रूप में लागू करता है जिसका लक्ष्य एक वैध फ़ाइल नाम नहीं है (यह कुछ ऐसा है pipe:[123456])। Emacs देखता है कि सिमलिंक का लक्ष्य एक मौजूदा फ़ाइल नाम नहीं है और यह इसे काफी भ्रमित करता है कि यह फ़ाइल को नहीं पढ़ता है (वैसे भी इसे पढ़ने के लिए एक विकल्प हो सकता है, हालांकि Emacs एक पाइप को खोलने के रूप में पसंद नहीं करता है वैसे भी फाइल)।
गिल्स एसओ- बुराई को रोकना '

10

बैश 4 में कोप्रोसेस हैं

एक उपप्रकार को एक उप-प्रकार में अतुल्यकालिक रूप से निष्पादित किया जाता है, जैसे कि कमांड को 'और' नियंत्रण ऑपरेटर के साथ समाप्त कर दिया गया था, निष्पादन शेल और कॉप्रोसेस के बीच दो-तरफा पाइप स्थापित किया गया था।

नकल के लिए प्रारूप है:

coproc [NAME] command [redirections] 

3

अक्टूबर 2012 तक यह कार्यक्षमता अभी भी बाश में मौजूद नहीं है, लेकिन यदि आप सभी को अनाम / अनाम पाइप की आवश्यकता है, तो चाइल्ड प्रक्रिया से बात करने के लिए कॉपीराइट का उपयोग किया जा सकता है। इस बिंदु पर कोप्रोक के साथ समस्या यह है कि जाहिरा तौर पर एक समय में केवल एक का समर्थन किया जाता है। मैं यह पता नहीं लगा सकता कि कॉप्रोक को यह सीमा क्यों मिली। उन्हें मौजूदा कार्य पृष्ठभूमि कोड (& op) का संवर्द्धन होना चाहिए था, लेकिन यह बैश के लेखकों के लिए एक सवाल है।


केवल एक कोप्रोसेस ही समर्थित नहीं है। आप उन्हें तब तक नाम दे सकते हैं, जब तक आप एक साधारण आदेश प्रदान नहीं करते हैं। इसके बजाय इसे एक कमांड लिस्ट दें: coproc THING { dothing; }अब आपके FD हैं ${THING[*]}और आप coproc OTHERTHING { dothing; }दोनों से और इन पर चीजें भेज और प्राप्त कर सकते हैं ।
13

2
@ बीजीजीएस man bashशीर्षक के तहत, वे कहते हैं: यह एक समय में केवल एक सक्रिय कोप्रोसेस हो सकता है । और अगर आपको दूसरा कॉप्रेस शुरू होता है तो आपको एक चेतावनी मिलती है। यह काम करने के लिए प्रकट होता है, लेकिन मुझे नहीं पता कि पृष्ठभूमि में क्या विस्फोट होता है।
रादु सी

ठीक है, इसलिए यह वर्तमान में केवल भाग्य से काम करता है, इसलिए नहीं कि यह जानबूझकर किया गया था। निष्पक्ष चेतावनी, धन्यवाद। :-)
क्लैक्च

2

जबकि @ डेविडएंडरसन का जवाब सभी आधारों को कवर करता है और कुछ अच्छे सुरक्षा उपायों की पेशकश करता है, सबसे महत्वपूर्ण बात यह है कि यह पता चलता है कि अनाम पाइप पर अपने हाथों को प्राप्त करना <(:)तब तक आसान है , जब तक आप लिनक्स पर रहते हैं।

तो आपके प्रश्न का सबसे छोटा और सरल उत्तर है:

exec 5<> <(:)

MacOS पर यह काम नहीं करेगा, तब आपको नामांकित फिफ़ो को घर में रखने के लिए एक अस्थायी निर्देशिका बनाने की आवश्यकता होगी जब तक कि आप इसे पुनर्निर्देशित नहीं करते। मैं अन्य बीएसडी के बारे में नहीं जानता।


आपको लगता है कि आपका उत्तर केवल लिनक्स में बग के कारण काम करता है। यह बग macOS में मौजूद नहीं है, इस प्रकार अधिक जटिल समाधान की आवश्यकता होती है। अंतिम संस्करण जो मैंने पोस्ट किया है, वह लिनक्स में भी काम करेगा, भले ही लिनक्स में बग तय हो।
डेविड एंडरसन

@DavidAnderson आपको लगता है कि मुझे इससे गहरा ज्ञान है। लिनक्स व्यवहार बग क्यों है?
clacke

1
यदि execपास और अनाम पंद्रो है जो केवल पढ़ने के लिए खोला गया है, तो execइस अनाम पंद्रो को कस्टम फ़ाइल डिस्क्रिप्टर का उपयोग करके पढ़ने और लिखने के लिए खोलने की अनुमति नहीं देनी चाहिए। आपको एक -bash: /dev/fd/5: Permission deniedसंदेश मिलने की उम्मीद करनी चाहिए , जो कि macOS समस्या है। मेरा मानना ​​है कि बग यह है कि उबंटू एक ही संदेश का उत्पादन नहीं करता है। मैं अपना दिमाग बदलने के लिए तैयार हो exec 5<> <(:)जाऊंगा अगर कोई यह कहकर दस्तावेज तैयार कर सके कि अनुमति दी गई है।
डेविड एंडरसन

@DavidAnderson वाह, यह आकर्षक है। मुझे लगा कि बैश आंतरिक रूप से कुछ कर रहा था, लेकिन यह पता चला कि यह लिनक्स है जो open(..., O_RDWR)प्रतिस्थापन द्वारा प्रदान किए गए एक यूनिडायरेक्शनल पाइप एंड पर कर रहा है और यह एक एफडी में द्विदिश पाइप में बदल जाता है। आप शायद सही हैं कि किसी को इस पर भरोसा नहीं करना चाहिए। : -D आउटपुट को पाइप लाइन बनाने के लिए पाइप लाइन का उपयोग करने से, फिर इसे बैश के साथ फिर से तैयार करना <>: libranet.de/display/0b6b25a8-195c-84af-6ac7-ee666/6661765
clacke

ऐसा नहीं है कि यह मायने रखता है, लेकिन यदि आप उबंटू के तहत देखना चाहते हैं कि क्या पारित किया गया है exec 5<>, तो दर्ज करें fun() { ls -l $1; ls -lH $1; }; fun <(:)
डेविड एंडरसन

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 हाई सिएरा था। यह फ़ंक्शन एक अस्थायी निर्देशिका में नाम 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 में बदल जाता है।

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
मैं यह ध्यान देने में मदद नहीं कर सकता कि आपके वन-लाइनर में एक से अधिक लाइन हैं।
दिमित्री ग्रिगोरीव जूल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.