合并大量文件


15

我有±10,000个文件(res.1- res.10000),它们全部由一列和相等数量的行组成。本质上,我想要的是简单;将所有文件按列合并到一个新文件中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 2015年

@shivams paste --serial不会按列合并文件...
Stephen Kitt,

@StephenKitt等待。我有些困惑。他是否意味着在输出文件中,每个文件的数据都需要一个不同的列?还是所有数据都放在一个列中?
shivams,2015年

@Stephen Kitt shivams使用paste -s确实可以工作,但是将单独的结果文件按行而不是按列粘贴。但是,这是我可以解决的问题。谢谢!

@shivams我想在输出文件中的每个文件的数据不同的列
草席

Answers:


17

如果您对该计算机具有root权限,则可以暂时增加“打开文件描述符的最大数量”限制:

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第一次使用stdin会失败,No such file or directory并且只能paste从stdin读取一个空文件,但是它会继续。该错误可以忽略。


谢谢!知道如何检查原始值吗?
2015年

只是ulimit -Sn为了软限制和ulimit -Hn硬限制
混乱

谢谢,这部分起作用。但是,对于另一组文件,出现以下错误:-bash: /usr/bin/paste: Argument list too long。想法如何解决呢?抱歉打扰你们。

@mats似乎您的内核不允许更多参数,您可以使用进行检查getconf ARG_MAX,您只能在重新编译内核时增加该值。您可以尝试第二种解决方案吗?
2015年

2
cat您可以从创建一个空final.res文件开始,而不是每次都使用循环。不管怎样,这可能是一个好主意,以防万一已存在final.res文件。
巴马尔2015年

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

使用限制减去十。

如果您的版本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,在这种情况下,您是否考虑将批次分成几部分。将编辑我的答案以给您提示
Romeo Ninov

是的,@ StephenKitt,我编辑了我的答案
Romeo Ninov

为了避免使用临时文件,请考虑将final.x00be管道(作为命名的FIFO或隐式地)使用进程替换(如果您的外壳支持它,例如bash)。手工编写不是很有趣,但是很适合makefile。
Toby Speight 2015年

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它将阻止输入固定长度的内容-如果您不尝试至少几种不同的长度,您将永远不知道是否实际上只处理一个长度。

无论如何,对于测试文件,我做了:

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,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 */
   }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.