बड़ी मात्रा में फ़ाइलों का संयोजन


15

मेरे पास एक कॉलम में have 10,000 फाइलें ( res.1- res.10000) हैं, और समान पंक्तियों की संख्या है। जो मैं चाहता हूं, संक्षेप में, सरल; सभी फ़ाइलों को एक नई फ़ाइल में कॉलम-वार मर्ज करें final.res। मैंने प्रयोग करने की कोशिश की है:

paste res.*

हालांकि (हालांकि यह परिणाम फ़ाइलों के एक छोटे सबसेट के लिए काम करने लगता है, यह निम्न त्रुटि देता है जब पूरे सेट पर प्रदर्शन किया: Too many open files

इसे पूरा करने के लिए एक 'आसान' तरीका होना चाहिए, लेकिन दुर्भाग्य से मैं यूनिक्स के लिए काफी नया हूं। अग्रिम में धन्यवाद!

पुनश्च: आपको यह बताने के लिए कि मेरे (डेटा) में से एक क्या है:

0.5
0.5
0.03825
0.5
10211.0457
10227.8469
-5102.5228
0.0742
3.0944
...

क्या आपने कमांड के --serialसाथ विकल्प का उपयोग करने की कोशिश की paste?
शिवाम्स

@ शिवम paste --serialने कॉलम-वार का विलय नहीं किया ...
स्टीफन किट

@StephenKitt प्रतीक्षा करें। मैं थोड़ा उलझन में हूं। क्या उसका मतलब है कि आउटपुट फ़ाइल में, उसे प्रत्येक फ़ाइल के डेटा के लिए एक अलग कॉलम की आवश्यकता है? या एक कॉलम में सभी डेटा?
शिवाम्स

@ स्टीफन किट शिव paste -sवास्तव में काम करता है का उपयोग करता है, लेकिन कॉलम-वार के बजाय अलग-अलग परिणाम फ़ाइलों को पंक्ति-वार चिपकाता है। हालाँकि, यह कुछ ऐसा है जिसे मैं हल कर सकता हूं। धन्यवाद!
मैट

@shivams मैं आउटपुट फ़ाइल में प्रत्येक फ़ाइल के डेटा के लिए एक अलग कॉलम चाहते हैं
मैट

जवाबों:


17

यदि आपके पास उस मशीन पर रूट अनुमतियाँ हैं, तो आप अस्थायी रूप से "ओपन फ़ाइल डिस्क्रिप्टर की अधिकतम संख्या" सीमा बढ़ा सकते हैं:

ulimit -Hn 10240 # The hard limit
ulimit -Sn 10240 # The soft limit

और तब

paste res.* >final.res

उसके बाद आप इसे मूल मूल्यों पर वापस सेट कर सकते हैं।


एक दूसरा उपाय , यदि आप सीमा नहीं बदल सकते हैं:

for f in res.*; do cat final.res | paste - $f >temp; cp temp final.res; done; rm temp

यह pasteप्रत्येक फ़ाइल के लिए एक बार कॉल करता है , और अंत में सभी कॉलमों के साथ एक बड़ी फ़ाइल होती है (इसमें मिनट लगता है)।

संपादित करें : बिल्ली का बेकार उपयोग ... नहीं !

जैसा कि टिप्पणियों में उल्लेख किया गया है catयहां ( cat final.res | paste - $f >temp) का उपयोग बेकार नहीं है। पहली बार लूप चलता है, फ़ाइल final.resपहले से मौजूद नहीं है। pasteतब विफल हो जाता है और फ़ाइल कभी भरी नहीं जाती, न ही बनाई जाती है। मेरे समाधान के साथ केवल catपहली बार विफल हो जाता है No such file or directoryऔर pasteस्टड से सिर्फ एक खाली फ़ाइल पढ़ता है, लेकिन यह जारी है। त्रुटि को नजरअंदाज किया जा सकता है।


धन्यवाद! कोई भी विचार कि मैं कैसे जांच सकता हूं कि मूल मूल्य क्या हैं?
मैट

बस ulimit -Snनरम सीमा के ulimit -Hnलिए और कठिन सीमा के लिए
अराजकता

धन्यवाद, यह आंशिक रूप से काम करता है। हालाँकि, फ़ाइलों के एक और सेट के लिए मुझे निम्न त्रुटि मिलती है -bash: /usr/bin/paste: Argument list too long:। यह कैसे हल करने के लिए विचार? आप लोगों को परेशान करने के लिए क्षमा करें।
मैट

@mats को लगता है कि आपका कर्नेल अधिक तर्क नहीं देता है, आप इसकी जांच getconf ARG_MAXकर सकते हैं, आप केवल कर्नेल को पुन: जमा करते समय उस मान को बढ़ा सकते हैं। आप मेरे दूसरे समाधान की कोशिश कर सकते हैं?
अराजकता

2
catलूप के माध्यम से हर बार उपयोग करने के बजाय , आप एक खाली final.resफ़ाइल बनाकर शुरू कर सकते हैं । यह शायद एक अच्छा विचार है किसी भी तरह से, अगर वहाँ पहले से ही एक final.resफ़ाइल है।
बरमार

10

यदि अराजकता का उत्तर लागू नहीं होता है (क्योंकि आपके पास आवश्यक अनुमति नहीं है), तो आप pasteकॉल को निम्नानुसार बैच सकते हैं:

ls -1 res.* | split -l 1000 -d - lists
for list in lists*; do paste $(cat $list) > merge${list##lists}; done
paste merge* > final.res

यह नामांकित फ़ाइलों में एक समय में 1000 फ़ाइलों को सूचीबद्ध करता है lists00, lists01आदि, फिर संबंधित res.फ़ाइलों को नाम merge00, merge01आदि फ़ाइलों में चिपकाता है , और अंत में सभी परिणामस्वरूप आंशिक रूप से विलय की गई फ़ाइलों को मर्ज करता है।

जैसा कि अराजकता द्वारा उल्लिखित है, आप एक ही बार में उपयोग की जाने वाली फ़ाइलों की संख्या बढ़ा सकते हैं; सीमा है मान दिया गया ulimit -nमाइनस हालांकि आपके पास पहले से मौजूद कई फाइलें हैं, इसलिए आप कहेंगे

ls -1 res.* | split -l $(($(ulimit -n)-10)) -d - lists

सीमा शून्य से दस का उपयोग करने के लिए।

यदि आपके संस्करण का splitसमर्थन नहीं करता है -d, तो आप इसे हटा सकते हैं: यह सब कुछ splitसंख्यात्मक प्रत्ययों का उपयोग करना है। डिफ़ॉल्ट रूप से प्रत्यय aa, abआदि के बजाय 01, 02आदि होंगे।

यदि ऐसी कई फाइलें हैं जो ls -1 res.*विफल हो जाती हैं ("तर्क सूची बहुत लंबी है"), तो आप इसे बदल सकते हैं findजिसके साथ वह त्रुटि से बच जाएगा:

find . -maxdepth 1 -type f -name res.\* | split -l 1000 -d - lists

(जैसा कि don_crissti द्वारा बताया गया है , -1पाइपिंग lsके आउटपुट के दौरान आवश्यक नहीं होना चाहिए ; लेकिन मैं उन मामलों को संभालने के लिए इसे छोड़ रहा हूं, जहां lsसे अलियास किया गया है -C।)


4

इसे इस तरह निष्पादित करने का प्रयास करें:

ls res.*|xargs paste >final.res

आप बैच को भागों में विभाजित कर सकते हैं और कुछ इस तरह आज़मा सकते हैं:

paste `echo res.{1..100}` >final.100
paste `echo res.{101..200}` >final.200
...

और अंत में अंतिम फाइलों को मिलाएं

paste final.* >final.res

@ रोमियो निनोव यह वही त्रुटि देता है जैसा कि मैंने अपने प्रारंभिक प्रश्न में व्यक्त किया था:Too many open files
मैट

@ माट्स, ऐसे मामले में आपने बैच को भागों में विभाजित करने पर विचार किया है। आपको विचार देने के लिए मेरे उत्तर को संपादित करूंगा
रोमियो निनोव

ठीक है, @StephenKitt, मैं अपना जवाब संपादित करता हूं
रोमियो निनोव

अस्थायी फ़ाइलों से बचने के लिए, final.x00पाइप को बनाने पर विचार करें - या तो फीफो नाम के रूप में, या संक्षेप में, प्रक्रिया प्रतिस्थापन का उपयोग करके (यदि आपका शेल इसका समर्थन करता है - जैसे बैश)। यह हाथ से लिखने के लिए मजेदार नहीं है, लेकिन अच्छी तरह से एक मेकफाइल के अनुरूप हो सकता है।
टोबी स्पाइट

4
i=0
{ paste res.? res.?? res.???
while paste ./res."$((i+=1))"[0-9][0-9][0-9]
do :; done; } >outfile

मुझे नहीं लगता कि यह सब उतना ही जटिल है - आप पहले से ही फाइलनामों का आदेश देकर कड़ी मेहनत कर चुके हैं। बस एक ही समय में उन सभी को मत खोलो, सब है।

दूसरा रास्ता:

pst()      if   shift "$1"
           then paste "$@"
           fi
set ./res.*
while  [ -n "${1024}" ] ||
     ! paste "$@"
do     pst "$(($#-1023))" "$@"
       shift 1024
done >outfile

... लेकिन मुझे लगता है कि यह उन्हें पीछे की ओर करता है ... यह बेहतर काम कर सकता है:

i=0;  echo 'while paste \'
until [ "$((i+=1))" -gt 1023 ] &&
      printf '%s\n' '"${1024}"' \
      do\ shift\ 1024 done
do    echo '"${'"$i"'-/dev/null}" \'
done | sh -s -- ./res.* >outfile

और यहाँ एक और तरीका है:

tar --no-recursion -c ./ |
{ printf \\0; tr -s \\0; }    |
cut -d '' -f-2,13              |
tr '\0\n' '\n\t' >outfile

यह आपके tarलिए सभी फ़ाइलों को अशक्त-सीमांकित स्ट्रीम में इकट्ठा करने की अनुमति देता है, इसके हेडर मेटाडेटा लेकिन फ़ाइल नाम के सभी को पार्स करता है, और सभी फ़ाइलों को टैब में सभी लाइनों को बदल देता है। हालांकि यह वास्तविक टेक्स्ट-फाइल्स के इनपुट पर निर्भर करता है - जिसका अर्थ है कि प्रत्येक छोर w / एक नई रेखा है और फाइलों में कोई नल-बाइट्स नहीं हैं। ओह - और यह भी खुद को न्यूलाइन-फ्री होने के फिल्नाम पर निर्भर करता है (हालांकि यह GNU tarके --xformविकल्प के साथ मजबूती से संभाला जा सकता है ) । इन शर्तों को पूरा करने के बाद, यह किसी भी संख्या में फ़ाइलों का बहुत कम काम करना चाहिए - और tarलगभग सभी करेंगे।

परिणाम लाइनों का एक सेट है जो जैसा दिखता है:

./fname1
C1\tC2\tC3...
./fname2
C1\tC2\t...

और इसी तरह।

मैंने पहले 5 टेस्टफाइल्स बनाकर इसका परीक्षण किया। मुझे वास्तव में अभी 10000 फ़ाइलों को जीनिंग की तरह महसूस नहीं हुआ था, इसलिए मैं बस प्रत्येक के लिए थोड़ा बड़ा हो गया - और यह भी सुनिश्चित किया कि फ़ाइल की लंबाई काफी हद तक भिन्न हो। tarस्क्रिप्ट का परीक्षण करते समय यह महत्वपूर्ण है क्योंकि tarनिश्चित लंबाई के लिए इनपुट को रोक देगा - यदि आप कम से कम कुछ अलग लंबाई की कोशिश नहीं करते हैं, तो आप कभी नहीं जान पाएंगे कि क्या आप वास्तव में केवल एक को संभालेंगे।

वैसे भी, परीक्षण फ़ाइलों के लिए मैंने किया:

for f in 1 2 3 4 5; do : >./"$f"
seq "${f}000" | tee -a [12345] >>"$f"
done

ls बाद में सूचना दी:

ls -sh [12345]
68K 1 68K 2 56K 3 44K 4 24K 5

... तो मैं भाग गया ...

tar --no-recursion -c ./ |
{ printf \\0; tr -s \\0; }|
cut -d '' -f-2,13          |
tr '\0\n' '\n\t' | cut -f-25

... केवल प्रति पंक्ति केवल 25 टैब-सीमांकित फ़ील्ड दिखाने के लिए (क्योंकि प्रत्येक फ़ाइल एक एकल पंक्ति है - बहुत कुछ है ... )

आउटपुट था:

./1
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./2
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./3
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./4
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./5
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25

4

शामिल फ़ाइलों, लाइन आकार, आदि की मात्रा को देखते हुए, मुझे लगता है कि यह टूल (awk, sed, पेस्ट, *, आदि) के डिफ़ॉल्ट आकारों को पार कर जाएगा।

मैं इसके लिए एक छोटा सा कार्यक्रम बनाऊंगा, इसमें न तो 10,000 फाइलें खुली होंगी, न ही हजारों की लंबाई में सौ (लाइन की 10 फाइलें (उदाहरण में लाइन का अधिकतम आकार))। इसमें केवल ~ 10,000 सरणी पूर्णांक की आवश्यकता होती है, प्रत्येक फ़ाइल से बाइट्स की संख्या को पढ़ने के लिए। नुकसान यह है कि इसमें केवल एक फ़ाइल विवरणक है, इसका उपयोग प्रत्येक फ़ाइल के लिए, प्रत्येक पंक्ति के लिए पुन: उपयोग किया जाता है, और यह धीमा हो सकता है।

की परिभाषाएँ FILESऔर ROWSवास्तविक सटीक मानों में बदलनी चाहिए। आउटपुट को मानक आउटपुट पर भेजा जाता है।

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FILES 10000 /* number of files */
#define ROWS 500    /* number of rows  */

int main() {
   int positions[FILES + 1];
   FILE *file;
   int r, f;
   char filename[100];
   size_t linesize = 100;
   char *line = (char *) malloc(linesize * sizeof(char));

   for (f = 1; f <= FILES; positions[f++] = 0); /* sets the initial positions to zero */

   for (r = 1; r <= ROWS; ++r) {
      for (f = 1; f <= FILES; ++f) {
         sprintf(filename, "res.%d", f);                  /* creates the name of the current file */
         file = fopen(filename, "r");                     /* opens the current file */
         fseek(file, positions[f], SEEK_SET);             /* set position from the saved one */
         positions[f] += getline(&line, &linesize, file); /* reads line and saves the new position */
         line[strlen(line) - 1] = 0;                      /* removes the newline */
         printf("%s ", line);                             /* prints in the standard ouput, and a single space */
         fclose(file);                                    /* closes the current file */
      }
      printf("\n");  /* after getting the line from each file, prints a new line to standard output */
   }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.