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
변경하여 다음으로 전달할 수 있습니다 .file2
cut
$ 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.txt
및 f2.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- -)
이전과 같이 잘라냅니다.