बैश वाली फाइल से लाइनें पढ़ना: बनाम जबकि


36

मैं एक पाठ फ़ाइल को पढ़ने और प्रत्येक लाइन के साथ कुछ करने की कोशिश कर रहा हूँ, एक बैश स्क्रिप्ट का उपयोग करके।

तो, मेरे पास एक सूची है जो इस तरह दिखती है:

server1
server2
server3
server4

मैंने सोचा कि मैं कुछ समय के लिए लूप का उपयोग करके इस पर लूप कर सकता हूं, जैसे:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt

जबकि लूप 1 रन के बाद बंद हो जाता है, इस प्रकार केवल uname -aसर्वर 1 पर चल रहा है

हालांकि, बिल्ली के लिए लूप के साथ यह ठीक काम करता है:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done

मेरे लिए और भी चौंकाने वाला यह है कि यह भी काम करता है:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

प्रथम पुनरावृत्ति के बाद मेरा पहला उदाहरण क्यों बंद हो जाता है?

जवाबों:


25

यहाँ forलूप ठीक है। लेकिन ध्यान दें कि ऐसा इसलिए है क्योंकि फ़ाइल में मशीन के नाम हैं, जिसमें कोई व्हाट्सएप वर्ण या ग्लोबिंग वर्ण नहीं हैं। सामान्य रूप for x in $(cat file); do …से लाइनों पर पुनरावृति करने के लिए काम नहीं करता है file, क्योंकि शेल पहले कमांड को आउटपुट को cat fileकहीं से भी व्हाट्सएप पर विभाजित करता है, और फिर प्रत्येक शब्द को एक ग्लोब पैटर्न के रूप में मानता है ताकि \[?*आगे विस्तार हो। for x in $(cat file)यदि आप इस पर काम करते हैं तो आप सुरक्षित बना सकते हैं :

set -f
IFS='
'
for x in $(cat file); do 

संबंधित पढ़ना: नामों में रिक्त स्थान के साथ फ़ाइलों के माध्यम से लूपिंग? ; मैं बैश में एक चर से लाइन द्वारा लाइन कैसे पढ़ सकता हूं? ; इसके बजाय अक्सर ऐसा क्यों while IFS= readकिया जाता है IFS=; while read..? ध्यान दें कि उपयोग करते समय while read, लाइनों को पढ़ने के लिए सुरक्षित सिंटैक्स है while IFS= read -r line; do …

अब बारी आती है कि आपके while readप्रयास में क्या गलत है । सर्वर सूची फ़ाइल से पुनर्निर्देशन पूरे लूप पर लागू होता है। इसलिए जब sshचलता है, तो इसका मानक इनपुट उस फाइल से आता है। Ssh क्लाइंट को पता नहीं चल सकता है कि रिमोट एप्लीकेशन कब अपने स्टैंडर्ड इनपुट से पढ़ना चाहता है। इसलिए जैसे ही ssh क्लाइंट कुछ इनपुट को नोटिस करता है, वह उस इनपुट को रिमोट साइड में भेजता है। Ssh सर्वर तब दूरस्थ कमांड को उस इनपुट को खिलाने के लिए तैयार है, क्या उसे यह चाहिए। आपके मामले में, रिमोट कमांड कभी भी कोई इनपुट नहीं पढ़ता है, इसलिए डेटा समाप्त हो जाता है, लेकिन ग्राहक पक्ष को इसके बारे में कुछ भी पता नहीं है। echoकाम के साथ आपका प्रयास echoकभी भी किसी इनपुट को नहीं पढ़ता है, यह अपने मानक इनपुट को अकेला छोड़ देता है।

कुछ तरीके हैं जिनसे आप इससे बच सकते हैं। आप ssh को बता सकते हैं कि -nविकल्प के साथ मानक इनपुट से न पढ़ें ।

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

-nवास्तव में विकल्प बताता sshसे अपने इनपुट रीडायरेक्ट करने के लिए /dev/null। आप ऐसा शेल स्तर पर कर सकते हैं, और यह किसी भी कमांड के लिए काम करेगा।

while read server; do
  ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt

फाइल से आने वाले ssh के इनपुट से बचने के लिए एक आकर्षक तरीका है readकमांड पर रीडायरेक्शन डालना while read server </home/kenny/list_of_servers.txt; do …। यह काम नहीं करेगा, क्योंकि यह हर बार readकमांड के निष्पादित होने पर फ़ाइल को फिर से खोलने का कारण बनता है (इसलिए यह फ़ाइल की पहली पंक्ति को बार-बार पढ़ेगा)। लूप पर पुनर्निर्देशन पूरे समय पर होना चाहिए ताकि लूप की अवधि के लिए फ़ाइल एक बार खोली जाए।

सामान्य समाधान मानक इनपुट के अलावा फ़ाइल डिस्क्रिप्टर पर लूप को इनपुट प्रदान करना है । शेल में एक डिस्क्रिप्टर नंबर से दूसरे इनपुट और आउटपुट को फ़ेरी करने के लिए निर्माण होता है। यहां, हम फाइल डिस्क्रिप्टर 3 पर फाइल को खोलते हैं, और readकमांड डिस्क्रिप्टर के इनपुट को फाइल डिस्क्रिप्टर 3 से रीडायरेक्ट करते हैं । ssh क्लाइंट ओपन नॉन-स्टैंडर्ड डिस्क्रिप्टर को अनदेखा करता है, इसलिए सब ठीक है।

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt

बैश में, readकमांड के पास एक अलग फ़ाइल डिस्क्रिप्टर से पढ़ने के लिए एक विशिष्ट विकल्प होता है, जिससे आप लिख सकते हैं read -u3 server

संबंधित पढ़ना: फ़ाइल विवरणकर्ता और शेल स्क्रिप्टिंग ; आप अतिरिक्त फ़ाइल डिस्क्रिप्टर का उपयोग कब करेंगे?


5
यार, यह स्टैकएक्सचेंज सुनिश्चित सर्वरफॉल्ट से अलग है। यहां के लोग वास्तव में न केवल जवाब देने के लिए, बल्कि शिक्षित भी हैं। बहुत बहुत धन्यवाद।
केनी रस्साचारर्ट

26

आपको उपयोग करना चाहिए while, नहींfor । इस तरह के लूप में मानक इनपुट निगलने से बचने का तरीका बस एक और फ़ाइल विवरणक का उपयोग करना है :

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

अधिक जानकारी के लिए, help [r]ead( वास्तव में ) और एक अन्य लेख की व्याख्या क्यों


1
दिलचस्प है, मैंने पहले कभी फ़ाइल डिस्क्रिप्टर का उपयोग नहीं किया है। -uझंडा क्या करता है? मुझे यकीन नहीं है कि मैं किस मैन पेज को देखने वाला हूं।
केनी रासचेर्ट 13

रीड कमांड शेल बैश का हिस्सा है, इसलिए यह रीड पैराग्राफ में "SHELL BUILTIN COMMANDS" सेक्शन में बैश के मैन पेज में है। आपको "फ़ाइल विवरणक fd से -u fd पढ़ें इनपुट मिलेगा।" इस पैरा में
f4m8

6
वैकल्पिक रूप से help read- शेल बिल्डरों के लिए पृष्ठों helpके बराबर प्राप्त करने के लिए कमांड है man
l0b0

22

आपके पहले कोड sshमें STDIN को "चोरी" करेगा while। इससे बचने के -nलिए विकल्प जोड़ें ssh। से man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).

ठीक है, किसी भी विचार है कि यह लूप के लिए क्यों नहीं होता है?
केनी रस्साचर

7
क्योंकि forलूप डेटा को पैरामीटर के रूप में प्राप्त करता है, इनपुट के रूप में नहीं।
मैनटवर्क

1
आप, श्रीमान, सज्जन और विद्वान हैं।
केनी रस्साचर

0

डिफ़ॉल्ट मानक इनपुट हैंडलिंग sshनालियों की लूप से शेष रेखा को संभालती है ।

इस समस्या से बचने के लिए, जहाँ समस्याग्रस्त कमांड मानक इनपुट पढ़ता है, उसे बदल दें। यदि कमांड के लिए कोई मानक इनपुट की आवश्यकता नहीं है, तो विशेष /dev/nullउपकरण से मानक इनपुट पढ़ें ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.