많은 양의 파일 결합


15

하나의 열과 동일한 수의 행으로 구성된 ± 10,000 개의 파일 ( res.1- res.10000)이 있습니다. 내가 원하는 것은 본질적으로 간단합니다. 모든 파일을 새 파일에 열 단위로 병합하십시오 final.res. 나는 다음을 사용하려고 시도했다.

paste res.*

그러나 결과 파일의 작은 하위 집합에 대해서는 작동하는 것처럼 보이지만 전체 세트에서 수행 할 때 다음 오류가 발생 Too many open files합니다.

이 작업을 수행하는 '쉬운'방법이 있어야하지만 불행히도 나는 유닉스를 처음 접했습니다. 미리 감사드립니다!

추신 : (내) 데이터 파일의 모양에 대한 아이디어를 제공합니다.

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

명령에 --serial옵션 을 사용해 보셨습니까 paste?
shivams

@shivams paste --serial는 파일을 열 단위로 병합하지 않습니다 ...
Stephen Kitt

@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합니다. 아이디어를 해결하는 방법? 귀찮게해서 죄송합니다.
매트

@mats는 커널이 더 많은 인수를 허용하지 않는 것 같습니다.로 확인할 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

한도에서 10을 뺀 값을 사용합니다.

의 버전이 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파이프 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

@mats, 이러한 경우 배치를 부분으로 분할하는 것을 고려하십시오. 내 답변을 편집하여 아이디어를 드리겠습니다
Romeo Ninov

맞습니다. @StephenKitt, 저는 답변을 편집합니다
Romeo Ninov

임시 파일을 피하려면 final.x00이름이 지정된 FIFO로 파이프를 만들 거나 프로세스 대체를 사용하여 암시 적으로 파이프를 만드십시오 (쉘이 지원하는 경우-bash). 손으로 쓰는 것이 재미는 없지만 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--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 개의 정수 배열 만 필요합니다. 단점은 파일 디스크립터가 하나만 있고 각 파일에 대해 각 라인마다 재사용되므로 느려질 수 있다는 것입니다.

의 정의 FILESROWS실제 정확한 값으로 변경해야합니다. 출력은 표준 출력으로 전송됩니다.

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