एक ही फाइल को एक ही पाइपलाइन में पढ़ने और लिखने को हमेशा "विफल" कैसे बनाएं?


9

कहो कि मेरे पास निम्नलिखित स्क्रिप्ट है:

#!/bin/bash
for i in $(seq 1000)
do
    cp /etc/passwd tmp
    cat tmp | head -1 | head -1 | head -1 > tmp  #this is the key line
    cat tmp
done

मुख्य पंक्ति पर, मैं वही फ़ाइल पढ़ता और लिखता हूं tmpजो कभी-कभी विफल हो जाती है।

(मैंने पढ़ा है कि यह दौड़ की स्थिति के कारण है क्योंकि पाइप लाइन में प्रक्रियाओं को समानांतर में निष्पादित किया जाता है, जो मुझे समझ में नहीं आता है - प्रत्येक headको पिछले एक से डेटा लेने की आवश्यकता है, है ना? यह मेरा मुख्य सवाल नहीं है? लेकिन आप इसका जवाब भी दे सकते हैं।)

जब मैं स्क्रिप्ट चलाता हूं, तो यह लगभग 200 लाइनों को आउटपुट करता है। क्या कोई ऐसा तरीका है जिससे मैं इस स्क्रिप्ट को हमेशा 0 लाइनों में आउटपुट करने के लिए मजबूर कर सकता हूं (इसलिए I / O पुनर्निर्देशन tmpहमेशा पहले तैयार किया जाता है और इसलिए डेटा हमेशा नष्ट हो जाता है)? स्पष्ट होने के लिए, मेरा मतलब है कि इस स्क्रिप्ट को नहीं, सिस्टम सेटिंग्स को बदलना।

आपके विचारों के लिए धन्यवाद।

जवाबों:


2

गिल्स का जवाब रेस की स्थिति के बारे में बताता है। मैं इस भाग का उत्तर देने जा रहा हूँ:

क्या कोई ऐसा तरीका है जिससे मैं इस स्क्रिप्ट को हमेशा 0 लाइनों में आउटपुट करने के लिए मजबूर कर सकता हूं (इसलिए I / O पुनर्निर्देशन को tmp में हमेशा पहले तैयार किया जाता है और इसलिए डेटा हमेशा नष्ट हो जाता है)? स्पष्ट होने के लिए, मेरा मतलब है कि सिस्टम सेटिंग्स बदलना

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

आप इसे चलाएंगे racechecker bash foo.sh

उसी सिस्टम-कॉल ट्रेसिंग / इंटरसेप्टिंग सुविधाओं strace -fका ltrace -fउपयोग करें जो हर बच्चे की प्रक्रिया में संलग्न करने के लिए उपयोग करें। (लिनक्स पर, यह ptraceGDB और अन्य डीबगर्स द्वारा ब्रेकपॉइंट्स, सिंगल स्टेप को सेट करने और किसी अन्य प्रक्रिया की मेमोरी / रजिस्टरों को संशोधित करने के लिए उपयोग की जाने वाली समान प्रणाली कॉल है।)

साधन openऔर openatसिस्टम कॉल: जब किसी भी प्रक्रिया के लिए इस उपकरण के तहत चल रहा एक बनाता है एक open(2)सिस्टम कॉल (या openat) के साथ O_RDONLY, नींद शायद 1/2 या 1 सेकंड के लिए। अन्य openसिस्टम कॉल (विशेषकर सहित O_TRUNC) को बिना देरी किए निष्पादित करें।

यह लेखक को लगभग हर दौड़ की स्थिति में दौड़ जीतने की अनुमति देनी चाहिए, जब तक कि सिस्टम लोड भी अधिक न हो, या यह एक जटिल दौड़ की स्थिति थी, जहां कुछ अन्य पढ़ने के बाद ट्रंकेशन नहीं हुआ। इतने यादृच्छिक रूपांतर जिनमें से open()s (और शायद read()s या लिखते हैं) में देरी हो रही है , इस उपकरण की पहचान शक्ति को बढ़ा सकते हैं, लेकिन निश्चित रूप से एक अनन्त समय के लिए परीक्षण के बिना एक देरी सिम्युलेटर के साथ जो अंततः सभी संभावित स्थितियों को कवर कर सकता है, जिसमें आप मुठभेड़ कर सकते हैं वास्तविक दुनिया, आप यह सुनिश्चित नहीं कर सकते कि आपकी स्क्रिप्ट दौड़ से मुक्त है जब तक आप उन्हें ध्यान से नहीं पढ़ते हैं और साबित करते हैं कि वे नहीं हैं।


आपको शायद इसमें श्वेतसूची की आवश्यकता होगी (देरी में नहीं open) फाइलों के लिए /usr/binऔर /usr/libइसलिए प्रक्रिया-स्टार्टअप हमेशा के लिए नहीं लेता है। (रनटाइम डायनेमिक लिंकिंग में open()कई फाइलें होती हैं (कुछ समय पर strace -eopen /bin/trueया देखें /bin/ls), हालाँकि यदि पैरेंट शेल खुद ट्रंकेशन कर रहा है, तो यह ठीक रहेगा। लेकिन इस टूल के लिए यह अभी भी अच्छा होगा कि स्क्रिप्ट को अनजाने में धीमा न करें।

या हो सकता है कि हर श्वेत सूची में आने वाली कॉलिंग प्रक्रिया में पहले स्थान पर छंटनी की अनुमति न हो। यानी ट्रेसिंग प्रक्रिया access(2)वास्तव में open()एक फाइल को चाहने वाली प्रक्रिया को निलंबित करने से पहले एक सिस्टम कॉल कर सकती है ।


racecheckerखुद को C में लिखा होगा, शेल में नहीं, लेकिन शायद straceशुरुआती बिंदु के रूप में कोड का उपयोग कर सकता है और इसे लागू करने में अधिक काम नहीं कर सकता है।

आप शायद FUSE फाइल सिस्टम के साथ समान कार्यक्षमता प्राप्त कर सकते हैं । वहाँ शायद एक शुद्ध passthrough फाइल सिस्टम का एक FUSE उदाहरण है, तो आप इसमें open()फ़ंक्शन को चेक जोड़ सकते हैं जो इसे केवल-पढ़ने के लिए सोता है, लेकिन ट्रंकेशन तुरंत होता है।


रेस चेकर के लिए आपका विचार वास्तव में काम नहीं करता है। सबसे पहले, वहाँ मुद्दा है कि टाइमआउट विश्वसनीय नहीं हैं: एक दिन दूसरे आदमी को आपकी अपेक्षा से अधिक समय लगेगा (यह निर्माण या परीक्षण स्क्रिप्ट के साथ एक क्लासिक मुद्दा है, जो थोड़ी देर के लिए काम करते हैं और फिर हार्ड-टू-डिबग तरीके से विफल हो जाते हैं जब कार्यभार का विस्तार होता है और कई चीजें समानांतर में चलती हैं)। लेकिन इससे आगे, आप किस विलंब को जोड़ने जा रहे हैं? किसी भी दिलचस्प चीज़ का पता लगाने के लिए, आपको विभिन्न विलंब पैटर्न के साथ कई रन बनाने होंगे और उनके परिणामों की तुलना करनी होगी।
गाइल्स का SO- बुराई का होना बंद करो '

@ गिल्स: ठीक है, किसी भी तरह से कम-देरी की गारंटी नहीं देता है कि ट्रंककेट दौड़ जीत जाएगा (एक भारी लोड की गई मशीन पर जैसा कि आप बताते हैं)। यहाँ विचार यह है कि आप इसका उपयोग अपनी स्क्रिप्ट का परीक्षण करने के लिए करते हैं, न कि यह कि आप racecheckerहर समय उपयोग करते हैं। और शायद आप बहुत अधिक भरी हुई मशीनों पर लोगों के लाभ के लिए कॉन्फ़िगर करने योग्य नींद का समय खोलना चाहते हैं जो इसे 10 सेकंड की तरह उच्चतर सेट करना चाहते हैं। या इसे कम सेट करें, जैसे कि लंबी या अकुशल स्क्रिप्ट के लिए 0.1 सेकंड जो फ़ाइलों को बहुत खोल देती हैं ।
पीटर कॉर्ड्स

@ गिल्स: विभिन्न देरी पैटर्न के बारे में महान विचार, जो आपको केवल एक ही-पाइप लाइन के सामान की तुलना में अधिक सरल दौड़ को पकड़ने की अनुमति दे सकता है, जो ओपी के मामले की तरह "स्पष्ट होना चाहिए (एक बार आपको पता है कि शेल कैसे काम करते हैं)"। लेकिन "जो खुलता है?" किसी भी केवल पढ़ने के लिए खुला, एक श्वेतसूची या प्रक्रिया स्टार्टअप में देरी न करने के लिए किसी अन्य तरीके से।
पीटर कॉर्ड्स

मुझे लगता है कि आप पृष्ठभूमि की नौकरियों के साथ अधिक जटिल दौड़ के बारे में सोच रहे हैं जो किसी अन्य प्रक्रिया के पूरा होने तक कम नहीं करते हैं? हां, इसे पकड़ने के लिए यादृच्छिक भिन्नता की आवश्यकता हो सकती है। या शायद प्रक्रिया पेड़ को देखो और देरी "जल्दी" अधिक पढ़ता है, सामान्य आदेश को पलटने की कोशिश करता है। आप अधिक से अधिक पुन: व्यवस्थित करने की संभावनाओं को अनुकरण करने के लिए उपकरण को अधिक से अधिक जटिल बना सकते हैं, लेकिन यदि आप मल्टी-टास्किंग कर रहे हैं तो कुछ बिंदुओं पर आपको अभी भी अपने कार्यक्रमों को सही ढंग से डिजाइन करना होगा। स्वचालित परीक्षण सरल लिपियों के लिए उपयोगी हो सकता है जहाँ संभव समस्याएं अधिक सीमित हैं।
पीटर कॉर्ड्स

यह बहु-थ्रेडेड कोड, विशेष रूप से लॉकलेस एल्गोरिदम का परीक्षण करने के समान है: यह सही होने के बारे में तार्किक तर्क बहुत महत्वपूर्ण है, साथ ही परीक्षण भी, क्योंकि आप सभी पुनरावर्ती का उत्पादन करने के लिए मशीनों के किसी विशेष सेट पर परीक्षण पर भरोसा नहीं कर सकते हैं। यदि आप सभी खामियों को बंद नहीं करते हैं तो एक समस्या है। लेकिन एआरएम या पावरपीसी जैसी कमजोर-ऑर्डर वाली वास्तुकला पर परीक्षण करना अभ्यास में एक अच्छा विचार है, एक प्रणाली के तहत एक स्क्रिप्ट का परीक्षण करना जो कृत्रिम रूप से देरी करता है , कुछ दौड़ को उजागर कर सकता है , इसलिए यह कुछ भी नहीं से बेहतर है। आप हमेशा बग को पकड़ सकते हैं यह पकड़ नहीं होगा!
पीटर कॉर्ड्स

18

क्यों एक दौड़ की स्थिति है

एक पाइप के दो किनारों को समानांतर में निष्पादित किया जाता है, एक के बाद एक नहीं। इसे प्रदर्शित करने का एक बहुत ही सरल तरीका है: रन

time sleep 1 | sleep 1

इसमें एक दो नहीं बल्कि दो सेकंड लगते हैं।

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

सिंक्रनाइज़ेशन के एक बिंदु को देखने के लिए, निम्न आदेशों का पालन करें ( sh -xप्रत्येक कमांड को प्रिंट करता है क्योंकि यह इसे निष्पादित करता है):

time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'

जब तक आप जो निरीक्षण करते हैं उसके साथ सहज होने तक विविधताओं के साथ खेलें।

कंपाउंड कमांड दी

cat tmp | head -1 > tmp

बाएं हाथ की प्रक्रिया निम्न कार्य करती है (मैंने केवल वही चरण सूचीबद्ध किए हैं जो मेरे स्पष्टीकरण के लिए प्रासंगिक हैं):

  1. बाहरी कार्यक्रम catको तर्क के साथ निष्पादित करें tmp
  2. tmpपढ़ने के लिए खोलें ।
  3. हालांकि यह फ़ाइल के अंत तक नहीं पहुँचा है, फ़ाइल से एक हिस्सा पढ़ें और इसे मानक आउटपुट में लिखें।

दाहिने हाथ की प्रक्रिया निम्नलिखित करती है:

  1. मानक आउटपुट को रीडायरेक्ट करने के लिए tmp, इस प्रक्रिया में फ़ाइल को छोटा कर देता है।
  2. बाहरी कार्यक्रम headको तर्क के साथ निष्पादित करें -1
  3. मानक इनपुट से एक पंक्ति पढ़ें और इसे मानक आउटपुट पर लिखें।

सिंक्रोनाइज़ेशन का एकमात्र बिंदु यह है कि दाएं -3 बाएं के लिए प्रतीक्षा करता है-3 ने एक पूरी लाइन संसाधित की है। बाएं -2 और दाएं -1 के बीच कोई तालमेल नहीं है, इसलिए वे किसी भी क्रम में हो सकते हैं। वे किस क्रम में होते हैं, यह अनुमान लगाने योग्य नहीं है: यह सीपीयू आर्किटेक्चर पर निर्भर करता है, शेल पर, कर्नेल पर, जिस पर कोर की प्रक्रिया निर्धारित होती है, उस समय सीपीयू के आसपास क्या होता है, आदि को बाधित करता है।

व्यवहार को कैसे बदलें

आप सिस्टम सेटिंग बदलकर व्यवहार नहीं बदल सकते। कंप्यूटर वही करता है जो आप उसे करने के लिए कहते हैं। आपने इसे समानांतर रूप tmpसे छोटा और पढ़ने के लिए कहा था tmp, इसलिए यह दो चीजों को समानांतर में करता है।

ठीक है, एक "सिस्टम सेटिंग" है जिसे आप बदल सकते हैं: आप /bin/bashएक अलग प्रोग्राम द्वारा प्रतिस्थापित कर सकते हैं जो बैश नहीं है। मुझे उम्मीद है कि यह बिना कहे चले जाएंगे कि यह अच्छा विचार नहीं है।

यदि आप चाहते हैं कि पाइप के बायीं ओर से पहले छंटनी हो, तो आपको इसे पाइप लाइन के बाहर रखना होगा, उदाहरण के लिए:

{ cat tmp | head -1; } >tmp

या

( exec >tmp; cat tmp | head -1 )

मुझे नहीं पता कि आप ऐसा क्यों चाहते हैं। उस फ़ाइल से पढ़ने का क्या मतलब है जिसे आप खाली होना जानते हैं?

इसके विपरीत, यदि आप चाहते हैं कि आउटपुट रीडायरेक्शन (ट्रंकेशन सहित) catसमाप्त होने के बाद रीडिंग हो, तो आपको मेमोरी में डेटा को पूरी तरह से बफर करने की आवश्यकता है, जैसे।

line=$(cat tmp | head -1)
printf %s "$line" >tmp

या किसी भिन्न फ़ाइल पर लिखें और फिर उसे स्थान पर ले जाएं। यह आमतौर पर लिपियों में चीजों को करने का सबसे मजबूत तरीका है, और इसका फायदा यह है कि मूल नाम के माध्यम से दिखाई देने से पहले फ़ाइल को पूर्ण रूप से लिखा जाता है।

cat tmp | head -1 >new && mv new tmp

Moreutils संग्रह एक प्रोग्राम है जो सिर्फ इतना है कि कहा जाता है, करता है शामिल हैं sponge

cat tmp | head -1 | sponge tmp

कैसे स्वचालित रूप से समस्या का पता लगाने के लिए

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


यह मेरा लक्ष्य था, यह निर्धारित करना कि स्क्रिप्ट अच्छी तरह से लिखी गई है या नहीं। यदि स्क्रिप्ट ने इस तरह से डेटा को नष्ट कर दिया है, तो मैं चाहता था कि यह हर बार उन्हें नष्ट कर दे। यह सुनना अच्छा नहीं है कि यह लगभग असंभव है। आपके लिए धन्यवाद, मुझे अब पता है कि समस्या क्या है और एक समाधान सोचने की कोशिश करेंगे।
कारलोस

@karlosss: हम्म, मुझे आश्चर्य है कि अगर आप एक ही सिस्टम-कॉल ट्रेसिंग / इंटरसेप्टिंग सामान का उपयोग strace(यानी लिनक्स ptrace) के रूप में कर सकते हैं, तो सभी open-पढ़ने के लिए कॉल सिस्टम (सभी बच्चे की प्रक्रियाओं में) आधे से एक सेकंड के लिए सोते हैं, इसलिए दौड़ के साथ एक ट्रंकेशन, ट्रंकेशन लगभग हमेशा जीत जाएगा।
पीटर कॉर्ड्स

@PeterCordes मैं इसके लिए एक नौसिखिया हूँ, अगर आप इसे प्राप्त करने का एक तरीका प्रबंधित कर सकते हैं और इसे उत्तर के रूप में लिख सकते हैं, तो मैं इसे स्वीकार करूँगा।
karlosss

@PeterCordes आप इस बात की गारंटी नहीं दे सकते हैं कि ट्रंकेशन देरी से जीतेगा। यह ज्यादातर समय काम करेगा, लेकिन कभी-कभार एक भारी भरकम मशीन पर आपकी स्क्रिप्ट अधिक या कम रहस्यमय तरीके से विफल हो जाएगी।
गिल्स एसओ- बुराई को रोकना '

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