Menggabungkan sejumlah besar file


15

Saya memiliki ± 10.000 file ( res.1- res.10000) yang semuanya terdiri dari satu kolom, dan jumlah baris yang sama. Yang saya inginkan adalah, pada dasarnya, sederhana; gabungkan semua file dengan bijaksana dalam file baru final.res. Saya sudah mencoba menggunakan:

paste res.*

Namun (meskipun hal ini tampaknya bekerja untuk subset kecil dari file hasil, ini memberikan error berikut ketika dilakukan pada seluruh set: Too many open files.

Pasti ada cara 'mudah' untuk menyelesaikan ini, tapi sayangnya saya cukup baru untuk unix. Terima kasih sebelumnya!

PS: Untuk memberi Anda gambaran tentang bagaimana (salah satu) datafile terlihat seperti:

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

Apakah Anda mencoba menggunakan --serialopsi dengan pasteperintah?
shivams

@shivams paste --serialtidak menggabungkan file dengan bijaksana ...
Stephen Kitt

@StephenKitt Tunggu. Saya sedikit bingung. Apakah maksudnya dalam file output, ia membutuhkan kolom berbeda untuk setiap data file? Atau semua data dalam satu kolom?
shivams

@Stephen Kitt shivams Menggunakan paste -smemang berfungsi, tetapi menempelkan hasil yang terpisah file bijaksana baris bukan kolom bijaksana. Namun, ini adalah sesuatu yang bisa saya pecahkan. Terima kasih!
tikar

@shivams Saya ingin kolom yang berbeda untuk setiap data file dalam file output
tikar

Jawaban:


17

Jika Anda memiliki izin root pada mesin itu, Anda sementara dapat meningkatkan batas "jumlah maksimum deskriptor file terbuka":

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

Lalu

paste res.* >final.res

Setelah itu Anda dapat mengaturnya kembali ke nilai aslinya.


Sebuah solusi kedua , jika Anda tidak dapat mengubah batas:

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

Ini panggilan pasteuntuk setiap file satu kali, dan pada akhirnya ada file besar dengan semua kolom (perlu beberapa menit).

Sunting : Penggunaan kucing yang tidak berguna ... Tidak !

Seperti disebutkan dalam komentar penggunaan di catsini ( cat final.res | paste - $f >temp) tidak sia-sia. Pertama kali loop dijalankan, file final.resbelum ada. pastekemudian akan gagal dan file tidak pernah diisi, atau dibuat. Dengan solusi saya hanya catgagal pertama kali dengan No such file or directorydan pastemembaca dari stdin hanya file kosong, tetapi terus berlanjut. Kesalahan bisa diabaikan.


Terima kasih! Adakah yang tahu bagaimana saya bisa mengecek apa nilai aslinya?
tikar

Hanya ulimit -Snuntuk batas lunak dan ulimit -Hnbatas keras
kekacauan

Terima kasih, ini sebagian berfungsi. Namun, untuk satu set file saya mendapatkan error berikut: -bash: /usr/bin/paste: Argument list too long. Gagasan bagaimana mengatasi ini? Maaf mengganggu kalian.
tikar

@ tikar tampaknya kernel Anda tidak mengizinkan lebih banyak argumen, Anda dapat memeriksanya getconf ARG_MAX, Anda hanya dapat meningkatkan nilai itu saat mengkompilasi ulang kernel. Anda dapat mencoba solusi kedua saya?
kekacauan

2
Alih-alih menggunakan catsetiap kali melalui loop, Anda bisa mulai dengan membuat final.resfile kosong . Ini mungkin ide yang bagus, kalau-kalau sudah ada final.resfile di sana.
Barmar

10

Jika jawaban chaos tidak berlaku (karena Anda tidak memiliki izin yang diperlukan), Anda dapat mengumpulkan pastepanggilan sebagai berikut:

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

Ini mencantumkan file 1000 sekaligus dalam file bernama lists00, lists01dll., Lalu menempelkan res.file yang sesuai ke file bernama merge00, merge01dll., Dan akhirnya menggabungkan semua file yang digabungkan sebagian.

Seperti disebutkan oleh kekacauan Anda dapat meningkatkan jumlah file yang digunakan sekaligus; batasnya adalah nilai yang diberikan ulimit -ndikurangi berapa banyak file yang sudah Anda buka, jadi Anda akan mengatakannya

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

untuk menggunakan batas minus sepuluh.

Jika versi splitAnda tidak mendukung -d, Anda dapat menghapusnya: yang diperlukan hanyalah splitsufiks numerik. Secara default sufiksnya adalah aa, abdll. , Bukan 01, 02dll.

Jika ada begitu banyak file yang ls -1 res.*gagal ("daftar argumen terlalu panjang"), Anda dapat menggantinya dengan findyang akan menghindari kesalahan itu:

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

(Seperti yang ditunjukkan oleh don_crissti , -1seharusnya tidak perlu ketika lsmengeluarkan piping ; tapi saya meninggalkannya untuk menangani kasus-kasus di mana lsalias dengan -C.)


4

Coba jalankan dengan cara ini:

ls res.*|xargs paste >final.res

Anda juga dapat membagi batch menjadi beberapa bagian dan mencoba sesuatu seperti:

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

dan pada akhirnya menggabungkan file akhir

paste final.* >final.res

@ Romeo Ninov Ini memberikan kesalahan yang sama dengan yang saya metion di pertanyaan awal saya:Too many open files
tikar

@ tikar, dalam hal demikian apakah Anda mempertimbangkan untuk membagi batch menjadi beberapa bagian. Akan mengedit jawaban saya untuk memberi Anda ide
Romeo Ninov

Benar, @StephenKitt, saya mengedit jawaban saya
Romeo Ninov

Untuk menghindari file-file sementara, pertimbangkan untuk membuat final.x00be be pipe - baik sebagai yang bernama FIFOs, atau secara implisit, menggunakan substitusi proses (jika shell Anda mendukungnya - misalnya bash). Ini tidak menyenangkan untuk ditulis dengan tangan, tetapi mungkin cocok untuk makefile.
Toby Speight

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

Saya tidak berpikir ini serumit semua itu - Anda sudah melakukan kerja keras dengan memesan nama file. Hanya saja, jangan membuka semuanya sekaligus, itu saja.

Cara lain:

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

... tapi saya pikir itu membuat mereka mundur ... Ini mungkin bekerja lebih baik:

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

Dan inilah cara lain :

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

Yang memungkinkan taruntuk mengumpulkan semua file menjadi aliran tanpa batas nol untuk Anda, mem-parsing semua metadata headernya kecuali nama file, dan mengubah semua baris di semua file menjadi tab. Itu bergantung pada input menjadi file teks aktual - yang berarti masing-masing berakhir dengan baris baru dan tidak ada null-byte dalam file. Oh - dan juga bergantung pada nama file sendiri menjadi baris baru bebas (meskipun yang mungkin ditangani bersemangat dengan GNU tar's --xformpilihan) . Mengingat kondisi ini terpenuhi, itu harus membuat pekerjaan yang sangat singkat dari sejumlah file - dan tarakan melakukan hampir semua itu.

Hasilnya adalah serangkaian garis yang terlihat seperti:

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

Dan seterusnya.

Saya mengujinya dengan terlebih dahulu membuat 5 testfile. Saya tidak benar-benar ingin membuat 10.000 file sekarang, jadi saya hanya sedikit lebih besar untuk masing-masing - dan juga memastikan bahwa panjang file berbeda dengan banyak. Ini penting ketika menguji tarskrip karena tarakan memblokir input ke panjang tetap - jika Anda tidak mencoba setidaknya beberapa panjang berbeda Anda tidak akan pernah tahu apakah Anda benar-benar akan menangani hanya satu.

Lagi pula, untuk file tes yang saya lakukan:

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

ls setelah itu dilaporkan:

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

... lalu aku berlari ...

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

... hanya untuk menampilkan hanya 25 bidang tab-dibatasi pertama per baris (karena setiap file adalah satu baris - ada banyak ) ...

Outputnya adalah:

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

Mengingat jumlah file, ukuran garis, dll. Yang terlibat, saya pikir itu akan melampaui ukuran standar alat (awk, sed, paste, *, dll)

Saya akan membuat program kecil untuk ini, tidak akan ada 10.000 file yang terbuka, atau panjang baris ratusan ribu (10.000 file 10 (ukuran maksimum baris dalam contoh)). Hanya membutuhkan ~ 10.000 array bilangan bulat, untuk menyimpan jumlah byte yang telah dibaca dari setiap file. Kerugiannya adalah ia hanya memiliki satu deskriptor file, digunakan kembali untuk setiap file, untuk setiap baris, dan ini bisa lambat.

Definisi FILESdan ROWSharus diubah ke nilai sebenarnya yang sebenarnya. Output dikirim ke output standar.

#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 */
   }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.