텍스트 파일에서 2개 이상의 단어(공백으로 구분되지 않음)가 포함된 줄을 제거하는 방법은 무엇입니까?
문서에는 이러한 단어의 "단일 버전"도 있습니다.
예를 들어:
alpha
beta
gama
alphabeta
zeta
gamabeta
출력은 다음과 같아야 합니다.
alpha
beta
gama
zeta
편집하다: 내 파일에는 150만 줄이 포함되어 있습니다.
답변1
상당히 짧은 파일의 경우 행에 ERE 연산자가 포함되어 있지 않다고 가정하면 다음과 같습니다.
$ LC_ALL=C grep -vxE "($(paste -sd '|' file)){2,}" file
alpha
beta
gama
zeta
2개 이상의 행 시퀀스를 포함하지 않는 행을 반환합니다 file
.
작동 방식은 grep
다음과 같은 명령을 작성하는 것입니다.
LC_ALL=C grep -vxE '(alpha|beta|gama|alphabeta|zeta|gamabeta){2,}' file
더 큰 파일의 경우 길이 또는 매개변수 + 환경(또는 Linux의 단일 매개변수) 제한에 직면하게 됩니다. 인수 대신 표준 입력을 사용하여 정규식을 전달하면 이 문제를 해결할 수 있지만 -f -
, 그래도 정규식 크기에 한계가 있습니다.
perl
대신 다음을 사용하여 grep
더 큰 입력을 처리할 수 있습니다 .
perl -le '
chomp (@words = <>);
$re = "^(" . join("|", map {qr{\Q$_\E}} @words) . "){2,}\\z";
for (@words) {print unless m/$re/}' file
(이것은 위에서 언급한 다른 제한 사항도 방지합니다).
각 단어를 다른 단어와 비교해야 하기 때문에(아마도 두 번 이상) 어쨌든 시간이 오래 걸립니다.
답변2
그러면 파일에 있는 두 단어의 조합이 아닌 파일의 모든 단어가 인쇄됩니다.
$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file
alpha
beta
gama
zeta
명령을 여러 줄로 나누고 싶은 분들을 위해:
awk '
{
one[NR]=$1
}
END{
for (i=1;i<=length(one);i++)
for (j=1;j<=length(one);j++)
two[one[i] one[j]]
for (i=1;i<=length(one);i++)
if (!(one[i] in two))
print one[i]
}' file
다른 예시
유사한 단어가 포함된 파일을 고려해 보겠습니다. 하지만 때로는 개별 단어 앞에 조합이 나타나는 경우가 있습니다.
$ cat file2
alphabeta
alpha
gammaalpha
beta
gamma
동일한 명령을 실행해도 여전히 올바른 결과가 생성됩니다.
$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file2
alpha
beta
gamma
어떻게 작동하나요?
one[NR]=$1
one
그러면 키가 줄 번호NR
이고 값이 해당 줄의 단어인 배열이 생성됩니다 .END{...}
중괄호 안의 명령은 파일 읽기가 완료된 후에 실행됩니다. 이 명령은 두 개의 루프로 구성됩니다. 첫 번째 루프는 다음과 같습니다.
for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]
two
그러면 파일에 있는 두 단어의 모든 조합으로 구성된 키가 포함된 배열이 생성됩니다 .두 번째 루프는 다음과 같습니다.
for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]
이 루프는 배열의 키로 나타나지 않는 파일의 모든 단어를 인쇄합니다
two
.
더 짧고 간단한 버전
이 버전은 더 짧은 코드를 사용하고 동일한 단어를 인쇄합니다. 단점은 단어의 순서가 입력 파일과 동일하다고 보장되지 않는다는 것입니다.
$ awk '{one[$1]} END{for (w1 in one) for (w2 in one) two[w1 w2]; for (w in one) if (!(w in two)) print w}' file1
gama
zeta
alpha
beta
메모리를 더 절약하는 방법
대용량 파일의 경우 위의 방법으로 인해 메모리 오버플로가 발생할 수 있습니다. 이러한 경우 다음을 고려하십시오.
$ sort -u file | awk '{one[$1]} END{for (w1 in one) for (w2 in one) print w1 w2}' >doubles
$ grep -vxFf doubles file
alpha
beta
gama
zeta
이는 sort -u
file1에서 중복된 단어를 제거한 다음 이라는 이중 단어를 포함할 수 있는 파일을 만드는 데 사용 됩니다 doubles
. 그런 다음 .grep
file
doubles
답변3
<file awk 'NF {print length "\t" $0}' | sort -k1n,1 | cut -f2- |
awk 'NR==1 {min=length}
(l=length) >= 2*min {
delete k; # clear k array
k[1];
while (length(k))
for (i in k) {
for (j=l-i+1; j>=min; --j)
if (substr($0,i,j) in seen) {
if (i+j-1==l)
next;
k[i+j];
}
delete k[i];
}
}
!seen[$0]++'
이전에 본 줄로만 구성된 줄은 인쇄되지 않습니다.
이미 표시된 문자열에 하위 문자열이 있는지 확인하여 작동합니다.
입력 파일을 줄 길이에 따라 가장 짧은 것부터 가장 긴 것까지 정렬해야 합니다. awk | sort | cut
이 방법.
다음 awk
프로그램은 먼저 가장 짧은 줄의 길이를 기록합니다( 로 저장됨 min
). 길이가 다음보다 작은 줄은 해당 2*min
하위 문자열을 확인할 필요가 없습니다. 대신 seen
배열 해시 에 추가하고 인쇄할 수 있습니다 ( !seen[$0]++
중복되지 않은 항목을 인쇄하기 위한 조건으로 사용됨, 추가 정보:awk '!a[$0]++'는 어떻게 작동하나요?). min
하위 문자열을 확인할 때 컷오프 길이로 사용할 수도 있습니다.
부분 문자열에 대한 라인을 스캔할 때 가능한 새로운 시작 위치를 모두 기록해야 합니다. 이는 k
이러한 오프셋을 저장하는 배열을 사용하여 수행됩니다. 하위 문자열을 검색하고 해당 문자열이 배열의 해시로 존재하는지 확인하세요 seen
. 표시된 문자열이 발견되면:
- 하위 문자열이 줄 끝에 있으면
next
입력 줄로 이동합니다. 해당 행은 인쇄되지 않거나 표시 배열에 추가되지 않습니다. - 그렇지 않으면 다음 시작 위치를 추가
k
하고 더 많은 하위 문자열을 계속 검색합니다. - 새로운 시작 위치를 찾는 동안 계속 시도하십시오 (
while (length(k))
). - 위의 루프가 다음 줄로 진행되지 않으면 해당 줄이
seen
배열 해시에 추가됩니다(또는 아직 표시되지 않은 경우 인쇄됩니다).
답변4
awk '{for (i in a) if (index($0,i)) next; print; a[$0]}' file