awk '!a[$0]++'는 어떻게 작동하나요?

awk '!a[$0]++'는 어떻게 작동하나요?

이 줄은 사전 정렬 없이 텍스트 입력에서 중복된 줄을 제거합니다.

예를 들어:

$ 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 대신 sior를 사용할 수도 있습니다 .stepisstep

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의 정점. 짧지만 강력하고 신비롭다. 순서를 유지하면서 중복을 제거합니다. 인접한 중복 항목 uniqsort -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

그들은 어떻게 함께 일합니까?

대략적인 순서는 다음과 같습니다.

  1. $0현재 행입니다
  2. a[$0]배열의 현재 행 값입니다.
  3. Post-increment( ++)는 에서 값을 가져 a[$0]와서 다시 저장한 다음 a[$0]원래 값을 해당 줄의 다음 연산자인 인버터에 "반환"합니다.
  4. negator( )는 에서 원래 값인 !값을 가져옵니다 . 부울로 평가된 다음 부정된 다음 암시적 if에 전달됩니다.++a[$0]
  5. 그런 다음 행을 인쇄할지 여부를 결정합니다.

따라서 이는 해당 행이 인쇄되는지 여부 또는 이 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++싶었고 . 하지만++exprexpr=expr+1

$ awk '!a[$0]++' f # or 
$ awk '!(a[$0]++)' f

추가하기 전에 expr++평가되므로 모든 고유 값을 인쇄합니다.expr

$ awk '!(++a[$0])' f

이 경우 항상 0이 아닌 값을 반환하고 부정은 항상 0 값을 반환하기 때문에 무엇이든 인쇄할 것입니다 ++expr.expr+1

관련 정보