大量のファイルを結合する


15

±10,000個のファイル(res.1- res.10000)があり、すべて1つの列と同じ数の行で構成されています。私が欲しいのは、本質的にはシンプルです。すべてのファイルを列ごとに新しいファイルにマージしますfinal.res。私は使用してみました:

paste res.*

ただし(これは結果ファイルの小さなサブセットで機能するようですが、セット全体で実行すると次のエラーが発生します:Too many open files

これを実行するための「簡単な」方法が必要ですが、残念ながら私はunixを初めて使用します。前もって感謝します!

PS:(私の)データファイルがどのように見えるかを知るため:

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

コマンドで--serialオプションを使用してみましたpasteか?
シヴァム

@shivamsはpaste --serial...ファイルの列方向をマージしません
スティーブン・キット

@StephenKitt待って。私は少し混乱しています。彼は、出力ファイルで、各ファイルのデータに異なる列が必要であることを意味しますか?または、すべてのデータが単一の列にありますか?
シヴァム

@Stephen Kitt shivams Usingは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各ファイルを1回呼び出し、最後にすべての列を持つ巨大なファイルがあります(1分かかります)。

編集猫の無駄な使用 ... ない

コメントで述べたように、catここ(cat final.res | paste - $f >temp)の使用法は無駄ではありません。ループが初めて実行されるとき、ファイルfinal.resはまだ存在していません。paste失敗し、ファイルがいっぱいになることも作成されることもありません。私のソリューションでcatは、最初に失敗するだけで、stdinから空のファイルNo such file or directorypaste読み取りますが、それは継続します。エラーは無視できます。


ありがとう!元の値が何であるかをどのように確認できますか?
マット

ただ、ulimit -Snソフト制限のためとulimit -Hnハードリミットのために
混沌

おかげで、これは部分的に機能します。ただし、別のファイルセットでは、次のエラーが表示されます-bash: /usr/bin/paste: Argument list too long。これを解決する方法はありますか?お邪魔してすみません。
マット

@matsは、カーネルがそれ以上の引数を許可していないようでgetconf ARG_MAX、で確認できます。カーネルを再コンパイルするときにのみ、その値を増やすことができます。私の2番目の解決策を試すことができますか?
カオス

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個のファイルをリストします lists00lists01などのres.され、対応するファイルがなどの名前のファイルに貼り付けられmerge00merge01最終的に部分的にマージされたすべてのファイルがマージされます。

カオスで述べたように、一度に使用するファイルの数を増やすことができます。制限は、指定した値ulimit -nから既に開いているファイルの数を引いたものです。したがって、

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

制限マイナス10を使用します。

のバージョンがをsplitサポートしていない-d場合は、削除できsplitます。数値サフィックスを使用するよう指示するだけです。デフォルトでは、接尾辞になりますaaabなどの代わりに0102など

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

@ Romeo Ninovこれは、最初の質問で出会ったのと同じエラーを与えます。- Too many open files
マット

@mats、このような場合、バッチを部分に分割することを検討してください。あなたのアイデアを与えるために私の答えを編集します
ロミオニノフ

右、@StephenKitt、I編集私の答え
ロミオNinov

一時ファイルを回避するには、final.x00名前付きFIFOとして、またはプロセス置換を使用して暗黙的にパイプを作成することを検討してください(シェルがサポートしている場合-bashなど)。これは手で書くのは楽しいことではありませんが、メイクファイルに適しているかもしれません。
トビースパイト

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、すべてのファイルをヌル区切りのストリームに収集し、ファイル名を除くすべてのヘッダーメタデータを解析し、すべてのファイルのすべての行をタブに変換できます。ただし、入力は実際のテキストファイルであることに依存します。つまり、それぞれが改行で終了し、ファイルにヌルバイトがないことを意味します。また、ファイル名自体が改行なしであることにも依存します(ただし、GNU tar--xformオプションを使用すると堅牢に処理される可能性があります)。これらの条件が満たされている場合、任意の数のファイルの非常に短い作業を行う必要があります-とtarほぼすべての処理を実行します。

結果は、次のような一連の行です。

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

等々。

最初に5つのテストファイルを作成してテストしました。今は10000個のファイルを生成する気分になれなかったので、それぞれについてもう少し大きくしました。また、ファイルの長さが大きく異なることを確認しました。tarスクリプトをテストする場合、これは重要です。なぜなら、tar入力を固定長にブロックするです-少なくともいくつかの異なる長さを試さないと、実際に1つだけを処理するかどうかわかりません。

とにかく、私がしたテストファイルについて:

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、*など)を超えると思います

このために小さなプログラムを作成し、10,000個のファイルを開いたり、長さ数十万行(10個の10,000ファイル(例では行の最大サイズ))したりしません。各ファイルから読み取られたバイト数を格納するために、整数の〜10,000配列のみが必要です。欠点は、ファイル記述子が1つしかなく、各ファイル、各行で再利用されることです。これは時間がかかる可能性があります。

定義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.