इसे निष्पादित करने से पहले पूरे शेल स्क्रिप्ट को कैसे पढ़ें?


35

आमतौर पर, यदि आप एक स्क्रिपिट को संपादित करते हैं, तो स्क्रिप्ट के सभी चल रहे त्रुटियों में त्रुटियां होती हैं।

जहां तक ​​मैं इसे समझता हूं, bash (अन्य गोले भी?) स्क्रिप्ट को वृद्धिशील रूप से पढ़ें, इसलिए यदि आपने स्क्रिप्ट फ़ाइल को बाहरी रूप से संशोधित किया है, तो यह गलत सामान पढ़ना शुरू कर देता है। क्या इसे रोकने का कोई तरीका है?

उदाहरण:

sleep 20

echo test

यदि आप इस स्क्रिप्ट को निष्पादित करते हैं, तो बैश पहली पंक्ति (10 बाइट्स कहें) पढ़ेगा और सो जाएगा। जब यह फिर से शुरू होता है, तो 10 वीं बाइट पर शुरू होने वाली स्क्रिप्ट में अलग-अलग सामग्री हो सकती है। मैं नई स्क्रिप्ट में एक पंक्ति के बीच में हो सकता हूं। इस प्रकार चल रही स्क्रिप्ट को तोड़ दिया जाएगा।


"स्क्रिप्ट को बाहरी रूप से संशोधित करने" से आपका क्या मतलब है?
मौलिंग्लावन्स

1
हो सकता है कि किसी फ़ंक्शन या किसी चीज़ में सभी सामग्रियों को लपेटने का एक तरीका है, इसलिए शेल पहले पूरी स्क्रिप्ट पढ़ेगा? लेकिन उस अंतिम पंक्ति के बारे में क्या है जहां आप फ़ंक्शन को लागू करते हैं, क्या इसे ईओएफ तक पढ़ा जाएगा? हो सकता है कि आखिरी चूक \nसे चाल चली जाए? शायद एक उपधारा ()करेंगे? मैं इसके साथ बहुत अनुभवी नहीं हूँ, कृपया मदद करें!
वास्यानोविकोव

@maulinglawns यदि स्क्रिप्ट में सामग्री है sleep 20 ;\n echo test ;\n sleep 20और मैं इसे संपादित करना शुरू करता हूं, तो यह दुर्व्यवहार हो सकता है। उदाहरण के लिए, बैश स्क्रिप्ट के पहले 10 बाइट्स पढ़ सकता है, sleepकमांड को समझ सकता है और सो सकता है। इसे फिर से शुरू करने के बाद, 10 बाइट्स से शुरू होने वाली फ़ाइल में अलग-अलग सामग्री होगी।
वास्यानोविकोव

1
तो, आप जो कह रहे हैं वह यह है कि आप एक स्क्रिप्ट को संपादित कर रहे हैं जो निष्पादित हो रही है? स्क्रिप्ट को पहले बंद करें, अपना संपादन करें, और फिर इसे फिर से शुरू करें।
मौलिंग्लवन्स

@maulinglawns हां, यह मूल रूप से है। समस्या यह है कि स्क्रिप्ट को रोकना मेरे लिए सुविधाजनक नहीं है, और ऐसा करना हमेशा याद रखना कठिन है। हो सकता है कि पहले पूरी स्क्रिप्ट पढ़ने के लिए बाध्य करने का कोई तरीका हो?
वासनीनोविकोव

जवाबों:


43

हां गोले, और bashविशेष रूप से, एक समय में फ़ाइल को एक पंक्ति पढ़ने के लिए सावधान किया जाता है, इसलिए यह उसी तरह काम करता है जब आप इसे अंतःक्रियात्मक रूप से उपयोग करते हैं।

आप देखेंगे कि जब फ़ाइल खोज योग्य नहीं है (जैसे कि पाइप), bashयहां तक ​​कि एक बार में एक बाइट पढ़ता है , तो यह सुनिश्चित करें कि \nचरित्र को अतीत में न पढ़ें । जब फ़ाइल खोजी जा सकती है, तो यह एक बार में पूर्ण ब्लॉक पढ़कर अनुकूलित करता है, लेकिन बाद में वापस आ जाता है \n

इसका मतलब है कि आप इस तरह की चीजें कर सकते हैं:

bash << \EOF
read var
var's content
echo "$var"
EOF

या खुद को अपडेट करने वाली स्क्रिप्ट लिखें। यदि आप उस गारंटी को नहीं देते हैं तो आप ऐसा नहीं कर पाएंगे।

अब, यह दुर्लभ है कि आप उस तरह की चीजें करना चाहते हैं और, जैसा कि आपको पता चला है, यह सुविधा उस तरीके से प्राप्त करने के लिए अधिक बार उपयोगी होती है।

इससे बचने के लिए, आप कोशिश कर सकते हैं और सुनिश्चित करें कि आप फ़ाइल को इन-प्लेस (उदाहरण के लिए, कॉपी को संशोधित करें, और कॉपी को जगह में स्थानांतरित न करें (जैसे sed -iया perl -piकुछ संपादक उदाहरण के लिए करते हैं)।

या आप अपनी स्क्रिप्ट लिख सकते हैं जैसे:

{
  sleep 20
  echo test
}; exit

(ध्यान दें कि यह महत्वपूर्ण है कि एक exitही पंक्ति के रूप में हो }; हालांकि आप इसे बंद करने से ठीक पहले ब्रेसिज़ के अंदर भी डाल सकते हैं)।

या:

main() {
  sleep 20
  echo test
}
main "$@"; exit

शेल को exitकुछ भी करने से पहले स्क्रिप्ट को पढ़ने तक की आवश्यकता होगी । यह सुनिश्चित करता है कि शेल स्क्रिप्ट से दोबारा नहीं पढ़ा जाएगा।

इसका मतलब है कि पूरी स्क्रिप्ट को स्मृति में संग्रहीत किया जाएगा।

यह स्क्रिप्ट के पार्सिंग को भी प्रभावित कर सकता है।

उदाहरण के लिए, इसमें bash:

export LC_ALL=fr_FR.UTF-8
echo $'St\ue9phane'

यूटीएफ -8 में यू + 00E9 एन्कोडेड आउटपुट करेगा। हालाँकि, यदि आप इसे इसमें बदलते हैं:

{
  export LC_ALL=fr_FR.UTF-8
  echo $'St\ue9phane'
}

\ue9चारसेट उस समय प्रभाव में था कि आदेश पार्स किया गया था इस मामले में है, जिसमें विस्तार किया जाएगा से पहलेexport आदेश निष्पादित किया जाता है।

यह भी ध्यान दें कि यदि sourceउर्फ .कमांड का उपयोग कुछ गोले के साथ किया जाता है, तो आपको खट्टी फाइलों के लिए एक ही तरह की समस्या होगी।

bashहालांकि यह ऐसा नहीं है जिसकी sourceकमान इंटरप्रिट करने से पहले फाइल को पूरी तरह से पढ़ ले। यदि bashविशेष रूप से लेखन के लिए , आप वास्तव में स्क्रिप्ट के प्रारंभ में जोड़कर, इसका उपयोग कर सकते हैं:

if [[ ! $already_sourced ]]; then
  already_sourced=1
  source "$0"; exit
fi

(मैं उस पर भरोसा नहीं करूंगा, हालांकि जैसा कि आप भविष्य के संस्करणों की कल्पना bashकर सकते हैं, उस व्यवहार को बदल सकते हैं जिसे वर्तमान में एक सीमा के रूप में देखा जा सकता है (bash और AT & T ksh केवल POSIX जैसे गोले हैं जो इस तरह का व्यवहार करते हैं, जहां तक ​​वे बता सकते हैं) और already_sourcedचाल थोड़ी भंगुर है क्योंकि यह मानता है कि चर वातावरण में नहीं है, यह उल्लेख नहीं है कि यह BASH_SOURCE चर की सामग्री को प्रभावित करता है)


@VasyaNovikov, इस समय एसई के साथ कुछ गलत हो रहा है (या कम से कम मेरे लिए)। जवाब देने के लिए कुछ ही थे जब मैंने अपना जोड़ा, और आपकी टिप्पणी केवल अब बदल गई है, भले ही यह कहता हो कि यह 16 मिनट पहले पोस्ट किया गया था (या शायद यह सिर्फ मुझे मेरी कमी खो रहा है)। फिर भी, अतिरिक्त "बाहर निकलने" पर ध्यान दें, जब फ़ाइल का आकार बढ़ने पर समस्याओं से बचने के लिए यहाँ आवश्यक है (जैसा कि मैंने आपके उत्तर में जोड़ा गया है टिप्पणी में उल्लेख किया है)।
स्टीफन चेज़लस

स्टीफन, मुझे लगता है कि मुझे एक और समाधान मिल गया है। इसका उपयोग करना है }; exec true। इस तरह, फ़ाइल के अंत में newlines पर कोई आवश्यकता नहीं है, जो कुछ संपादकों (जैसे emacs) के अनुकूल है। वे सभी परीक्षण जिनके साथ मैं सही ढंग से काम करने के बारे में सोच सकता था}; exec true
वास्यानोविकोव

@VasyaNovikov, निश्चित नहीं कि आपका क्या मतलब है। यह कैसे बेहतर है }; exit? आप बाहर निकलने की स्थिति भी खो रहे हैं।
स्टीफन चेजेलस

जैसा कि एक अलग प्रश्न में उल्लेख किया गया है: यह पूरी फ़ाइल को पहले पार्स करने के लिए आम है और फिर डॉट कमांड ( . script) का उपयोग किए जाने पर कंपाउंड स्टेटमेंट को निष्पादित करें ।
शास्त्री

@ सामान्य रूप से, हां, मैं इस जवाब में एटी एंड टी केश और बैश की सीमा के रूप में उल्लेख करता हूं। अन्य POSIX- प्रकार के गोले में वह सीमा नहीं होती है।
स्टीफन चेज़लस

12

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

आरसीएस जैसी सरल संस्करण नियंत्रण प्रणाली का उपयोग करके जो आसानी से विम और एमएसीएस के लिए उपलब्ध है, आपको अपने परिवर्तनों का इतिहास होने का दोहरा लाभ मिलता है, और चेकआउट सिस्टम को वर्तमान फ़ाइल को डिफ़ॉल्ट रूप से हटाकर इसे सही मोड के साथ फिर से बनाना चाहिए। (पाठ्यक्रम की ऐसी फाइलों को हार्ड-लिंक करने से सावधान रहें)।


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

11

सबसे सरल समाधान:

{
  ... your code ...

  exit
}

इस तरह, बैश {}इसे निष्पादित करने से पहले पूरे ब्लॉक को पढ़ेगा , और exitनिर्देश यह सुनिश्चित करेगा कि कोड ब्लॉक के बाहर कुछ भी नहीं पढ़ा जाएगा।

यदि आप स्क्रिप्ट को "निष्पादित" नहीं करना चाहते हैं, बल्कि इसे "स्रोत" करना चाहते हैं, तो आपको एक अलग समाधान की आवश्यकता है। यह तब काम करना चाहिए:

{
  ... your code ...

  return 2>/dev/null || exit
}

या यदि आप निकास कोड पर सीधे नियंत्रण चाहते हैं:

{
  ... your code ...

  ret="$?";return "$ret" 2>/dev/null || exit "$ret"
}

देखा! यह स्क्रिप्ट संपादित, स्रोत और निष्पादित करने के लिए सुरक्षित है। आपको अभी भी यह सुनिश्चित करना होगा कि आप इसे उन मिलीसेकंड में संशोधित न करें जब यह शुरू में पढ़ा जा रहा हो।


1
मैंने पाया कि यह ईओएफ को नहीं देखता है और फ़ाइल को पढ़ना बंद कर देता है, लेकिन यह अपने "बफर स्ट्रीम" प्रसंस्करण में उलझ जाता है और फ़ाइल के अंत की मांग करते हुए समाप्त हो जाता है, यही कारण है कि इसका आकार ठीक है फ़ाइल बहुत अधिक बढ़ जाती है, लेकिन जब आप फ़ाइल को पहले की तुलना में दोगुने से अधिक बनाते हैं तो यह बहुत बुरा लगता है। मैं जल्द ही बैश मेंटेनर्स को बग की सूचना दूंगा।
स्टीफन चेज़लस


टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
terdon

5

अवधारणा के सुबूत। यहाँ एक स्क्रिप्ट है जो खुद को संशोधित करती है:

cat <<EOF >/tmp/scr
#!/bin/bash
sed  s/[k]ept/changed/  /tmp/scr > /tmp/scr2

# this next line overwites the on disk copy of the script
cat /tmp/scr2 > /tmp/scr
# this line ends up changed.
echo script content kept
EOF
chmod u+x /tmp/scr
/tmp/scr

हम परिवर्तित संस्करण प्रिंट देखते हैं

ऐसा इसलिए है क्योंकि बैश लोड स्क्रिप्ट को खोलने के लिए एक फ़ाइल हैंडल रखता है, इसलिए फ़ाइल में परिवर्तन तुरंत दिखाई देगा।

यदि आप इन-मेमोरी कॉपी अपडेट नहीं करना चाहते हैं, तो मूल फ़ाइल को अनलिंक करें और उसे बदल दें।

ऐसा करने का एक तरीका sed -i का उपयोग करना है।

sed -i '' filename

अवधारणा के सुबूत

cat <<EOF >/tmp/scr
#!/bin/bash
sed  s/[k]ept/changed/  /tmp/scr > /tmp/scr2

# this next line unlinks the original and creates a new copy.
sed -i ''  /tmp/scr

# now overwriting it has no immediate effect
cat /tmp/scr2 > /tmp/scr
echo script content kept
EOF

chmod u+x /tmp/scr
/tmp/scr

यदि आप स्क्रिप्ट बदलने के लिए एक संपादक का उपयोग कर रहे हैं, तो "बैकअप बैकअप रखें" सुविधा को सक्षम करने के लिए संपादक को मौजूदा फ़ाइल को ओवरराइट करने के बजाय परिवर्तित संस्करण को एक नई फ़ाइल में लिखने की आवश्यकता हो सकती है।


2
नहीं, bashफ़ाइल को नहीं खोलता है mmap()। जब जरूरत होती है तो बस एक लाइन को पढ़ने की सावधानी बरतें, ठीक उसी तरह जैसे किसी टर्मिनल डिवाइस से कमांड्स को इंटरेक्टिव होने पर मिलती है।
स्टीफन चेज़लस 12

2

अपनी स्क्रिप्ट को ब्लॉक में लपेटना {}सबसे अच्छा विकल्प है, लेकिन अपनी स्क्रिप्ट को बदलने की आवश्यकता है।

F=$(mktemp) && cp test.sh $F && bash $F; rm $F;

दूसरा सबसे अच्छा विकल्प होगा ( tmpfs को मानते हुए ) नुकसान यह है कि यदि आपकी स्क्रिप्ट इसका उपयोग करती है तो यह $ 0 का ब्रेक होता है।

कुछ का उपयोग करना F=test.sh; tail -n $(cat "$F" | wc -l) "$F" | bashकम आदर्श है क्योंकि इसमें पूरी फ़ाइल को मेमोरी में रखना पड़ता है और $ 0 टूट जाता है।

मूल फ़ाइल को छूने से बचा जाना चाहिए ताकि, अंतिम संशोधित समय, ताले पढ़ें, और हार्ड लिंक परेशान न हों। इस तरह आप फ़ाइल को चलाते समय एक संपादक को खुला छोड़ सकते हैं और rsync बैकअप और हार्ड लिंक फ़ंक्शन के लिए अपेक्षित रूप से फ़ाइल की अनावश्यक जांच नहीं करेगा।

फाइल को एडिट पर बदलना काम करेगा लेकिन यह कम मजबूत है क्योंकि यह अन्य स्क्रिप्ट / यूजर्स / या किसी के लिए लागू नहीं हो सकता है। और फिर से यह कड़ी को तोड़ देगा।


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