이 줄은 사전 정렬 없이 텍스트 입력에서 중복된 줄을 제거합니다.
예를 들어:
$ cat >f
q
w
e
w
r
$ awk '!a[$0]++' <f
q
w
e
r
$
인터넷에서 찾은 원본 코드는 다음과 같습니다.
awk '!_[$0]++'
Perl처럼 awk에서도 특별한 의미가 있다고 생각했는데 _
알고 보니 배열의 이름일 뿐이었기 때문입니다.
이제 나는 이 문장의 논리를 이해합니다. 각 입력 행은 해시 배열의 키로 사용되므로 완료되면 해시에 도착 순서대로 고유한 행이 포함됩니다.
내가 알고 싶은 것은 awk가 이 기호를 어떻게 해석하는지입니다. 예를 들어 !
느낌표( )의 의미와 이 코드 조각의 기타 요소입니다.
어떻게 작동하나요?
답변1
이것은 "직관적인" 답변입니다. awk의 메커니즘에 대한 더 자세한 설명은 @Cuonglm의를 참조하세요.
이 경우 !a[$0]++
후증분은 ++
따로 설정할 수 있습니다. 표현식의 값은 변경되지 않습니다. 그러니 여기를 보세요 !a[$0]
:
a[$0]
현재 행을 $0
배열의 키로 사용 a
하고 거기에 저장된 값을 가져옵니다. 이 특정 키가 이전에 참조된 적이 없는 경우 a[$0]
빈 문자열로 평가됩니다.
!a[$0]
!
이전 값을 부정합니다 . 비어 있거나 0(거짓)이면 이제 참 결과를 얻습니다. 0이 아닌 경우(true) 잘못된 결과를 얻게 됩니다. 전체 표현식이 true로 평가되면(즉, a[$0]
시작하도록 설정되지 않았음을 의미) 전체 행이 기본 작업으로 인쇄됩니다.
또한 사후 증가 연산자는 이전 값에 관계없이 1씩 증가하므로 a[$0]
다음에 배열의 동일한 값에 액세스하면 양수가 되고 전체 조건이 실패합니다.
답변2
프로세스는 다음과 같습니다.
a[$0]
$0
: 연관 배열의 키 값을 봅니다a
. 존재하지 않는 경우 빈 문자열로 자동 생성됩니다.a[$0]++
:증가된 값a[$0]
, 이전 값을 표현식의 값으로 반환합니다.++
연산자는 숫자 값을 반환하므로a[$0]
처음에 비어 있으면0
반환되고a[$0]
증가됩니다1
.!a[$0]++
: 표현식의 값을 부정합니다. (false)a[$0]++
가 반환 되면0
전체 표현식이 true로 평가되고awk
기본 작업이 수행됩니다print $0
. 그렇지 않고 전체 표현식이 false로 평가되면 추가 작업이 수행되지 않습니다.
인용하다:
그걸로 gawk
우리는 사용할 수 있습니다dgawk (또는 awk --debug
그 이상)디버그 gawk
스크립트. 먼저 다음 gawk
과 같은 스크립트를 만듭니다 test.awk
.
BEGIN {
a = 0;
!a++;
}
그런 다음 다음을 실행하십시오.
dgawk -f test.awk
또는:
gawk --debug -f test.awk
디버거 콘솔에서:
$ dgawk -f test.awk
dgawk> trace on
dgawk> watch a
Watchpoint 1: a
dgawk> run
Starting program:
[ 1:0x7fe59154cfe0] Op_rule : [in_rule = BEGIN] [source_file = test.awk]
[ 2:0x7fe59154bf80] Op_push_i : 0 [PERM|NUMCUR|NUMBER]
[ 2:0x7fe59154bf20] Op_store_var : a [do_reference = FALSE]
[ 3:0x7fe59154bf60] Op_push_lhs : a [do_reference = TRUE]
Stopping in BEGIN ...
Watchpoint 1: a
Old value: untyped variable
New value: 0
main() at `test.awk':3
3 !a++;
dgawk> step
[ 3:0x7fe59154bfc0] Op_postincrement :
[ 3:0x7fe59154bf40] Op_not :
Watchpoint 1: a
Old value: 0
New value: 1
main() at `test.awk':3
3 !a++;
dgawk>
보시 Op_postincrement
다시피 이전에 실행되었습니다 Op_not
.
더 명확하게 보려면 or 대신 si
or를 사용할 수도 있습니다 .stepi
s
step
dgawk> si
[ 3:0x7ff061ac1fc0] Op_postincrement :
3 !a++;
dgawk> si
[ 3:0x7ff061ac1f40] Op_not :
Watchpoint 1: a
Old value: 0
New value: 1
main() at `test.awk':3
3 !a++;
답변3
아, 어디에나 존재하지만 불길한 awk 중복 제거기
awk '!a[$0]++'
이 귀여운 아기는 awk의 힘과 단순함을 사랑하는 사람입니다. awk oneliner의 정점. 짧지만 강력하고 신비롭다. 순서를 유지하면서 중복을 제거합니다. 인접한 중복 항목 uniq
만 sort -u
제거하거나 중복 항목을 제거하기 위해 순서를 깨야 하는 달성되지 않은 업적입니다 .
나는 이 awk oneliner가 어떻게 작동하는지 설명하려고 합니다. 나는 awk를 모르는 사람들도 따라갈 수 있도록 설명하려고 노력합니다. 내가 이것을 할 수 있었으면 좋겠다.
먼저 배경 지식: awk는 프로그래밍 언어입니다. 이 명령은 awk '!a[$0]++'
awk 코드에서 awk 인터프리터/컴파일러를 호출합니다 !a[$0]++
. python -c 'print("foo")'
또는 와 유사합니다 node -e 'console.log("foo")'
. awk 코드는 일반적으로 한 줄로 구성됩니다. awk는 간결한 텍스트 필터링을 위해 특별히 설계되었기 때문입니다.
이제 의사 코드가 있습니다. 이 패드는 기본적으로 다음을 수행합니다.
for every line of input
if i have not seen this line before then
print line
take note that i have now seen this line
순서를 유지하면서 중복을 제거하는 방법을 확인하시기 바랍니다.
그러나 문자열을 반복하고, if하고, 인쇄하고, 저장하고 검색하는 메커니즘이 8자 awk 코드에 어떻게 적합합니까? 대답은 암시적입니다.
루프, if 및 인쇄는 암시적입니다.
설명하기 위해 의사 코드를 다시 확인해 보겠습니다.
for every line of input
if line matches condition then
execute code block
이는 어떤 언어로든 코드에 어떤 형식으로든 많이 작성해 본 일반적인 필터입니다. awk 언어는 이러한 필터를 작성하는 데 시간이 거의 걸리지 않도록 설계되었습니다.
awk는 우리를 위해 루프를 수행하므로 루프 내부에 코드만 작성하면 됩니다. awk의 구문은 if 상용구를 추가로 제거하므로 조건과 코드 블록만 작성하면 됩니다.
condition { code block }
awk에서는 이것을 "규칙"이라고 합니다.
조건부나 코드 블록 중 하나를 생략할 수 있으며(분명히 둘 다 생략할 수는 없습니다) awk는 누락된 부분을 일부 암시적으로 채울 것입니다.
조건을 생략하면
{ code block }
그러면 그것은 암묵적으로 참이 될 것이다
true { code block }
이는 코드 블록이 각 라인에 대해 실행됨을 의미합니다.
코드 블록을 생략하면
condition
그런 다음 암시적으로 현재 줄을 인쇄합니다.
condition { print current line }
원래 awk 코드를 다시 살펴보겠습니다.
!a[$0]++
중괄호 안에 있지 않으므로 규칙의 조건부 부분입니다.
암시적 루프를 작성하고 if 및 인쇄해 보겠습니다.
for every line of input
if !a[$0]++ then
print line
원래 의사코드와 비교
for every line of input # implicit by awk
if i have not seen this line before then # at least we know the conditional part
print line # implicit by awk
take note that i have now seen this line # ???
루프, if 및 인쇄에 대해 배웠습니다. 그러나 행이 반복되는 경우에만 false로 평가되도록 하려면 어떻게 작동합니까? 본 라인을 어떻게 기록합니까?
이 짐승을 분해해 봅시다:
!a[$0]++
C나 Java를 알고 있다면 이미 몇 가지 기호도 알고 있어야 합니다. 의미론은 동일하거나 적어도 유사합니다.
느낌표( !
)는 부정적인 단어입니다. 표현식을 결과에 관계없이 부정되는 부울 값으로 평가합니다. 표현식이 true로 평가되면 최종 결과는 false이고 그 반대도 마찬가지입니다.
a[..]
배열입니다. 연관 배열. 다른 언어에서는 지도나 사전이라고 부릅니다. awk에서는 모든 배열이 연관 배열입니다. a
특별한 의미 는 없습니다 . 단지 배열의 이름일 뿐입니다. x
또는 일 수도 있습니다 eliminatetheduplicate
.
$0
현재 입력 줄입니다. 이것은 awk 특정 변수입니다.
더하기 더하기( ++
)는 후위 증가 연산자입니다. 이 연산자는 변수의 값을 증가시키는 두 가지 작업을 수행하기 때문에 약간 까다롭습니다. 그러나 추가 처리를 위해 증가하지 않은 원래 값을 "반환"하기도 합니다.
! a[ $0 ] ++
negator array current line post increment
그들은 어떻게 함께 일합니까?
대략적인 순서는 다음과 같습니다.
$0
현재 행입니다a[$0]
배열의 현재 행 값입니다.- Post-increment(
++
)는 에서 값을 가져a[$0]
와서 다시 저장한 다음a[$0]
원래 값을 해당 줄의 다음 연산자인 인버터에 "반환"합니다. - negator( )는 에서 원래 값인
!
값을 가져옵니다 . 부울로 평가된 다음 부정된 다음 암시적 if에 전달됩니다.++
a[$0]
- 그런 다음 행을 인쇄할지 여부를 결정합니다.
따라서 이는 해당 행이 인쇄되는지 여부 또는 이 awk 프로그램의 맥락에서 의미합니다. 해당 행이 반복되는지 여부는 궁극적으로 의 값에 의해 결정됩니다 a[$0]
.
++
확장: 증가된 값이 다시 저장될 때 행이 표시되었는지 여부를 기록하는 메커니즘이 발생해야 합니다 a[$0]
.
의사코드를 다시 살펴보겠습니다.
for every line of input
if i have not seen this line before then # decided based on value in a[$0]
print line
take note that i have now seen this line # happens by increment from ++
여러분 중 일부는 이미 이것이 어떻게 작동하는지 알고 있을 수도 있지만 여기까지 왔습니다. 마지막 몇 단계를 수행하고 조치를 취하겠습니다.++
암시적 awk 코드를 삽입하는 것부터 시작합니다.
for each line as $0
if !a[$0]++ then
print $0
작업할 여지를 남겨두기 위해 변수를 도입해 보겠습니다.
for each line as $0
tmp = a[$0]++
if !tmp then
print $0
이제 분해해 보겠습니다 ++
.
기억하세요. 이 연산자는 두 가지 작업을 수행합니다. 즉, 변수의 값을 증가시키고 추가 처리를 위해 원래 값을 반환합니다. 따라서 ++
두 줄이 됩니다.
for each line as $0
tmp = a[$0] # get original value
a[$0] = tmp + 1 # increment value in variable
if !tmp then
print $0
아니면 다른 말로 하면
for each line as $0
tmp = a[$0] # query if have seen this line
a[$0] = tmp + 1 # take note that has seen this line
if !tmp then
print $0
첫 번째 의사코드와 비교
for every line of input:
if i have not seen this line before:
print line
take note that i have now seen this line
그래서 우리는 그것을 가지고 있습니다. 루프, if, 인쇄, 쿼리 및 메모가 있습니다. 단지 순서가 의사코드와 다를 뿐입니다.
8자로 압축
!a[$0]++
아마도 암시적 루프, 암시적 if, 암시적 인쇄 및 ++
쿼리와 로깅을 동시에 수행하기 때문일 것입니다.
여전히 문제입니다. a[$0]
첫 번째 행의 값은 무엇입니까? 아니면 이전에 본 적이 없는 라인이 있습니까? 대답은 다시 암시적입니다.
awk에서 처음 사용되는 모든 변수는 암시적으로 선언되고 빈 문자열로 초기화됩니다. 배열은 제외됩니다. 배열은 빈 배열로 선언되고 초기화됩니다.
숫자로의 암시적 ++
변환. 빈 문자열은 0으로 변환됩니다. 다른 문자열은 최선의 알고리즘을 통해 숫자로 변환됩니다. 문자열이 숫자로 인식되지 않으면 다시 0으로 변환됩니다.
boolean 으로의 암시적 변환입니다 !
. 숫자 0과 빈 문자열은 false로 변환됩니다. 다른 모든 것은 true로 변환됩니다.
이는 행이 처음으로 표시되면 a[$0]
빈 문자열로 설정된다는 의미입니다. 빈 문자열은 0으로 변환됩니다 ++
(또한 1로 증가하고 다시 저장됨 a[$0]
). 0 패스는 false 로 변환됩니다 !
. 결과는 !
true이므로 해당 행이 인쇄됩니다.
현재 값 a[$0]
은 숫자 1입니다.
두 번째 줄이 보이면 a[$0]
숫자 1이 true로 변환되어 결과 !
가 false가 되므로 인쇄되지 않습니다.
같은 행에서 더 이상 만나면 숫자가 늘어납니다. 0을 제외한 모든 숫자는 true이므로 결과 !
는 항상 false이므로 해당 줄은 다시 인쇄되지 않습니다.
중복을 제거하는 방법입니다.
긴 이야기 짧게: 라인이 얼마나 자주 나타나는지 카운트합니다. 0이면 인쇄합니다. 다른 숫자가 있으면 인쇄되지 않습니다. 숨겨진 내용이 많아 내용이 짧을 수 있습니다.
보너스: 한 줄짜리 코드의 몇 가지 변형과 그 기능에 대한 매우 간단한 설명.
$0
(전체 행)을 $2
(두 번째 열)로 바꾸면 중복 항목이 제거되지만 두 번째 열만 기반으로 합니다.
$ cat input
x y z
p q r
a y b
$ awk '!a[$2]++' input
x y z
p q r
!
(negator)를 ==1
(equal to one)으로 바꾸면 반복되는 첫 번째 줄이 인쇄됩니다.
$ cat input
a
b
c
c
b
b
$ awk 'a[$0]++==1' input
c
b
>0
(0보다 큼) 으로 바꾸고 추가하면 {print NR":"$0}
줄 번호와 함께 모든 중복 줄이 인쇄됩니다. NR
줄 번호(awk 용어의 레코드 번호)를 포함하는 특수 awk 변수입니다.
$ awk 'a[$0]++>0 {print NR":"$0}' input
4:c
5:b
6:b
이러한 예가 위에서 설명한 개념을 더 잘 이해하는 데 도움이 되기를 바랍니다.
답변4
그냥 그것을 추가하고 expr++
싶었고 . 하지만++expr
expr=expr+1
$ awk '!a[$0]++' f # or
$ awk '!(a[$0]++)' f
추가하기 전에 expr++
평가되므로 모든 고유 값을 인쇄합니다.expr
$ awk '!(++a[$0])' f
이 경우 항상 0이 아닌 값을 반환하고 부정은 항상 0 값을 반환하기 때문에 무엇이든 인쇄할 것입니다 ++expr
.expr+1