การรวมไฟล์จำนวนมากเข้าด้วยกัน


15

ฉันมีไฟล์± 10,000 ไฟล์ ( res.1- res.10000) ทั้งหมดประกอบด้วยหนึ่งคอลัมน์และจำนวนแถวเท่ากัน สิ่งที่ฉันต้องการคือโดยพื้นฐานแล้วเรียบง่าย final.resรวมไฟล์ทุกคอลัมน์ที่ชาญฉลาดในไฟล์ใหม่ ฉันได้ลองใช้:

paste res.*

อย่างไรก็ตาม (ถึงแม้ว่าสิ่งนี้ดูเหมือนว่าจะทำงานกับไฟล์ย่อยเล็ก ๆ ของไฟล์ผลลัพธ์ แต่ก็ให้ข้อผิดพลาดต่อไปนี้เมื่อดำเนินการกับทั้งชุด: Too many open files.

จะต้องมีวิธี 'ง่าย' ในการทำสิ่งนี้ แต่น่าเสียดายที่ฉันค่อนข้างใหม่กับยูนิกซ์ ขอบคุณล่วงหน้า!

PS: เพื่อให้คุณมีความคิดเกี่ยวกับสิ่งที่ (หนึ่งใน) ของฉัน datafile (s) ดูเหมือนว่า:

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

คุณลองใช้--serialตัวเลือกกับpasteคำสั่งหรือไม่?
shivams

@shivams paste --serialไม่ได้รวมไฟล์แบบฉลาด ๆ ...
สตีเฟ่นคิตต์

@StephenKitt รอ ฉันสับสนเล็กน้อย เขาหมายความว่าในไฟล์เอาต์พุตเขาต้องการคอลัมน์ที่แตกต่างกันสำหรับข้อมูลของแต่ละไฟล์หรือไม่ หรือข้อมูลทั้งหมดในคอลัมน์เดียว
shivams

@Stephen Kitt shivams การใช้paste -sงานได้ผลจริง แต่วางไฟล์ผลลัพธ์ที่แยกต่างหากแถวฉลาดแทนที่จะคอลัมน์ฉลาด อย่างไรก็ตามนี่คือสิ่งที่ฉันสามารถแก้ไขได้ ขอบคุณ!
เสื่อ

@ shivams ฉันต้องการคอลัมน์ที่แตกต่างกันสำหรับข้อมูลของแต่ละไฟล์ในไฟล์เอาต์พุต
mats

คำตอบ:


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อ่านจาก stdin เพียงไฟล์ที่ว่างเปล่า แต่มันยังคง ข้อผิดพลาดสามารถถูกละเว้น


ขอบคุณ! ความคิดใดที่ฉันสามารถตรวจสอบว่าค่าดั้งเดิมคืออะไร?
เสื่อ

เพียงulimit -Snเพื่อ จำกัด นุ่มและulimit -Hnสำหรับขีด จำกัด ยาก
ความสับสนวุ่นวายที่

ขอขอบคุณมันใช้งานได้บางส่วน อย่างไรก็ตามสำหรับชุดไฟล์อื่นฉันได้รับข้อผิดพลาดดังต่อไปนี้: -bash: /usr/bin/paste: Argument list too long. ความคิดวิธีการแก้ปัญหานี้? ขอโทษที่รบกวนพวกคุณ
เสื่อ

@ats ดูเหมือนว่าเคอร์เนลของคุณไม่อนุญาตให้มีการขัดแย้งมากขึ้นคุณสามารถตรวจสอบได้ด้วยgetconf ARG_MAXคุณสามารถเพิ่มค่านั้นได้เมื่อทำการคอมไพล์เคอร์เนลอีกครั้ง คุณอาจลองวิธีที่สองของฉัน
ความสับสนวุ่นวาย

2
แทนที่จะใช้catทุกครั้งในการวนซ้ำคุณสามารถเริ่มต้นด้วยการสร้างfinal.resไฟล์เปล่า นี่อาจเป็นความคิดที่ดี แต่อย่างใดในกรณีที่มีfinal.resไฟล์อยู่ที่นั่น
Barmar

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ไม่ควรจำเป็นเมื่อการแสดงผลของ piping ls; แต่ฉันปล่อยไว้ในการจัดการกรณีที่lsaliased ด้วย-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

@ Romeo Ninov นี้จะช่วยให้เกิดข้อผิดพลาดเช่นเดียวกับฉัน metioned ในคำถามแรกของฉัน:Too many open files
เสื่อ

@ats ในกรณีเช่นนี้คุณได้พิจารณาแยกแบทช์เป็นส่วน ๆ จะแก้ไขคำตอบของฉันเพื่อให้ความคิดแก่คุณ
Romeo Ninov

ใช่ @StephenKitt ฉันแก้ไขคำตอบของฉัน
Romeo Ninov

เพื่อหลีกเลี่ยงไฟล์ชั่วคราวให้พิจารณาว่าการใช้final.x00ไพพ์เป็นแบบไม่ว่าจะเป็นชื่อ FIFO หรือโดยปริยายโดยใช้การทดแทนกระบวนการ (หากเชลล์ของคุณรองรับมัน - เช่นทุบตี) การเขียนด้วยมือไม่สนุก แต่อาจเหมาะกับ makefile
Toby Speight

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รวบรวมไฟล์ทั้งหมดเป็นสตรีมที่มีการคั่นด้วย null สำหรับคุณแยกวิเคราะห์ข้อมูลเมตาส่วนหัวทั้งหมดยกเว้นชื่อไฟล์และแปลงทุกบรรทัดในไฟล์ทั้งหมดเป็นแท็บ มันขึ้นอยู่กับการป้อนข้อมูลเป็นไฟล์ข้อความจริงแม้ว่า - หมายถึงปลายแต่ละด้านด้วยขึ้นบรรทัดใหม่และไม่มีไบต์เป็นโมฆะในไฟล์ โอ้ - และยังอาศัยอยู่กับชื่อไฟล์ที่ตัวเองเป็นบรรทัดใหม่ฟรี( แต่ที่อาจจะได้รับการจัดการอย่างทนทานกับ GNU tar's--xformตัวเลือก) เมื่อปฏิบัติตามเงื่อนไขเหล่านี้แล้วก็ควรทำงานให้สั้นมาก ๆ ในจำนวนไฟล์ใด ๆ และtarจะทำเกือบทั้งหมด

ผลลัพธ์คือชุดของบรรทัดที่มีลักษณะดังนี้:

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

และอื่น ๆ

ฉันทดสอบโดยสร้าง 5 ไฟล์แรก ตอนนี้ฉันไม่ได้รู้สึกว่ากำลังสร้างไฟล์ 10,000 ไฟล์ดังนั้นฉันจึงยิ่งใหญ่ขึ้นสำหรับแต่ละไฟล์ - และทำให้มั่นใจได้ว่าความยาวของไฟล์นั้นแตกต่างกันมาก สิ่งนี้มีความสำคัญเมื่อทำการทดสอบ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, paste, *, etc)

ฉันจะสร้างโปรแกรมขนาดเล็กสำหรับสิ่งนี้มันจะไม่เปิด 10,000 ไฟล์หรือความยาวหนึ่งแสนบรรทัด (10,000 ไฟล์จาก 10 (ขนาดตัวอย่างสูงสุดของบรรทัด)) ต้องการเพียงจำนวนเต็ม ~ 10,000 อาร์เรย์เพื่อเก็บจำนวนไบต์ที่อ่านจากแต่ละไฟล์ ข้อเสียคือมีไฟล์ descriptor เพียงไฟล์เดียวเท่านั้นมันถูกใช้ซ้ำสำหรับแต่ละไฟล์สำหรับแต่ละบรรทัดและอาจช้า

คำจำกัดความของ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 */
   }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.