Jak działa awk '! A [0 $] ++'?

40

Ten jednowarstwowy usuwa zduplikowane linie z wprowadzania tekstu bez wstępnego sortowania.

Na przykład:

$ cat >f
q
w
e
w
r
$ awk '!a[$0]++' <f
q
w
e
r
$ 

Oryginalny kod znaleziony w Internecie brzmiał:

awk '!_[$0]++'

Było to dla mnie jeszcze bardziej kłopotliwe, ponieważ wziąłem _specjalne znaczenie w awk, podobnie jak w Perlu, ale okazało się, że to tylko nazwa tablicy.

Teraz rozumiem logikę stojącą za linią jednokreskową: każda linia wejściowa jest używana jako klucz w tablicy mieszającej, dlatego po zakończeniu skrót zawiera unikalne wiersze w kolejności nadejścia.

Chciałbym się dowiedzieć, jak dokładnie interpretacja tego zapisu jest interpretowana przez awk. Np. Co oznacza znak huku ( !) i inne elementy tego fragmentu kodu.

Jak to działa?

Alexander Shcheblikin
źródło
tytuł wprowadza w błąd, powinien wynosić 0 $ (zero), a nie $ o (o).
Archemar
2
Ponieważ jest to skrót, jest nieuporządkowany, więc „w kolejności nadejścia” nie jest właściwie poprawne.
Kevin

Odpowiedzi:

35

Zobaczmy,

 !a[$0]++

pierwszy

 a[$0]

patrzymy na wartość a[$0](tablica az całą linią wejściową ( $0) jako kluczem).

Jeśli nie istnieje ( !czy negacja w teście stanie się prawdą)

 !a[$0]

wypisujemy wiersz wejściowy $0(akcja domyślna).

Dodajemy również jeden ( ++) a[$0], więc następnym razem !a[$0]zostanie ustawiona wartość false.

Fajnie, znajdź !! Powinieneś rzucić okiem na kod golfa!

Archemar
źródło
1
Zatem istotą jest to: wyrażenie w pojedynczych cudzysłowach służy awkjako test dla każdej linii wejściowej; za każdym razem, gdy test się powiedzie, awkwykonuje akcję w nawiasach klamrowych, co w przypadku pominięcia jest {print}. Dzięki!
Alexander Shcheblikin
3
@Archemar: Ta odpowiedź jest zła, patrz moja.
cuonglm
@AlexanderShcheblikin w awk, akcja domyślna to {print $0}. Oznacza to, że wszystko ocenione jako prawda, wykona to jako domyślną. Na przykład awk '1' filedrukuje wszystkie linie, awk '$1' filedrukuje wszystkie linie, których pierwsze pole nie jest puste lub 0, itd.
fedorqui
6
@Gnouc Nie widzę poważnego błędu w tej odpowiedzi. Jeśli o to chodzi, przyrost jest rzeczywiście stosowany po obliczeniu wartości wyrażenia. To prawda, że ​​inkrementacja ma miejsce przed drukowaniem, ale jest to drobna niedokładność, która nie wpływa na podstawowe wyjaśnienie.
Gilles 'SO - przestań być zły'
1
Znalazłem najlepsze wytłumaczenie dla początkującego do zrozumienia tutaj w quora
GP92
30

Oto przetwarzanie:

  • a[$0]: spójrz na wartość klucza $0w tablicy asocjacyjnej a. Jeśli nie istnieje, utwórz go.

  • a[$0]++: zwiększ wartość a[$0], zwróć starą wartość jako wartość wyrażenia. Jeżeli a[$0]nie istnieje, powrót 0i przyrost a[$0]do 1( ++wraca operatora wartości numeryczne).

  • !a[$0]++: zaneguj wartość wyrażenia. Jeśli a[$0]++return 0, całe wyrażenie jest oceniane na true, należy wykonać awkdomyślną akcję print $0. W przeciwnym razie całe wyrażenie zostanie ocenione na false, awkponieważ nic nie robi.

Referencje:

Za pomocą gawkmożemy użyć dgawk (lub awk --debugnowszej wersji) do debugowania gawkskryptu. Najpierw utwórz gawkskrypt o nazwie test.awk:

BEGIN {                                                                         
    a = 0;                                                                      
    !a++;                                                                       
}

Następnie uruchomić:

dgawk -f test.awk

lub:

gawk --debug -f test.awk

W konsoli debuggera:

$ 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>

Widać, Op_postincrementzostał stracony wcześniej Op_not.

Możesz także użyć silub stepizamiast slub, stepaby lepiej widzieć:

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++;
Cuonglm
źródło
3
@Archemar: Twoja odpowiedź wskazuje, że !została zastosowana wcześniej ++.
cuonglm
6
Ta odpowiedź jest zła. Inkrementacja następuje po !obliczeniu wyniku operatora. Mylisz pierwszeństwo operatora ( !a[$0]++jest parsowane jak !(a[$0]++)) z kolejnością oceny (przypisanie nowej wartości a[$0]zdarza się po obliczeniu wartości wyrażenia).
Gilles 'SO - przestań być zły'
5
@Gnouc Napisano w cytowanym przez Ciebie fragmencie, a gdyby działał tak, jak to opisałeś, ten kod nie miałby pożądanego efektu. Najpierw !xobliczana jest wartość , gdzie xjest stara wartość a[$0]. Następnie a[$0]jest ustawiony na 1+x.
Gilles 'SO - przestań być zły'
7
Uważam, że twoja analiza tego, co robi awk, jest poprawna. Przepraszam, jeśli wczoraj sugerowałem inaczej. Jednak twoja krytyka odpowiedzi Archemara jest błędna. Archemar nie rozumie pierwszeństwa, ty, mylisz pierwszeństwo z kolejnością oceny (zobacz mój poprzedni komentarz). Jeśli usuniesz jakiekolwiek wzmianki o odpowiedzi Archemara w twojej, Twoja odpowiedź powinna być poprawna. Obecnie koncentruje się na udowodnieniu, że Archemar się myli, i tak nie jest.
Gilles 'SO - przestań być zły'
5
cóż, przynajmniej teraz wiem o debuggerze awk ...
Archemar