कैसे बचाएं / dev / stdout लक्ष्य स्थान को bash स्क्रिप्ट में?


12

मेरे पास एक निश्चित बैश स्क्रिप्ट है, जो /dev/stdoutअन्य स्थान के साथ 1 फ़ाइल डिस्क्रिप्टर को बदलने से पहले मूल स्थान को संरक्षित करना चाहती है ।

इसलिए, स्वाभाविक रूप से, मैंने कुछ ऐसा लिखा है

old_stdout=$(readlink -f /dev/stdout)

और यह काम नहीं किया। बहुत जल्दी मैं समझता हूँ कि समस्या क्या थी:

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

Obvioulsly, $()एक उपधारा में चलता है, जिसे मूल खोल में पाइप किया जाता है।

तो सवाल यह है: क्या कोई विश्वसनीय (लिनक्स वितरण के बीच पोर्टेबिलिटी के लिए स्कोप) /dev/stdoutबैश स्क्रिप्ट में स्ट्रिंग के रूप में स्थान बचाने का तरीका है ?


यह एक XY समस्या की तरह थोड़ा सा लगता है । अंतर्निहित मुद्दा क्या है?
Kusalananda

अंडररिंग इश्यू एक निश्चित इंस्टॉलेशन स्क्रिप्ट है जो दो मोड में चलती है - साइलेंट, जिसमें यह फाइल और वर्बोज़ के लिए सभी आउटपुट को लॉग करता है, जिसमें यह न केवल फ़ाइल में लॉग होता है, बल्कि टर्मिनल के लिए सब कुछ प्रिंट करता है। लेकिन दोनों मोड में स्क्रिप्ट उपयोगकर्ता के साथ बातचीत करना चाहती है, अर्थात प्रिंट टू टर्मिनल और उपयोगकर्ता की प्रतिक्रिया पढ़ें। इसलिए मैंने सोचा कि सेविंग /dev/stdoutसाइलेंट मोड में प्रिंटिंग मेसेज से समस्या को हल कर देगा। वैकल्पिक उत्पादन को पैदा करने वाली हर दूसरी कार्रवाई को पुनर्निर्देशित करता है, और उनमें से काफी संख्या में हैं। मोटे तौर पर उपयोगकर्ता इंटरैक्शन संदेशों की तुलना में 100 गुना अधिक है।
एलेक्सी.ऑर्गोव जूल

उपयोगकर्ता के साथ बातचीत करने का मानक तरीका प्रिंट करना है stderr। यह, उदाहरण के लिए, संकेत stderrडिफ़ॉल्ट रूप से क्यों जा रहे हैं ।
Kusalananda

दुर्भाग्य से, stderrस्क्रिप्ट को कई बाहरी कार्यक्रमों को कॉल करने के बाद, इसे पुनः निर्देशित और सहेजा जाना चाहिए, और सभी संभावित त्रुटि संदेश एकत्र और लॉग किए जाने हैं।
अलेक्सी.ईगोरोव

जवाबों:


14

किसी फाइल डिस्क्रिप्टर को बचाने के लिए, आप इसे दूसरे fd पर डुप्लिकेट करते हैं। संबंधित फ़ाइल के लिए एक पथ को सहेजना पर्याप्त नहीं है, आपको उद्घाटन मोड, खुलने वाले झंडे, फ़ाइल के भीतर की वर्तमान स्थिति आदि को सहेजने की आवश्यकता होगी। और निश्चित रूप से, अनाम पाइपों या सॉकेट्स के लिए, यह काम नहीं करेगा क्योंकि उनके पास कोई रास्ता नहीं है। आप जो बचाना चाहते हैं, वह है ओपन फाइल डिस्क्रिप्शन जो कि fd को संदर्भित करता है, और एक fd को डुप्लिकेट करना वास्तव में उसी ओपन फाइल डिस्क्रिप्शन में एक नया fd लौटा रहा है ।

बॉर्न-जैसे शेल के साथ फ़ाइल डिस्क्रिप्टर को दूसरे पर डुप्लिकेट करने के लिए, सिंटैक्स है:

exec 3>&1

ऊपर, fd 1 को fd 3 पर दोहराया गया है।

जो कुछ भी fd 3 बंद होने से पहले ही खुला था, लेकिन ध्यान दें कि 3 से 9 (आमतौर पर अधिक, 99 तक yash) उस उद्देश्य के लिए आरक्षित हैं (और 0, 1, या 2 के विपरीत कोई विशेष अर्थ नहीं है), शेल अपने स्वयं के आंतरिक व्यवसाय के लिए उनका उपयोग नहीं करना जानता है। एकमात्र कारण fd 3 पहले ही खुल गया होगा क्योंकि आपने इसे स्क्रिप्ट 1 में किया था , या इसे कॉलर द्वारा लीक किया गया था।

फिर, आप स्टडआउट को कुछ और में बदल सकते हैं:

exec > /dev/null

और बाद में, stdout को पुनर्स्थापित करने के लिए:

exec >&3 3>&-

( 3>&-फाइल डिस्क्रिप्टर को बंद करने की आवश्यकता है, जिसकी हमें अब आवश्यकता नहीं है)।

अब, इसके साथ समस्या यह है कि ksh को छोड़कर, आपके द्वारा चलायी जाने वाली प्रत्येक कमांड exec 3>&1उस fd को इनहेरिट करेगी 3. यह एक fd लीक है। आम तौर पर एक बड़ी बात नहीं है, लेकिन यह समस्या पैदा कर सकता है।

kshउन fds (2 से अधिक के लिए fds) पर क्लोज़-ऑन-एग्ज़िक्यूशन फ्लैग सेट करता है , लेकिन अन्य शेल और अन्य शेल के पास उस फ़्लैग को मैन्युअल रूप से सेट करने का कोई तरीका नहीं है।

अन्य शेल के लिए चारों ओर का काम प्रत्येक और प्रत्येक कमांड के लिए fd 3 को बंद करना है, जैसे:

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

बोझिल। यहां, सबसे अच्छा तरीका यह होगा कि आप इसका उपयोग न करें exec, लेकिन कमांड समूहों को पुनर्निर्देशित करें:

{
  ls
  uname
} > file.log

वहां, यह शेल है जो स्टैडआउट को बचाने और बाद में इसे पुनर्स्थापित करने के लिए ध्यान रखता है (और यह आंतरिक रूप से इसे fd पर बंद करके नकल करता है (9 से ऊपर, 99 के लिए yash) क्लोज-ऑन-एक्ज़िक फ्लैग सेट के साथ)।

नोट 1

अब, उन 3 से 9 के fds का प्रबंधन बोझिल और समस्याग्रस्त हो सकता है यदि आप उन्हें बड़े पैमाने पर या फ़ंक्शंस में उपयोग करते हैं, खासकर यदि आपकी स्क्रिप्ट कुछ तृतीय पक्ष कोड का उपयोग करती है जो संभवतः उन fds का उपयोग कर सकती हैं।

कुछ गोले ( zsh, bash, ksh93, सभी सुविधा (जोड़ा के ओलिवर महाजाल ने सुझाव दियाzsh 2005 में एक ही समय के आसपास) के बाद यह उनकी डेवलपर्स के बीच चर्चा की गई) के बजाय 10 जो इस मामले में मदद करता है ऊपर पहली मुक्त fd आवंटित करने के लिए एक विकल्प के वाक्य रचना है:

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}

इसके अलावा, आपका कोड इस मायने में गलत है कि fd 3 पहले ही लिया जा सकता है, क्योंकि ऐसा तब होता है, जब किसी rc.localसेवा से एक डिफरेंशियल रन होता है , जैसे कि आप वास्तव में किसी चीज का इस्तेमाल करते हैं exec {FD}>&1। लेकिन यह केवल बैश 4 में समर्थित है, जो वास्तव में दुखद है। तो यह वास्तव में पोर्टेबल नहीं है।
alexey.e.egorov

@ alexey.e.egorov, संपादित देखें।
स्टीफन चेजेलस

बैश 3. * इस सुविधा का समर्थन नहीं करता है, और यह संस्करण Centos 5 में उपयोग किया जाता है, जो अभी भी समर्थित है और अभी भी उपयोग किया जाता है। और मुक्त डिस्क्रिप्टर खोजना और फिर eval "exec $i>&1"एक ऐसी चीज है जिससे मैं बचना चाहूंगा, क्योंकि यह बोझिल है। क्या मैं वास्तव में भरोसा कर सकता हूं कि 9 से ऊपर की एफडीएस मुफ्त होगी?
एलेक्सी.गोरोव 12

@ alexey.e.egorov, नहीं, आप इसे पीछे देख रहे हैं। fds 3 से 9 का उपयोग करने के लिए स्वतंत्र हैं (और यह आप पर निर्भर है कि आप उन्हें प्रबंधित करना चाहते हैं) और उस उद्देश्य के लिए अभिप्रेत हैं। 9 से ऊपर के एफडी का उपयोग शेल द्वारा आंतरिक रूप से किया जा सकता है और उन्हें बंद करने से बुरा परिणाम हो सकता है। अधिकांश गोले आपको उनका उपयोग नहीं करने देंगे। bashतुम्हें पैर में गोली मार देगा।
स्टीफन चेज़लस

2
@ alexey.e.egorov, यदि आप स्क्रिप्ट शुरू करते हैं, तो (3..9) ओपन में कुछ fds हैं, ऐसा इसलिए है क्योंकि आपका कॉलर उन्हें बंद करना या उन पर क्लोज़-ऑन-एक्ज़िक फ़्लैट सेट करना भूल गया है। जिसे मैं एक fd लीक कहता हूं। अब, हो सकता है कि फोन करने वाले का इरादा आपके लिए उन fds को पास करने का हो, ताकि आप उनसे / से डेटा पढ़ सकें और / या लिख ​​सकें, लेकिन तब आपको इसके बारे में पता चलेगा। यदि आप उनके बारे में नहीं जानते हैं, तो आप परवाह नहीं करते हैं, तो आप उन्हें स्वतंत्र रूप से बंद कर सकते हैं (ध्यान दें कि यह आपकी स्क्रिप्ट की प्रक्रिया को बंद कर देता है, आपके कॉलर की नहीं)।
स्टीफन चेज़लस

3

जैसा कि आप देख सकते हैं, बैश स्क्रिप्टिंग एक नियमित प्रोग्रामिंग भाषा की तरह नहीं है जहाँ आप फ़ाइल डिस्क्रिप्टर असाइन कर सकते हैं।

सबसे सरल उपाय यह है कि आप जो रीडायरेक्ट करना चाहते हैं उसे चलाने के लिए एक उप-शेल का उपयोग करें ताकि प्रसंस्करण को शीर्ष-शेल में वापस लाया जा सके, जिसके मानक I / O बरकरार हैं।

ttyTTY डिवाइस की पहचान करने और अपनी स्क्रिप्ट में I / O को नियंत्रित करने के लिए उपयोग करने के लिए एक वैकल्पिक समाधान होगा । उदाहरण के लिए:

dev=$(tty)

और फिर आप कर सकते हैं ..

echo message > $dev

> एक वैकल्पिक समाधान टीटीवाई डिवाइस की पहचान करने और अपनी स्क्रिप्ट में I / O को नियंत्रित करने के लिए ट्टी का उपयोग करना होगा। यह कैसे करता है?
alexey.e.egorov

1
मैंने अपने उत्तर में केवल एक उदाहरण शामिल किया है।
जूली पेलेटियर

1

$$ इंटरैक्टिव शेल या स्क्रिप्ट के प्रासंगिक शेल PID के मामले में आपको वर्तमान प्रक्रिया PID मिल जाएगी।

तो आप उपयोग कर सकते हैं:

readlink -f /proc/$$/fd/1

उदाहरण:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33

1
हालांकि यह कार्यशील है, एक विशिष्ट /procसंरचना पर निर्भर करना पोर्टेबिलिटी मुद्दों का कारण बनता है, जैसा /dev/stdoutकि प्रश्न में उल्लेख किया गया है।
जूली पेलेटियर

1
@JuliePelletier एक /procसंरचना संरचना पर निर्भर है ? यह किसी भी लिनक्स पर काम करेगा procfs..
heemayl

1
ठीक है, इसलिए हम लिनक्स के लिए सामान्यीकरण कर सकते हैं जैसा procfsकि लगभग हमेशा मौजूद होता है, लेकिन हम अक्सर पोर्टेबिलिटी प्रश्न देखते हैं और एक अच्छी विकास पद्धति में अन्य प्रणालियों के लिए पोर्टेबिलिटी पर विचार करना शामिल है। bashऑपरेटिंग सिस्टम की एक भीड़ पर चला सकते हैं।
जूली पेलेटियर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.