यदि आप किसी स्क्रिप्ट को निष्पादन के दौरान संपादित करते हैं तो क्या होता है?


31

मेरे पास एक सामान्य प्रश्न है, जो गलतफहमी का एक परिणाम हो सकता है कि लिनक्स में प्रक्रियाओं को कैसे नियंत्रित किया जाता है।

अपने उद्देश्यों के लिए मैं एक 'स्क्रिप्ट' को परिभाषित करने जा रहा हूं, जो टेक्स्ट फाइल में सेव किए गए बैश कोड के स्निपेट के रूप में है, जो वर्तमान उपयोगकर्ता के लिए सक्षम अनुमतियों को निष्पादित करता है।

मेरे पास स्क्रिप्ट की एक श्रृंखला है जो एक दूसरे को मिलकर कहते हैं। सादगी की खातिर मैं उन्हें स्क्रिप्ट A, B, और C. स्क्रिप्ट कहूंगा। A कथन की एक श्रृंखला को आगे बढ़ाता है और फिर उसे रोक देता है, फिर वह स्क्रिप्ट B को निष्पादित करता है, फिर वह रुक जाता है, फिर वह पटकथा C. निष्पादित करता है। दूसरे शब्दों में, श्रृंखला कदम कुछ इस तरह है:

स्क्रिप्ट A चलाएं:

  1. बयानों की श्रृंखला
  2. ठहराव
  3. स्क्रिप्ट बी चलाएं
  4. ठहराव
  5. स्क्रिप्ट सी चलाएं

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

यहाँ असली सवाल तब है, क्या स्क्रिप्ट ए को संपादित करने का कोई तरीका है जबकि यह अभी भी चल रहा है? या इसका निष्पादन शुरू होने के बाद संपादन असंभव है?


2
मुझे लगता है कि यह शेल पर निर्भर करेगा। यद्यपि आप कहते हैं कि आप बैश का उपयोग कर रहे हैं। ऐसा लगता है कि यह उस तरीके पर निर्भर करेगा जिस तरह से शेल आंतरिक रूप से स्क्रिप्ट को लोड करता है।
4

यदि आप फ़ाइल को निष्पादित करने के बजाय स्रोत को बदलते हैं तो व्यवहार भी बदल सकता है।
स्ट्रगलर

1
मुझे लगता है कि मार से पहले पूरी स्क्रिप्ट को मेमोरी में पढ़ा जाता है।
w4etwetewtwet

2
@ खंडू, नहीं, यह नहीं है। जब तक आप दर्ज किए गए आदेशों की व्याख्या करना शुरू करने के लिए संकेत पर "बाहर निकलने" टाइप नहीं करते तब तक यह इंतजार नहीं करता है।
स्टीफन चेजलस

1
@StephaneChazelas हाँ, यह उस स्क्रिप्ट को चलाने के लिए अलग है, लेकिन टर्मिनल से पढ़ना नहीं है।
w4etwetewtwet

जवाबों:


21

यूनिक्स में, अधिकांश संपादक एक नई अस्थायी फ़ाइल बनाते हैं जिसमें संपादित सामग्री होती है। जब संपादित फ़ाइल सहेज ली जाती है, तो मूल फ़ाइल हटा दी जाती है और अस्थायी फ़ाइल का नाम बदलकर मूल नाम रख दिया जाता है। (निश्चित रूप से, डटलॉस को रोकने के लिए विभिन्न सुरक्षा उपाय हैं।) यह उदाहरण के लिए, ("इन-प्लेस") झंडे के साथ sedया perlजब इनवाइस किया जाता है -i, जो वास्तव में "इन-प्लेस" नहीं है। इसे "पुराने नाम के साथ नया स्थान" कहा जाना चाहिए था।

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

[नोट: सभी मानकों-आधारित टिप्पणियों के साथ, उपरोक्त कई व्याख्याओं के अधीन है और विभिन्न कोने-मामले हैं, जैसे कि एनएफएस। अपवाद के साथ टिप्पणियों को भरने के लिए बच्चों का स्वागत है।]

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

इसलिए, यदि आप शेल द्वारा संसाधित होने के दौरान फ़ाइल को संपादित करना चाहते हैं, तो आपके पास दो विकल्प हैं:

  1. आप फ़ाइल में संलग्न कर सकते हैं। यह हमेशा काम करना चाहिए।

  2. आप फ़ाइल को उसी लंबाई की नई सामग्री के साथ अधिलेखित कर सकते हैं । यह कार्य हो सकता है या नहीं, इस पर निर्भर करता है कि शेल फ़ाइल के उस हिस्से को पहले ही पढ़ चुका है या नहीं। चूंकि अधिकांश फ़ाइल I / O में पढ़ने वाले बफ़र्स शामिल हैं, और चूंकि सभी गोले मुझे पता है कि इसे निष्पादित करने से पहले एक संपूर्ण यौगिक कमांड पढ़ा जाता है, यह बहुत संभावना नहीं है कि आप इस से दूर हो सकते हैं। यह निश्चित रूप से विश्वसनीय नहीं होगा।

मैं Posix मानक में किसी भी शब्द के बारे में नहीं जानता, जिसके लिए फ़ाइल को निष्पादित करते समय वास्तव में एक स्क्रिप्ट फ़ाइल में संलग्न होने की संभावना की आवश्यकता होती है, इसलिए यह हर Posix अनुरूप शेल के साथ काम नहीं कर सकता है, लगभग वर्तमान की पेशकश के साथ बहुत कम- और कभी-कभी-पॉज़िक्‍स-कंप्‍लीट गोले। तो YMMV। लेकिन जहां तक ​​मुझे पता है, यह बैश के साथ मज़बूती से काम करता है।

सबूत के रूप में, यहां बैश में बीयर कार्यक्रम की कुख्यात 99 बोतलों का "लूप-मुक्त" कार्यान्वयन है, जो ddओवरराइट करने और अपेंड करने के लिए उपयोग करता है (ओवरराइटिंग संभवतः सुरक्षित है क्योंकि यह वर्तमान में निष्पादित लाइन को प्रतिस्थापित करता है, जो हमेशा की अंतिम पंक्ति होती है। फ़ाइल, बिल्कुल समान लंबाई की टिप्पणी के साथ; मैंने ऐसा इसलिए किया ताकि अंतिम परिणाम को आत्म-संशोधित व्यवहार के बिना निष्पादित किया जा सके।)

#!/bin/bash
if [[ $1 == reset ]]; then
  printf "%s\n%-16s#\n" '####' 'next ${1:-99}' |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^#### $0 | cut -f1 -d:) bs=1 2>/dev/null
  exit
fi

step() {
  s=s
  one=one
  case $beer in
    2) beer=1; unset s;;
    1) beer="No more"; one=it;;
    "No more") beer=99; return 1;;
    *) ((--beer));;
  esac
}
next() {
  step ${beer:=$(($1+1))}
  refrain |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^next\  $0 | cut -f1 -d:) bs=1 conv=notrunc 2>/dev/null
}
refrain() {
  printf "%-17s\n" "# $beer bottles"
  echo echo ${beer:-No more} bottle$s of beer on the wall, ${beer:-No more} bottle$s of beer.
  if step; then
    echo echo Take $one down, pass it around, $beer bottle$s of beer on the wall.
    echo echo
    echo next abcdefghijkl
  else
    echo echo Go to the store, buy some more, $beer bottle$s of beer on the wall.
  fi
}
####
next ${1:-99}   #

जब मैं इसे चलाता हूं, तो यह "नहीं और अधिक" से शुरू होता है, फिर -1 और नकारात्मक संख्या में अनिश्चित काल तक जारी रहता है।
डैनियल हर्शकोविच

यदि मैं export beer=100स्क्रिप्ट चलाने से पहले करता हूं , तो यह उम्मीद के मुताबिक काम करता है।
डैनियल हर्शकोविच

@ डैनियल हर्शकोविच: काफी सही; मेरी ओर से मैला परीक्षण। मुझे लगता है कि मैंने इसे ठीक कर लिया; अब यह एक वैकल्पिक गणना पैरामीटर लेता है। एक बेहतर और अधिक दिलचस्प फिक्स स्वचालित रूप से रीसेट करना होगा यदि पैरामीटर कैश्ड कॉपी के साथ मेल नहीं खाता है।
रिक्की

18

bash यह सुनिश्चित करने के लिए एक लंबा रास्ता तय करता है कि यह उन्हें निष्पादित करने से ठीक पहले कमांड पढ़ता है।

उदाहरण के लिए:

cmd1
cmd2

शेल स्क्रिप्ट को ब्लॉक से पढ़ेगा, इसलिए संभवतः दोनों कमांड को पढ़ें, पहले एक की व्याख्या करें और फिर cmd1स्क्रिप्ट के अंत में वापस जाएं cmd2और इसे पढ़ने और निष्पादित करने के लिए स्क्रिप्ट को फिर से पढ़ें ।

आप इसे आसानी से सत्यापित कर सकते हैं:

$ cat a
echo foo | dd 2> /dev/null bs=1 seek=50 of=a
echo bar
$ bash a
foo

(हालांकि straceउस पर आउटपुट को देखते हुए, ऐसा लगता है कि यह कुछ और फैंसी चीजें करता है (जैसे कि डेटा को कई बार पढ़ते हैं, वापस चाहते हैं ...) की तुलना में जब मैंने कुछ साल पहले भी ऐसा ही प्रयास किया था, तो ऊपर दिए गए मेरे बयान के बारे में पता चल सकता है अब नए संस्करणों पर लागू नहीं होता है)।

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

{
  cmd1
  cmd2
  exit
}

खोल को समापन तक पढ़ना होगा }, मेमोरी में स्टोर करना होगा और इसे निष्पादित करना होगा। के कारण exit, शेल फिर से स्क्रिप्ट से नहीं पढ़ेगा इसलिए आप इसे सुरक्षित रूप से संपादित कर सकते हैं जबकि शेल इसे व्याख्या कर रहा है।

वैकल्पिक रूप से, स्क्रिप्ट को संपादित करते समय, सुनिश्चित करें कि आप स्क्रिप्ट की एक नई प्रति लिखें। शेल मूल एक को पढ़ता रहेगा (भले ही वह हटा दिया गया हो या नाम बदला गया हो)।

ऐसा करने के लिए, नाम बदलने the-scriptके लिए the-script.oldऔर नकल the-script.oldकरने के लिए the-scriptऔर इसे संपादित करें।


4

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

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


4

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

#! /bin/bash

CMD="$0"
ARGS=("$@")

trap reexec 1

reexec() {
    exec "$CMD" "${ARGS[@]}"
}

while : ; do sleep 1 ; clear ; date ; done

यह स्क्रीन पर दिनांक प्रदर्शित करना जारी रखेगा। मैं तो मेरी स्क्रिप्ट और परिवर्तन को संपादित कर सकता dateकरने के लिए echo "Date: $(date)"। यह लिखने पर कि चल रही स्क्रिप्ट अभी भी केवल दिनांक प्रदर्शित करती है। यदि मैं सिग्नल trapको कैप्चर करने के लिए सेट किया हुआ संकेत भेजता हूं , तो स्क्रिप्ट स्क्रिप्ट exec(कमांड के साथ वर्तमान रनिंग प्रक्रिया को बदल देता है) जो कि कमांड $CMDऔर दलीलें हैं, कैसे होगी $@। आप इसे जारी करके कर सकते हैं kill -1 PID- जहां पीआईडी ​​रनिंग स्क्रिप्ट का पीआईडी ​​है - और आउटपुट आउटपुट कमांड कमांड Date:से पहले दिखाने के लिए बदलता है date

आप अपनी स्क्रिप्ट की "स्थिति" को बाहरी फ़ाइल (कह / tmp) में संग्रहीत कर सकते हैं, और यह जानने के लिए कि कार्यक्रम फिर से निष्पादित हो रहा है, पर "फिर से शुरू" करने के लिए सामग्री को पढ़ें। फिर आप एक अतिरिक्त जाल समाप्ति (SIGINT / SIGQUIT / SIGKILL / SIGTERM) जोड़ सकते हैं ताकि उस tmp फ़ाइल को खाली कर सकें जब आप "स्क्रिप्ट A" को बाधित करने के बाद पुनः आरंभ करते हैं तो यह शुरुआत से शुरू होगा। एक स्टेटफुल वर्जन कुछ इस तरह होगा:

#! /bin/bash

trap reexec 1
trap cleanup 2 3 9 15

CMD="$0"
ARGS=("$@")
statefile='/tmp/scriptA.state'
EXIT=1

reexec() { echo "Restarting..." ; exec "$CMD" "${ARGS[@]}"; }
cleanup() { rm -f $statefile; exit $EXIT; }
run_scriptB() { /path/to/scriptB; echo "scriptC" > $statefile; }
run_scriptC() { /path/to/scriptC; echo "stop" > $statefile;  }

while [ "$state" != "stop" ] ; do

    if [ -f "$statefile" ] ; then
        state="$(cat "$statefile")"
    else
        state='starting'
    fi

    case "$state" in
        starting)         
            run_scriptB
        ;;
        scriptC)
            run_scriptC
        ;;
    esac
done

EXIT=0
cleanup

मैं पर कब्जा कर कि इस मुद्दे को ठीक करने के बाद $0और $@स्क्रिप्ट के शुरू में और में उन वैरिएबल का उपयोग कर execके बजाय।
द्रविण स्लोअन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.