Kết hợp số lượng lớn các tập tin


15

Tôi có ± 10.000 tệp ( res.1- res.10000) tất cả bao gồm một cột và số lượng hàng bằng nhau. Những gì tôi muốn là, về bản chất, đơn giản; hợp nhất tất cả các tệp theo cột trong một tệp mới final.res. Tôi đã thử sử dụng:

paste res.*

Tuy nhiên (mặc dù điều này dường như hoạt động đối với một tập hợp nhỏ các tệp kết quả, nhưng điều này gây ra lỗi sau khi được thực hiện trên toàn bộ tập hợp: Too many open files .

Phải có một cách 'dễ dàng' để thực hiện điều này, nhưng thật không may, tôi khá mới với unix. Cảm ơn trước!

PS: Để cho bạn biết về (các) tệp dữ liệu của tôi trông như thế nào:

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

Bạn đã thử sử dụng --serialtùy chọn với pastelệnh?
shivams

@shivams paste --serialkhông hợp nhất các tập tin theo cột ...
Stephen Kitt

@StephenKitt Chờ đã. Tôi hơi bối rối. Có phải anh ta có nghĩa là trong tệp đầu ra, anh ta cần một cột khác nhau cho mỗi dữ liệu của tệp? Hoặc tất cả các dữ liệu trong một cột duy nhất?
shivams

@Stephen Kitt shivams Sử dụng paste -sthực sự hoạt động, nhưng dán các tệp kết quả riêng biệt theo hàng thay vì theo cột. Tuy nhiên, đây là điều tôi có thể giải quyết. Cảm ơn!
chiếu

@shivams Tôi muốn một cột khác nhau cho mỗi dữ liệu của tệp trong tệp đầu ra
chiếu

Câu trả lời:


17

Nếu bạn có quyền root trên máy đó, bạn có thể tạm thời tăng giới hạn "số lượng mô tả tệp mở tối đa":

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

Và sau đó

paste res.* >final.res

Sau đó, bạn có thể đặt nó trở lại các giá trị ban đầu.


Một giải pháp thứ hai , nếu bạn không thể thay đổi giới hạn:

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

Nó gọi pastecho mỗi tệp một lần và cuối cùng có một tệp lớn với tất cả các cột (phải mất vài phút).

Chỉnh sửa : Sử dụng mèo vô dụng ... Không phải !

Như đã đề cập trong các ý kiến, việc sử dụng catở đây ( cat final.res | paste - $f >temp) không phải là vô ích. Lần đầu tiên vòng lặp chạy, tập tin final.reskhông tồn tại. pastesau đó sẽ thất bại và tập tin không bao giờ được điền, cũng không được tạo. Với giải pháp của tôi chỉ catthất bại lần đầu tiên với No such file or directorypasteđọc từ stdin chỉ là một tập tin trống, nhưng nó vẫn tiếp tục. Các lỗi có thể được bỏ qua.


Cảm ơn! Bất kỳ ý tưởng làm thế nào tôi có thể kiểm tra các giá trị ban đầu là gì?
chiếu

Chỉ ulimit -Sndành cho giới hạn mềm và giới hạn ulimit -Hncứng
hỗn loạn

Cảm ơn, điều này một phần hoạt động. Tuy nhiên, đối với một tập hợp tệp khác, tôi gặp lỗi sau : -bash: /usr/bin/paste: Argument list too long. Ý tưởng làm thế nào để giải quyết điều này? Xin lỗi vì đã làm phiền các bạn.
chiếu

@mats dường như kernel của bạn không cho phép nhiều đối số hơn, bạn có thể kiểm tra nó getconf ARG_MAX, bạn chỉ có thể tăng giá trị đó khi biên dịch lại kernel. Bạn có thể thử giải pháp thứ hai của tôi?
hỗn loạn

2
Thay vì sử dụng catmỗi lần qua vòng lặp, bạn có thể bắt đầu bằng cách tạo một final.restệp trống . Đây có lẽ là một ý tưởng tốt, trong trường hợp đã có một final.restập tin ở đó.
Barmar

10

Nếu câu trả lời của hỗn loạn không áp dụng được (vì bạn không có quyền yêu cầu), bạn có thể thực hiện các pastecuộc gọi như sau:

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

Điều này liệt kê các tệp 1000 tại một thời điểm trong các tệp có tên lists00, lists01v.v., sau đó dán các res.tệp tương ứng vào các tệp có tên merge00,merge01 v.v., và cuối cùng hợp nhất tất cả các tệp được hợp nhất một phần.

Như đã đề cập bởi sự hỗn loạn, bạn có thể tăng số lượng tệp được sử dụng cùng một lúc; giới hạn là giá trị được đưa ra ulimit -ntrừ đi tuy nhiên nhiều tệp bạn đã mở, vì vậy bạn nói

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

để sử dụng giới hạn trừ mười.

Nếu phiên bản splitkhông hỗ trợ của -dbạn, bạn có thể xóa nó: tất cả những gì nó làm là splitsử dụng hậu tố số. Theo mặc định, các hậu tố sẽ là aa, abvv thay vì 01,02 vv

Nếu có quá nhiều tệp ls -1 res.*bị lỗi ("danh sách đối số quá dài"), bạn có thể thay thế nó findđể tránh lỗi đó:

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

(Như don_crissti đã chỉ ra , -1không cần thiết khi lsđầu ra của đường ống ; nhưng tôi sẽ để nó xử lý các trường hợp có lsbí danh -C.)


4

Cố gắng thực hiện nó theo cách này:

ls res.*|xargs paste >final.res

Bạn cũng có thể chia lô theo từng phần và thử một số thứ như:

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

và cuối cùng kết hợp các tập tin cuối cùng

paste final.* >final.res

@ Romeo Ninov Điều này đưa ra lỗi tương tự như tôi đã gặp trong câu hỏi ban đầu của mình:Too many open files
chiếu

@mats, trong trường hợp như vậy bạn có cân nhắc chia lô theo từng phần. Sẽ chỉnh sửa câu trả lời của tôi để cung cấp cho bạn ý tưởng
Romeo Ninov

Phải, @StephenKitt, tôi chỉnh sửa câu trả lời của mình
Romeo Ninov

Để tránh các tệp tạm thời, hãy xem xét việc tạo các final.x00đường ống - có tên là FIFO, hoặc ngầm định, sử dụng thay thế quy trình (nếu trình bao của bạn hỗ trợ nó - ví dụ bash). Điều này không thú vị để viết bằng tay, nhưng cũng có thể phù hợp với một makefile.
Toby Speight

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

Tôi không nghĩ rằng điều này phức tạp như tất cả những điều đó - bạn đã hoàn thành công việc khó khăn bằng cách đặt tên tập tin. Chỉ cần không mở tất cả chúng cùng một lúc, là tất cả.

Cách khác:

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

... nhưng tôi nghĩ rằng chúng làm ngược lại ... Điều này có thể hoạt động tốt hơn:

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

Và đây là một cách khác :

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

Điều đó cho phép tartập hợp tất cả các tệp thành một luồng được phân tách bằng null cho bạn, phân tích tất cả siêu dữ liệu tiêu đề của nó trừ tên tệp và chuyển đổi tất cả các dòng trong tất cả các tệp thành các tab. Mặc dù vậy, nó phụ thuộc vào đầu vào là các tệp văn bản thực tế - có nghĩa là mỗi đầu kết thúc với một dòng mới và không có byte rỗng trong các tệp. Ồ - và nó cũng dựa vào tên tập tin không có dòng mới (mặc dù điều đó có thể được xử lý mạnh mẽ với tùy chọn tarcủa GNU --xform) . Với những điều kiện này được đáp ứng, nó sẽ làm cho công việc rất ngắn của bất kỳ số lượng tệp nào - và tarsẽ thực hiện hầu hết tất cả.

Kết quả là một tập hợp các dòng trông như:

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

Và như thế.

Tôi đã thử nghiệm nó bằng cách tạo 5 testfiles đầu tiên. Tôi thực sự không cảm thấy giống như việc tạo ra 10000 tệp, vì vậy tôi chỉ lớn hơn một chút cho mỗi tệp - và cũng đảm bảo rằng độ dài của tệp khác nhau rất nhiều. Điều này rất quan trọng khi kiểm tra tartập lệnh vì tarsẽ chặn đầu vào theo độ dài cố định - nếu bạn không thử ít nhất một vài độ dài khác nhau, bạn sẽ không bao giờ biết liệu bạn có thực sự chỉ xử lý một độ dài hay không.

Dù sao, đối với các tập tin thử nghiệm tôi đã làm:

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

ls sau đó báo cáo:

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

... sau đó tôi chạy ...

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

... Chỉ hiển thị 25 trường được phân tách bằng tab đầu tiên trên mỗi dòng (vì mỗi tệp là một dòng - có rất nhiều ) ...

Đầu ra là:

./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

Với số lượng tệp, kích thước dòng, v.v., tôi nghĩ rằng nó sẽ vượt qua kích thước mặc định của các công cụ (awk, sed, paste, *, v.v.)

Tôi sẽ tạo một chương trình nhỏ cho việc này, nó sẽ không mở 10.000 tệp, cũng không phải là hàng trăm nghìn chiều dài (10.000 tệp 10 (kích thước tối đa của dòng trong ví dụ)). Nó chỉ cần một mảng ~ 10.000 số nguyên, để lưu trữ số byte đã được đọc từ mỗi tệp. Nhược điểm là nó chỉ có một mô tả tệp, nó được sử dụng lại cho mỗi tệp, cho mỗi dòng và điều này có thể chậm.

Các định nghĩa FILESROWSnên được thay đổi thành các giá trị chính xác thực tế. Đầu ra được gửi đến đầu ra tiêu chuẩn.

#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 */
   }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.