बैश स्क्रिप्टिंग और बड़ी फाइलें (बग): रीडायरेक्शन से रीड बिलिन के साथ इनपुट अप्रत्याशित परिणाम देता है


16

मैं बड़ी फ़ाइलों के साथ एक अजीब मुद्दा है और bash। यह संदर्भ है:

  • मेरे पास एक बड़ी फ़ाइल है: 75G और 400,000,000+ लाइनें (यह एक लॉग फ़ाइल है, मेरी खराब है, मैंने इसे बढ़ने दिया)।
  • प्रत्येक पंक्ति के पहले 10 चार्ट YYYY-MM-DD प्रारूप में एक समय टिकट है।
  • मैं उस फ़ाइल को विभाजित करना चाहता हूं: प्रति दिन एक फ़ाइल।

मैंने निम्नलिखित स्क्रिप्ट के साथ प्रयास किया जो काम नहीं किया। मेरा प्रश्न इस स्क्रिप्ट के काम नहीं करने के बारे में है, न कि वैकल्पिक समाधानों के बारे में

while read line; do
  new_file=${line:0:10}_file.log
  echo "$line" >> $new_file
done < file.log

डिबगिंग के बाद, मुझे new_fileचर में समस्या मिली । यह स्क्रिप्ट:

while read line; do
  new_file=${line:0:10}_file.log
  echo $new_file
done < file.log | uniq -c

परिणाम देता है bellow (मैं xडेटा को गोपनीय रखने के लिए शेष डालता हूं , अन्य चार्ट वास्तविक हैं)। नोटिस dhऔर छोटे तार:

...
  27402 2011-xx-x4
  27262 2011-xx-x5
  22514 2011-xx-x6
  17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
      1 2011-xx-x2
      3 2011-xx-x1
...
     12 2011-xx-x1
      1 2011-xx-dh
      1 2011-xx-x1
      1 208--
      1 2011-xx-x1
      1 2011-xx-dh
      1 2011-xx-x1    
...

यह मेरी फ़ाइल के प्रारूप में कोई समस्या नहीं है । स्क्रिप्ट cut -c 1-10 file.log | uniq -cकेवल मान्य समय टिकट देती है। दिलचस्प है, उपरोक्त आउटपुट का एक हिस्सा इसके साथ बन जाता है cut ... | uniq -c:

3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1

हम देख सकते हैं कि यूनीक गणना के बाद 4474604, मेरी प्रारंभिक स्क्रिप्ट विफल रही।

क्या मैंने बैश में एक सीमा को मारा था जो मुझे नहीं पता है, क्या मुझे बैश में एक बग मिला (यह संभावना नहीं है), या क्या मैंने कुछ गलत किया है?

अपडेट :

समस्या फ़ाइल के 2G पढ़ने के बाद होती है। यह सीम readऔर पुनर्निर्देशन 2 जी से बड़ी फ़ाइलों को पसंद नहीं करता है। लेकिन फिर भी एक अधिक सटीक स्पष्टीकरण के लिए खोज की जा रही है।

अपडेट 2 :

यह निश्चित रूप से बग की तरह दिखता है। इसके साथ पुन: पेश किया जा सकता है:

yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c

लेकिन यह वर्कअराउंड के रूप में ठीक काम करता है (यह मुझे लगता है कि इसका एक उपयोगी उपयोग पाया गया cat):

cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c 

जीएनयू और डेबियन के लिए एक बग दायर किया गया है। प्रभावित संस्करण bashडेबियन स्क्वीज़ 6.0.2 और 6.0.4 पर 4.1.5 हैं ।

echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu

Update3:

एंड्रियास श्वाब के लिए धन्यवाद जिन्होंने मेरी बग रिपोर्ट पर तुरंत प्रतिक्रिया दी, यह वह पैच है जो इस दुर्व्यवहार का समाधान है। प्रभावित फाइल lib/sh/zread.cजितनी जल्दी गिल्स ने बताई है:

diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
      int fd; {   off_t off;
-  int r;
+  off_t r;

  off = lused - lind;   r = 0;

rचर का वापसी मान धारण करने के लिए प्रयोग किया जाता है lseek। जैसा lseekकि फ़ाइल की शुरुआत से ऑफसेट लौटाता है, जब यह 2GB से अधिक है, तो intमान नकारात्मक है, जो परीक्षण if (r >= 0)को विफल करने का कारण बनता है जहां इसे सफल होना चाहिए।


1
क्या आप इनपुट डेटा के छोटे सेट के साथ समस्या को दोहरा सकते हैं? क्या यह हमेशा एक ही इनपुट लाइन है जिसके परिणामस्वरूप इन समस्याएं होती हैं?
लार्क्स

@larks: अच्छा सवाल। समस्या हमेशा # 13.520.918 लाइन पर शुरू होती है (वास्तव में मेरे द्वारा किए गए परीक्षणों के लिए दो बार)। इस लाइन से पहले फ़ाइल का आकार 2.147.487.726 है। यह कहा जाता है कि यहां 32 बिट्स की सीमा है, लेकिन बिल्कुल नहीं जैसा कि हम 2 ^ 31 (2.147.483.648) से थोड़ा अधिक हैं, लेकिन एक 4K बफर सीमा (2 ^ 31 + 4K = 2.147.487.744) पर सही है। पिछली और अगली पंक्तियाँ सामान्य 100 से 200 वर्ण रेखाएँ हैं।
jfg956

दूसरी फ़ाइल पर परीक्षण किया गया (समान आकार के बारे में): समस्या # 13.522.712 लाइन पर शुरू होती है, और फ़ाइल उस लाइन से पहले 2.147.498.679 बाइट्स होती है। यह readबैश में बयान की एक सीमा की दिशा में इंगित करता है ।
jfg956

जवाबों:


13

तुम एक बग को बग में पाया, प्रकार की। यह एक ज्ञात बग के साथ एक ज्ञात बग है।

प्रोग्राम एक पूर्ण आकार के साथ कुछ पूर्णांक प्रकार में एक चर के रूप में एक फ़ाइल में एक ऑफसेट का प्रतिनिधित्व करते हैं। पुराने दिनों में, हर कोई intबस के बारे में सब कुछ करता था, और intटाइप 32 बिट्स तक सीमित था, जिसमें साइन बिट भी शामिल था, इसलिए यह -2147483648 से 2147483647 तक मूल्यों को संग्रहीत कर सकता था। आजकल अलग- अलग चीजों के लिए विभिन्न प्रकार के नाम हैं , जिनमें off_tसे एक के लिए एक फ़ाइल में ऑफसेट।

डिफ़ॉल्ट रूप से, off_t32-बिट प्लेटफ़ॉर्म (2GB तक की अनुमति) पर 32-बिट प्रकार है, और 64-बिट प्लेटफ़ॉर्म (8EB तक की अनुमति) पर 64-बिट प्रकार है। हालाँकि, LARGEFILE विकल्प के साथ कार्यक्रमों को संकलित करना सामान्य है, जो कि प्रकार off_tको 64 बिट्स चौड़े होने के लिए स्विच करता है और प्रोग्राम को कार्यों के उपयुक्त कार्यान्वयन जैसे बनाता है lseek

ऐसा प्रतीत होता है कि आपका 32-बिट प्लेटफॉर्म पर बैश चल रहा है और आपका बैश बाइनरी बड़े फ़ाइल समर्थन के साथ संकलित नहीं है। अब, जब आप एक नियमित फ़ाइल से एक पंक्ति पढ़ते हैं, तो bash प्रदर्शन के लिए बैचों में वर्णों को पढ़ने के लिए एक आंतरिक बफर का उपयोग करता है (अधिक जानकारी के लिए, स्रोत देखें builtins/read.def)। जब लाइन पूरी हो जाती है, तो lseekउस फाइल में स्थिति के बारे में कुछ अन्य कार्यक्रम की परवाह किए बिना, फ़ाइल को वापस लाइन के अंत में स्थिति में वापस करने के लिए कॉल को बैश करें । फंक्शन में lseekहोने वाली कॉल ।zsyncfclib/sh/zread.c

मैंने स्रोत को बहुत विस्तार से नहीं पढ़ा है, लेकिन मुझे लगता है कि संक्रमण के बिंदु पर कुछ भी आसानी से नहीं हो रहा है जब पूर्ण ऑफसेट नकारात्मक है। जब यह 2GB मार्क पार कर जाता है, तो बैश गलत ऑफसेट पर पढ़ना बंद कर देता है।

यदि मेरा निष्कर्ष गलत है और आपका बैश वास्तव में 64-बिट प्लेटफॉर्म पर चल रहा है या लार्जफाइल समर्थन के साथ संकलित है, तो यह निश्चित रूप से एक बग है। कृपया इसे अपने वितरण या अपस्ट्रीम पर रिपोर्ट करें ।

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


1
मर्सी गिल्स। शानदार जवाब: पूर्ण, पर्याप्त जानकारी के साथ मजबूत सीएस पृष्ठभूमि (32 बिट्स ...) के बिना भी लोगों को इस मुद्दे को समझने के लिए। (लार्क्स भी लाइन नंबर पर सवाल करने में मदद करते हैं, और इसे स्वीकार किया जाना चाहिए।) उसके बाद, मैंने 32 बिट की समस्या के बावजूद और स्रोत को डाउनलोड किया, लेकिन विश्लेषण के इस स्तर पर अभी तक नहीं था। मर्सी एनकोर, एट बॉन पत्रिकाओं।
jfg956

4

मैं गलत के बारे में नहीं जानता, लेकिन यह निश्चित रूप से जटिल है। यदि आपकी इनपुट लाइनें इस तरह दिखती हैं:

YYYY-MM-DD some text ...

तब वास्तव में इसका कोई कारण नहीं है:

new_file=${line:0:4}-${line:5:2}-${line:8:2}_file.log

आप उस चीज़ को समाप्त करने के लिए बहुत सारे प्रतिस्थापन कार्य कर रहे हैं जो दिखता है ... ठीक उसी तरह जैसे यह पहले से ही फ़ाइल में दिखता है। इस बारे में कैसा है?

while read line; do
  new_file="${line:0:10}_file.log"
  echo "$line" >> $new_file
done

यह लाइन से पहले 10 वर्णों को पकड़ लेता है। तुम भी bashपूरी तरह से और बस उपयोग के साथ फैल सकता है awk:

awk '{print > ($1 "_file.log")}' < file.log

यह तारीख $1(प्रत्येक पंक्ति में पहला व्हाट्सएप-सीमांकित कॉलम) को पकड़ लेता है और फ़ाइल नाम उत्पन्न करने के लिए इसका उपयोग करता है।

ध्यान दें कि यह संभव है कि आपकी फ़ाइलों में कुछ फर्जी लॉग लाइनें हों। यही है, समस्या इनपुट के साथ हो सकती है, आपकी स्क्रिप्ट पर नहीं। आप awkइस तरह से फर्जी लाइनों को फ़्लैग करने के लिए स्क्रिप्ट का विस्तार कर सकते हैं :

awk '
$1 ~ /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ {
    print > ($1 "_file.log")
    next
}

{
    print "INVALID:", $0
}
'

यह YYYY-MM-DDआपकी लॉग फ़ाइलों के लिए लाइनों से मेल खाता है, और झंडे की लाइनें जो स्टैडआउट पर टाइमस्टैम्प से शुरू नहीं होती हैं।


मेरी फाइल में कोई फर्जी लाइन नहीं है: cut -c 1-10 file.log | uniq -cमुझे अपेक्षित परिणाम देता है। मैं उपयोग ${line:0:4}-${line:5:2}-${line:8:2}कर रहा हूं क्योंकि मैं एक निर्देशिका में फ़ाइल डालूंगा ${line:0:4}/${line:5:2}/${line:8:2}, और मैंने समस्या को सरल किया (मैं समस्या कथन को अपडेट करूंगा)। मुझे पता है कि awkयहां मेरी मदद की जा सकती है, लेकिन मैं इसका उपयोग कर अन्य समस्याओं में भाग गया। मैं जो चाहता हूं वह समस्या को समझ रहा है bash, वैकल्पिक समाधान नहीं।
jfg956

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

सरलीकृत समस्या अप्रत्याशित परिणाम देती है जो मैंने प्रश्न में प्रस्तुत किया है, इसलिए मुझे नहीं लगता कि यह एक ओवरसिम्प्लीफिकेशन है। इसके अलावा, सरलीकृत समस्या cutबयान के समान परिणाम देती है जो काम करती है। जैसा कि मैं सेब की तुलना संतरे से करना चाहता हूं, न कि संतरे से।
jfg956

1
मैंने आपको एक प्रश्न छोड़ दिया है जो यह पता लगाने में मदद कर सकता है कि चीजें कहां से खराब हो रही हैं ...
21

2

लगता है कि आप क्या करना चाहते हैं:

awk '
{  filename = substr($0, 0, 10) "_file.log";  # input format same as output format
   if (filename != lastfile) {
       close(lastfile);
       print 'finished writing to', lastfile;
   }
   print >> filename;
   lastfile=filename;
}' file.log

closeभरने से खुले फ़ाइल तालिका रहता है।


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