2,000개의 열이 포함된 대용량 파일에서 여러 열 가져오기

2,000개의 열이 포함된 대용량 파일에서 여러 열 가져오기

2000개의 열이 있는 Linux 시스템의 대용량 파일에서 여러 개의 특정 열을 가져오고 싶습니다. 어떻게 해야 하나요?

file1.gz 파일은 다음과 같습니다.

0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 0 0 ...

file2에 가져와야 하는 열은 다음과 같습니다.

186
187
188
189
190
191
192
193
194
195
(about 1000 column)

답변1

에서는 awk특정 열을 번호로 참조할 수 있습니다. 예를 들어 열 12 $12는 열 1345 입니다 $1345. 또한 기본 열 구분 기호는 공백이므로 공백으로 구분된 파일을 사용하는 예에서는 압축을 풀고 awk관심 있는 열을 인쇄하는 스크립트를 전달하기만 하면 됩니다.

zcat file.gz | awk '{print $1,$12,$195} > newFile

여기서 복잡한 점은 필요한 열이 너무 많아서 인쇄할 수 없다는 것입니다. 여기에서는 먼저 열을 읽은 다음 인쇄해야 합니다.

awk '{
        if (NR==FNR){ wantedColumns[NR]=$1 }
        else{ 
            for(i=1;i<=length(wantedColumns)-1;i++){ 
                printf "%s ", $(wantedColumns[i])
            }
            print $(wantedColumns[length(wantedColumns)])
        }
     }' file2 <(zcat file1.gz)

예를 들어:

$ zcat file1.gz
line1_field1 line1_field2 line1_field3 line1_field4 line1_field5 line1_field6 
line2_field1 line2_field2 line2_field3 line2_field4 line2_field5 line2_field6 
line3_field1 line3_field2 line3_field3 line3_field4 line3_field5 line3_field6 
line4_field1 line4_field2 line4_field3 line4_field4 line4_field5 line4_field6 
line5_field1 line5_field2 line5_field3 line5_field4 line5_field5 line5_field6 
line6_field1 line6_field2 line6_field3 line6_field4 line6_field5 line6_field6 
line7_field1 line7_field2 line7_field3 line7_field4 line7_field5 line7_field6 
line8_field1 line8_field2 line8_field3 line8_field4 line8_field5 line8_field6 
line9_field1 line9_field2 line9_field3 line9_field4 line9_field5 line9_field6 

$ cat file2
2
4
5

이 파일에 대해 위 스크립트를 실행하면 다음과 같은 결과를 얻습니다.

$ awk '{
>         if (NR==FNR){ wantedColumns[NR]=$1 }
>         else{ 
>             for(i=1;i<=length(wantedColumns)-1;i++){ 
>                 printf "%s ", $(wantedColumns[i])
>             }
>             print $(wantedColumns[length(wantedColumns)])
>         }
>      }' file2 <(zcat file1.gz)
line1_field2 line1_field4 line1_field5
line2_field2 line2_field4 line2_field5
line3_field2 line3_field4 line3_field5
line4_field2 line4_field4 line4_field5
line5_field2 line5_field4 line5_field5
line6_field2 line6_field4 line6_field5
line7_field2 line7_field4 line7_field5
line8_field2 line8_field4 line8_field5
line9_field2 line9_field4 line9_field5
line10_field2 line10_field4 line10_field5

설명하다

  • if (NR==FNR){ wantedColumns[NR]=$1 }: NR은 입력 줄 번호, FNR는 줄 번호현재 파일의. 둘은 첫 번째 파일을 읽을 때만 동일합니다. 따라서 NR같음 인 경우 FNR첫 번째 파일을 읽는 경우 해당 파일의 첫 번째 필드를 wantedColumns인덱스가 줄 번호이고 값이 필드인 배열에 저장합니다.
  • else { ... }: 우리라면아니요첫 번째 파일을 읽는 중입니다(지금 두 번째 파일에 있는 경우).
  • for(i=1;i<=length(wantedColumns)-1;i++){NR: 첫 번째 인덱스( 위 루프에서 사용한 값 으로 인해 1임)부터 두 번째 인덱스까지 원하는 열 배열을 반복 하고 각 열을 인쇄한 다음 공백을 넣습니다.중요한 경고: 반드시 원본 파일의 열 순서를 유지하지는 않습니다. 열은 에 있는 순서대로 인쇄됩니다 file2. 이 순서가 원본 파일의 순서와 다른 경우 file2(예: 1 3 2대신이 있는 경우 1 2 3) 이 순서대로 인쇄됩니다.
  • print $(wantedColumns[length(wantedColumns)]):마지막 필드를 인쇄하고 그 뒤에 개행 문자가 옵니다.
  • <(zcat file1.gz)bash: 이것은 명령의 출력을 파일로 처리할 수 있게 해주는 ["프로세스 대체"][1]라는 기능(다른 일부 쉘과 마찬가지로)입니다 . 여기서는 압축이 풀린 파일을 사용 zcat하고 이를 두 번째 입력 "파일"로 awk.

이 방법을 사용하면 각 줄 끝에 추가 공백이 추가됩니다. 이것이 문제인 경우 sed끝에 파이핑하여 피할 수 있습니다.

awk '...' | sed 's/ $//'

또는 쉼표로 구분된 필드 목록으로 cut변경하여 다음으로 전달할 수 있습니다 .file2cut

$ zcat file1.gz | cut -d' ' -f $(tr '\n' ',' < file2 | sed 's/,$//') 
line1_field2 line1_field4 line1_field5
line2_field2 line2_field4 line2_field5
line3_field2 line3_field4 line3_field5
line4_field2 line4_field4 line4_field5
line5_field2 line5_field4 line5_field5
line6_field2 line6_field4 line6_field5
line7_field2 line7_field4 line7_field5
line8_field2 line8_field4 line8_field5
line9_field2 line9_field4 line9_field5
line10_field2 line10_field4 line10_field5

설명하다

  • zcat file1.gz |: 압축을 풀고 file1.gz내용을 다음 명령으로 전달합니다.
  • cut -d' 'cut: 기본 탭( ) 대신 공백을 \t필드 구분 기호로 사용하라는 의미입니다 .
  • -f $(tr '\n' ',' < file2 | sed 's/,$//'): 인쇄할 필드를 -f알려줍니다 . cut쉼표로 구분된 필드 목록을 사용할 수 있으므로 tr '\n' ','모든 줄 바꿈을 쉼표로 변환하고 결과를 필드 목록으로 전달합니다. [1]:https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html

답변2

테스트할 수 있지만 정확해야 하는 입력/출력 예제를 제공하지 않았기 때문에 테스트되지 않았습니다.

zcat file1.gz | awk '
NR==FNR { out2inFldNr[++numOutFlds] = $1; next }
{
    for (outFldNr=1; outFldNr<=numOutFlds; outFldNr++) {
        inFldNr = out2inFldNr[outFldNr]
        printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
    }
}
' file2 -

답변3

펄 사용:

#!/usr/bin/perl
use strict;
my @file1;

# read in first file, assuming one column number per line
# subtract 1 because perl arrays start from 0 and append to
# an array called @file1
while(<>) {
  push @file1, $_-1;
  last if eof; # exit loop after end of the first file
};

# process second file, splitting it into an array called @line
# and then printing only the elements listed in the @file1 array
# (this is known as an "array slice", and perl is very flexible
#  about how it can be specified. see `man perldata` for details)
while(<>) {
  my @line = split;
  print join("\t", @line[@file1]),"\n";
};

입력 파일 f1.txtf2.txt.gz(아래 참조)을 사용하면 다음과 같은 출력이 생성됩니다.

$ ./extract.pl f1.txt <(zcat f2.txt.gz)
a       c       e       g
a       c       e       g
a       c       e       g
a       c       e       g
a       c       e       g

입력 파일:

$ cat f1.txt
1
3
5
7


$ zcat f2.txt.gz
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z

한 줄로 작성할 수도 있습니다.

$ perl -lne 'push @file1, $_-1; last if eof;
             END {
               while(<>) {
                 my @line=split;
                 print join("\t", @line[@file1]);
               };
             }' f1.txt <(zcat f2.txt.gz)

두 버전의 출력은 동일합니다.


그런데 위 스크립트의 두 가지 버전은필요하다두 개 이상의 파일 이름 인수(실제 파일 이름 또는 프로세스에 의한 대체) 대신 표준 입력에서 두 번째 파일을 읽으려면 다음과 같이 작성해야 합니다.

#!/usr/bin/perl
use strict;
my @file1;

my $f1 = shift;
open(my $fh,"<",$f1) || die "couldn't open $f1: $!\n";
while(<$fh>) {
  push @file1, $_-1;
};
close($f1);

while(<>) {
  my @line = split;
  print join("\t", @line[@file1]), "\n";
};

이렇게 하면 다음과 같이 실행할 수 있습니다.

$ zcat f2.txt.gz | ./extract.pl f1.txt

또는 첫 번째 버전처럼 계속 실행할 수 있습니다.

$ ./extract.pl f1.txt <(zcat f2.txt.gz)

즉, 이 버전에서는 첫 번째 파일은 파일 이름으로 제공되어야 하지만 두 번째 파일은 파일 또는 표준 입력이 될 수 있습니다.

또 다른 변형은 두 파일이 모두 표준 입력에서 나오도록 허용하는 것입니다.

#!/usr/bin/perl

use strict;
my @file1;

while(<>) {
  my @line = split;
  if (@line == 1) {
    push @file1, $_-1;
  } else {
    print join("\t", @line[@file1]), "\n";
  }
};

이 버전은 각 입력 라인에 몇 개의 필드가 있는지 확인합니다. 파일이 하나만 있으면 여전히 첫 번째 파일을 읽고 있으므로 @file1 배열에 추가하세요. 그렇지 않으면 배열 슬라이스를 인쇄합니다.

다음과 같이 실행됩니다:

$ (cat f1.txt ; zcat f2.txt.gz) | ./extract.pl

-a또는 Perl의 배열로 자동 분할 옵션을 사용하여 한 줄의 코드로 사용합니다 ( 입력을 $1, $2, $3 등으로 자동 분할하는 @F것처럼 작동함 ).awk

$ (cat f1.txt ; zcat f2.txt.gz) |
  perl -lane 'if (@F==1) {push @file1,$_-1} else {print join("\t",@F[@file1])}'

답변4

다음과 같이 할 수 있습니다. 먼저, file2를 수치적으로 정렬하고 고유화한 후 필드 조합이 범위 형태로 생성됩니다.

그런 다음 Perl 정규식(고급)은 입력을 24, 25, 26, 33 => 24-26,33으로 변환한 다음 이를 입력하여 옵션을 잘라냅니다.

$ cols=$(< file2 sort -nu | perl -00pe '$_ = s/(\d+)(?{$1})\K(?:\n(\d+)(?(?{++$^R!=$2})(*F)))+/-$2/gr =~ s/\n(?!\z)/,/gr')

$ gunzip -c file1.gz | cut -d' ' -f"$cols"

열 번호가 연속적인 경우 다음과 같이 처음 n개의 마지막 열 번호를 간단히 얻을 수 있습니다.

$ cols=$(< file2 sort -nu | sed '$q;1!d' | paste -sd- -)

이전과 같이 잘라냅니다.

관련 정보