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の頂点。短いが強力で神秘的です。順序を維持しながら重複を削除します。隣接する重複項目uniqのみをsort -u削除するか、重複項目を削除するために順序を破らなければならない達成されていない成果です。

私はこのawk onelinerがどのように機能するかを説明したいと思います。私はawkを知らない人もフォローできるように説明しようとしています。私はこれを行うことができたらと思います。

まず、背景知識:awkはプログラミング言語です。このコマンドは、awk '!a[$0]++'awkコードからawkインタプリタ/コンパイラを呼び出します!a[$0]++python -c 'print("foo")'またはに似ていますnode -e 'console.log("foo")'。 awkコードは通常1行で構成されています。 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固有の変数です。

プラスプラス(++)は、後位増加演算子です。この演算子は、変数の値を増やす2つの操作を実行するため、少し面倒です。ただし、さらなる処理のために増加していない元の値を「返す」こともあります。

   !        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

それでは分解してみましょう++

覚えておいてください。この演算子は2つのことを行います。つまり、変数の値を増やし、さらに処理するために元の値を返します。したがって、++2行になります。

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 で最初に使用されるすべての変数は暗黙的に宣言され、空の文字列で初期化されます。配列は除外されます。配列は空の配列として宣言され、初期化されます。

数値への暗黙的な++変換。空の文字列はゼロに変換されます。他の文字列は最良のアルゴリズムを介して数値に変換されます。文字列が数値として認識されない場合、再びゼロに変換されます。

boolean への暗黙的な変換です!。数値 0 と空の文字列は false に変換されます。他のすべてはtrueに変換されます。

これは、行が最初に表示されたときに空のa[$0]文字列に設定されることを意味します。空の文字列は0に変換されます++(さらに1にインクリメントされ、再保存されますa[$0])。 0パスはfalseに変換されます!。結果は!true なので、対応する行が印刷されます。

現在の値a[$0]は数値1です。

2行目が表示されると、a[$0]数値1がtrueに変換され、結果!がfalseになるため、印刷されません。

同じ行でもう会うと、数字が増えます。 0以外のすべての数値はtrueなので、結果!は常にfalseなので、その行は再印刷されません。

重複を削除する方法です。

長い話を短く:行がどのくらいの頻度で表示されるかをカウントします。 0の場合は印刷します。他の数字がある場合は印刷されません。隠された内容が多く、内容が短くなることがあります。


ボーナス:1行コードのいくつかのバリエーションとその機能の非常に簡単な説明。

$0(すべての行)を$2(2番目の列)に置き換えると、重複項目は削除されますが、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

この場合、常にゼロ以外の値を返し、否定は常にゼロ値を返すので、何でも印刷します++exprexpr+1

関連情報