2008-09-23 14 views
10

के साथ समस्या मैं अंतिम पंक्ति को छोड़कर फ़ाइल की सभी पंक्तियों को हटाने की कोशिश कर रहा था लेकिन निम्न आदेश काम नहीं करता था, हालांकि file.txt खाली नहीं है।बैश आउटपुट रीडायरेक्शन

$cat file.txt |tail -1 > file.txt 

$cat file.txt 

ऐसा क्यों है?

उत्तर

19

एक पाइपलाइन के माध्यम से एक फ़ाइल से रीडायरेक्ट करना एक ही फाइल में वापस असुरक्षित है; को tail से पहले पाइपलाइन के अंतिम चरण को सेट करते समय खोल से अधिलेखित किया जाता है, तो आप पहले चरण को पढ़ना शुरू कर देते हैं, तो आप खाली आउटपुट के साथ समाप्त होते हैं।

करो बजाय निम्नलिखित:

tail -1 file.txt >file.txt.new && mv file.txt.new file.txt 

... अच्छी तरह से, वास्तव में, ऐसा नहीं करते हैं कि उत्पादन कोड में; खासकर यदि आप एक सुरक्षा के प्रति संवेदनशील वातावरण में कर रहे हैं और रूट के रूप में चल रहा है, निम्नलिखित अधिक उपयुक्त है:

tempfile="$(mktemp file.txt.XXXXXX)" 
chown --reference=file.txt -- "$tempfile" 
chmod --reference=file.txt -- "$tempfile" 
tail -1 file.txt >"$tempfile" && mv -- "$tempfile" file.txt 

एक और दृष्टिकोण (अस्थायी फ़ाइलों से परहेज है, जब तक <<< परोक्ष अपने मंच पर उन्हें बनाता है) पीछा कर रहा है:

lastline="$(tail -1 file.txt)"; cat >file.txt <<<"$lastline" 

(उपर्युक्त कार्यान्वयन बैश-विशिष्ट है, लेकिन ऐसे मामलों में काम करता है जहां गूंज नहीं है - जैसे कि अंतिम पंक्ति में "--version" है, उदाहरण के लिए)।

अंत में, एक moreutils से स्पंज का उपयोग कर सकते हैं:

tail -1 file.txt | sponge file.txt 
+0

ध्यान दें कि पूंछ एक फ़ाइल नाम को तर्क के रूप में स्वीकार करता है: "tail -1 file.txt> file.txt.new && mv file.txt.new file.txt" –

+0

@ मार्सेल लेवी - बिल्कुल सही, और इसमें चलाने की क्षमता है उस तरह से अधिक कुशलता से; अपडेट किया गया। –

+0

प्रिय @ चार्ल्सडफी, मैं यह ध्यान रखना चाहता हूं कि आपके "सुरक्षित" संस्करण की आवश्यकता है कि उपयोगकर्ता ऐसा कर रहा है जो रूट होगा। यह कैसे सुरक्षित है, मैं कह सकता हूं कि "file.txt" तक पहुंच पर इसकी अनुमतियां और मालिक लागू करने के लिए पर्याप्त नहीं है। उस पर विचार करें "फाइल।txt "की अनुमति 0666 है और इसे" ~/.ssh "फ़ोल्डर के अंदर रखा गया है जिसमें 0700 की अनुमति है। इस मामले में मेरा समाधान अधिक सुरक्षित होगा क्योंकि यह उस फ़ाइल को दुनिया में प्रकट नहीं करेगा। क्या मुझे इसके कारण आपके जवाब को कम करना चाहिए ?;) – ony

0

ऐसा लगता है कि आप इसे उसी फ़ाइल नाम पर वापस लिख रहे हैं। यदि आप निम्न कार्य करते हैं तो यह काम करता है:

$cat file.txt | tail -1 > anotherfile.txt 
+0

के लिए कोई ज़रूरत नहीं है खो जाएगा होगा अटकलें "लगता है" , और रीडायरेक्शन (इस प्रकार, ट्रंकेटिंग मोड में 'anotherfile.txt' का खुलासा) * * निष्पादन से पहले * होता है (इस मामले में,' पूंछ 'के; चाहे यह' बिल्ली 'के निष्पादन से पहले होता है, अनिश्चित है, लेकिन एक कार्यक्रम के बाद से चूंकि 'बिल्ली' को शुरू करने के लिए समय की आवश्यकता होती है, इसलिए यह बहुत अधिक संभावना है कि 'catfile.txt' का कटाव पहले से ही हो जाएगा 'बिल्ली' लोड होने से पहले और इनपुट के लिए अपना तर्क खोलने के लिए तैयार हो जाएगा)। –

1

लुईस Baumstark कहते हैं, यह है कि आप उसी फ़ाइल नाम के लिए लिख रहे पसंद नहीं है।

ऐसा इसलिए है क्योंकि खोल "file.txt" खुलता है और "cat file.txt" चलाने से पहले इसे पुनर्निर्देशन करने के लिए इसे छोटा करता है। तो, आप

tail -1 file.txt > file2.txt; mv file2.txt file.txt 
0

tail -1 > file.txt करने के लिए है आपकी फ़ाइल के ऊपर लिख देगा, क्योंकि पुनर्लेखन का से पहले क्या होगा आपके पाइप लाइन में आदेशों के किसी भी क्रियान्वित कर रहे हैं एक खाली फ़ाइल को पढ़ने के लिए बिल्ली के कारण।

3

'बिल्ली' निष्पादित होने से पहले, बैश ने अपनी सामग्री को साफ़ करने के लिए पहले से ही 'file.txt' खोला है।

सामान्य रूप से, उन फ़ाइलों को न लिखें जिन्हें आप उसी कथन में पढ़ रहे हैं। इसे उपरोक्त के रूप में एक अलग फ़ाइल में लिखकर चारों ओर काम किया जा सकता है:

$cat file.txt | tail -1 >anotherfile.txt 
$mv anotherfile.txt file.txt
या स्पंज जैसे moreutils:
$cat file.txt | tail -1 | sponge file.txt
यह काम करता है क्योंकि स्पंज तब तक प्रतीक्षा करता है जब तक इसकी इनपुट स्ट्रीम अपनी आउटपुट फ़ाइल खोलने से पहले समाप्त हो जाती है।

+0

क्या 'cat' आपको 'tail -1 file.txt' पर कोई मान देता है? यह निश्चित रूप से चीजें ** दूर ** कम कुशल बनाता है यदि आपका 'file.txt' बड़ा है - यदि 'पूंछ' में प्रत्यक्ष खोज योग्य फ़ाइल डिस्क्रिप्टर है, तो यह सीधे फ़ाइल के अंतिम केबी पर कूद सकता है, केवल उसे पढ़ सकता है, और पिछली पंक्ति को 1kb से बड़ा होने पर बैक अप लेने की अंतिम पंक्ति को खोजने का प्रयास करें; यदि यह सब 'बिल्ली' से एक पाइप है, तो इसे शुरुआत से फ़ाइल को पढ़ना होगा - इससे कोई फर्क नहीं पड़ता कि यह कितना बड़ा है - अंत तक पहुंचने के लिए। –

+0

... 'पूंछ -1

2

जब आप पार्टी की योजना बनाई अपने कमांड स्ट्रिंग सबमिट करते हैं, यह निम्नलिखित है:

  1. बनाता है एक आई/ओ पाइप।
  2. पाइप से पढ़ने, और file.txt पर लिखने, "/ usr/bin/tail -1" शुरू करता है।
  3. पाइप को लिखते हुए "/ usr/bin/cat file.txt" शुरू करता है।

जब तक 'बिल्ली' पढ़ना शुरू होता है, तब तक 'file.txt' को 'पूंछ' द्वारा छोटा कर दिया गया है।

यह यूनिक्स और शैल पर्यावरण के डिजाइन का हिस्सा है, और मूल बोर्न शैल के लिए सभी तरह से वापस चला जाता है। 'एक विशेषता है, एक बग नहीं।

+0

क्या (2) और (3) के बीच ऑर्डरिंग निर्दिष्ट है? मेरी धारणा यह है कि वे एक साथ हो रहे हैं, जो प्रभावी रूप से एक दौड़ होता है - हालांकि '/ usr/bin/tail' से पहले लिखने के लिए' file.txt' खोला जाता है, जिससे छंटनी जीतने की अत्यधिक संभावना होती है दौड़ (चूंकि '/ usr/bin/cat' को एक निष्पादन की आवश्यकता होती है, जिसमें सभी लिंकर/लोडर प्रदर्शन प्रभाव का तात्पर्य है)। –

2

tmp = $ (tail -1 file.txt); echo $ tmp> file.txt;

+1

अस्थायी रूप से अस्थायी फ़ाइलों से बचाता है, लेकिन सभी मानक मुद्दों से बचने के लिए इसे उद्धृत किया जाना चाहिए। – wnoise

5

आप एसईडी सभी लाइनों को हटाने के लिए एक फ़ाइल से पिछले उपयोग कर सकते हैं लेकिन:

sed -i '$!d' file 
  • मैं जगह में फ़ाइल को बदलने के sed बताता है; अन्यथा, परिणाम STDOUT को लिखना होगा।
  • $ वह पता है जो फ़ाइल की अंतिम पंक्ति से मेल खाता है।
  • डी हटाएं आदेश है। इस मामले में, इसे से अस्वीकार कर दिया गया है!, इसलिए सभी लाइन पते से मेल नहीं खाए जाएंगे।
+0

इस बारे में एकमात्र दुर्भाग्यपूर्ण बात यह है कि 'sed -i' POSIX-compliant नहीं है लेकिन एक जीएनयू एक्सटेंशन है। जगह में संपादित करने के लिए 'ed' या' ex' का उपयोग करना उन चेतावनियों से बच जाएगा। –

2

यह एक लिनक्स खोल में अच्छी तरह से काम करता है:

replace_with_filter() { 
    local filename="$1"; shift 
    local dd_output byte_count filter_status dd_status 
    dd_output=$("[email protected]" <"$filename" | dd conv=notrunc of="$filename" 2>&1; echo "${PIPESTATUS[@]}") 
    { read; read; read -r byte_count _; read filter_status dd_status; } <<<"$dd_output" 
    ((filter_status > 0)) && return "$filter_status" 
    ((dd_status > 0)) && return "$dd_status" 
    dd bs=1 seek="$byte_count" if=/dev/null of="$filename" 
} 

replace_with_filter file.txt tail -1 

dd के "notrunc" विकल्प, वापस फ़िल्टर्ड सामग्री लिखने के लिए, जगह में, जबकि dd फिर से (एक बाइट गिनती के साथ की जरूरत है प्रयोग किया जाता है) वास्तव में फ़ाइल को कम करने के लिए। यदि नया फ़ाइल आकार पुराने फ़ाइल आकार के बराबर या बराबर है, तो दूसरा dd आमंत्रण आवश्यक नहीं है।

फ़ाइल कॉपी विधि पर इसका लाभ निम्न हैं: 1) कोई अतिरिक्त डिस्क स्थान आवश्यक नहीं है, 2) बड़ी फ़ाइलों पर तेज़ प्रदर्शन, और 3) शुद्ध खोल (डीडी के अलावा)।

+0

मुझे यह पसंद है - यह एक अभिनव विचार है। मैं इस समय केवल इसे ऊपर नहीं उठा रहा हूं क्योंकि '$ FILTER' का उपयोग http://mywiki.wooledge.org/BashFAQ/050 से गुजरता है (स्ट्रिंग-स्प्लिटिंग पर भरोसा करने के लिए सही ढंग से कमांड बनाने के लिए अत्यंत त्रुटि-प्रवण है)। –

+0

मुझे आशा है कि आप मेरे भारी हाथों के संपादन को ध्यान में रखें; यह समाधान अब काफी मजबूत होना चाहिए। –

+0

मैं इसे 'replace_with_filter' नहीं कहूंगा। यह केवल फ़िल्टर के लिए काम करेगा कि प्रत्येक अगले खंड के साथ डेटा मूल खंड से अधिक नहीं होता है। अन्यथा फ़िल्टर द्वारा पढ़ी गई जानकारी को इसके आउटपुट द्वारा ओवरराइट किया जा सकता है। ध्यान दें कि पिछले 'dd' के बजाय आप 'truncate -s $ byte_count" $ filename "' का उपयोग कर सकते हैं। – ony

1

बस इस मामले के लिए

cat < file.txt | (rm file.txt; tail -1 > file.txt)
का उपयोग करना संभव है जो "बिल्ली" कनेक्शन को "(...)" में सबहेल के साथ "file" कनेक्शन से पहले "file.txt" खोल देगा। "आरएम file.txt" डिस्क से संदर्भ को हटा देगा इससे पहले कि सबहेल इसे "पूंछ" के लिए लिखने के लिए खोल देगा, लेकिन सामग्री अभी भी खुली डिस्क्रिप्टर के माध्यम से उपलब्ध होगी जो "बिल्ली" को तब तक पारित कर दी जाएगी जब तक कि यह stdin बंद न हो जाए। वहाँ पाइपलाइन घटकों 'स्टार्टअप के बीच आदेश देने की कोई गारंटी नहीं है: तो आप बेहतर सुनिश्चित करें कि इस आदेश को खत्म हो जाएगा या "file.txt" की सामग्री को

+0

बेनामी डाउनवॉटर को दंडित किया जाना चाहिए। यह उत्तर काम करता है और इसमें तर्क है (यानी सामग्री तक पहुंच रखने के लिए इसे पढ़ने के लिए खोलने के बाद फ़ाइल हटाएं)। – ony

+0

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

+0

@ चार्ल्सडफी, "शर्मिंदा दंडित" अज्ञात के लिए है। क्योंकि किसी भी तर्क के बिना डाउनवोट होने से परेशान होता है। आपको ऐसा नहीं लगता? .. शुद्धता के लिए, क्या आप सुनिश्चित हैं कि उच्च उत्तर और विश्वसनीयता - सही उत्तर के लिए आवश्यक है? कभी-कभी आप कुछ और संग्रह करने के लिए कुछ बलिदान कर सकते हैं। इस प्रकार विभिन्न विचारों से आप अलग-अलग समाधानों का इलाज कर सकते हैं। यदि आप मेरे विचार को मेरा उत्तर देते हैं तो आप पाएंगे कि इस उत्तर के लिए पूर्व शर्त पूरी नहीं की जा सकती है, इसलिए यह गलत तर्क है कि यह गलत है। – ony

1
echo "$(tail -1 file.txt)" > file.txt 
+0

क्या होता है यदि अंतिम पंक्ति में केवल '-n' है? क्या होगा यदि इसमें बैकस्लैश अक्षर शामिल हैं और आपका 'इको' एक्सएसआई पॉज़िक्स एक्सटेंशन के अनुरूप है (जो किसी भी भागने के लिए कॉल करता है तो उन अक्षरों को '-ई' जैसी किसी भी चीज़ के बिना भी सम्मानित किया जा सकता है)? प्रतिध्वनि पर भरोसा करने के बजाय 'printf '% s \ n'" $ (tail -1 file.txt) "का उपयोग करना सुरक्षित होगा –

 संबंधित मुद्दे

  • कोई संबंधित समस्या नहीं^_^