Normal awk - 配列インデックスを簡単に並べ替え、選択した順序で出力します。

Normal awk - 配列インデックスを簡単に並べ替え、選択した順序で出力します。

[編集:明確にするには突然解決策と修正あいまいな「値」の代わりに「インデックス」をソートする必要があります(またはソートされた方法で出力)。

awkはしばしば数値を数えるか、値セットを配列に格納し、その値をインデックスとして使用します(awkのindexes_are_hashesメカニズムを使用)。

たとえば、私が出会った$2値の数と各値の発生頻度を知りたい場合は、次のようにします。

awk '
   ... several different treatments ...
   { count[$2]++ } 
   ... other treatments ...
   END { for(str in count) { 
           print "counted: " str " : " count[str] " times." 
           ... and other lines underneath, with additional infos ...
          }
       }
 '

問題は(GNUではないか、より良い)一般的なawk(および一般的なnawk)です。

  • [A] は「出会った」順に他の値を出力しません。
  • [B] 数字やアルファベット順にインデックスをナビゲートする簡単な方法も提供しません。

[A]の場合:それほど難しくありません。 「新しく見た」項目を索引付けする別の配列です。

問題は[B]についてです。簡単な呼び出しで他のインデックスの表示順序をどのように変更できますか?

(注:私はgnu awkが[B]に対して「簡単な」方法を持っていることを知っています:https://www.gnu.org/software/gawk/manual/html_node/Controlling-Array-Traversal.html...しかし、通常のawk / nawkで同様のことをする方法が欲しい! )

(例:表示されている別のインデックスを出力してソートし、[以前のawkで...]「何か」として再度読み込む必要があります(たとえば、別の配列が注文された_seen?)、それを使用してそれを表示する必要があります。見ることはあなたが選んだ順序で行わなければなりません。内部awk各インデックスの下に追加情報を出力する必要があることがよくあります。 awk以外の「ソート」はすべてを並べ替えます)

これまで、私はこれを行うための「公理的」断線(またはn線?)方法を見つけることができませんでした。

私は並べ替えで各値をファイルに出力するのに数行かかるパッチワークで終わりました。次に、ソートされたファイルを再度読み取り、各行を sorted_countindexes[n++] に順番に挿入し、for(i =0;i <= n;i++){ ...出力数[sorted_countindexes[n]]... }

通常のawk(またはnawk)の場合、ソートされた出力に基づくより良い/より単純な/より「公理的な」インデックスを歓迎します。

MCVE:以下は簡単な例です。インデックスをアルファベット順に出力するのが良いでしょう。

# create the 2 basic files to be parsed by the awk:
printf 'a b a a a c c d e s s s s e f s a e r r f\ng f r e d e z z c s d r\n' >fileA
printf 's f g r e d f g e z s d v f e z a d d g r f e a\ns d f e r\n'>fileB
# and the awk loop: It outputs in 'whatever order', I want in 'alphabetical order'
for f in file? ; do printf 'for file: %s: ' "$f"
  tr ' ' '\n' < "$f" | awk ' 
       { count[$0]++ } 
   END { for(str in count){ 
           printf("%s:%d ",str,count[str]) 
          }; print "" 
       } '
done
#this outputs:
for file: fileA: d:3 e:5 f:3 g:1 r:4 s:6 z:2 a:5 b:1 c:3
for file: fileB: d:5 e:5 f:5 g:3 r:3 s:3 v:1 z:2 a:2
# I'd like to have the letters outputted in alphabetical order instead!

答え1

GNUを使用すると、「コプロセッシング」機能()を使用して双方向に対話でき、awkソートするデータを送信し、gawkを使用して結果を取得できますが、これはgawkに固有のものです。sortinfo gawk coprocprint |& "sort""sort" |& getline

配列を通るループ出会いの順番、録音できます出会いの順番配列を埋めるとき:

awk '
  !seen[$1]++ {sequence[n++] = $1}
  END {
    for (i = 0; i < n; i++)
      print sequence[i], seen[sequence[i]]
  }'

NETでソートアルゴリズムを実装することもできますawk。借りることもできgawkquicksort.awk借りることもできますよマニュアルで探す(ここでは、比較ルーチンのリテラル呼び出しで置き換えることができる別のGNU固有の機能である間接関数呼び出しを示します。)それは次のとおりです。

awk '
  function less_than(left, right) {
    return "" left <= "" right
  }
  function quicksort(data, left, right,   i, last)
  {
    if (left >= right)
      return

    quicksort_swap(data, left, int((left + right) / 2))
    last = left
    for (i = left + 1; i <= right; i++)
      if (less_than(data[i], data[left]))
        quicksort_swap(data, ++last, i)
    quicksort_swap(data, left, last)
    quicksort(data, left, last - 1)
    quicksort(data, last + 1, right)
  }
  function quicksort_swap(data, i, j,   temp)
  {
    temp = data[i]
    data[i] = data[j]
    data[j] = temp
  }

  {seen[$1]++}
  END {
    for (i in seen) keys[n++]=i
    quicksort(keys, 0, n-1)
    for (i = 0; i < n; i++)
      print keys[i], seen[keys[i]]
  }'

perl個人的に私はここで代わりに使用しますawk

答え2

$ cat tst.awk
{ cnt[$0]++ }
END {
    n = sort(cnt,idxs)
    for (i=1; i<=n; i++) {
        idx = idxs[i]
        printf "%s:%d%s", idx, cnt[idx], (i<n ? OFS : ORS)
    }

}

function sort(arr, idxs, args,      i, str, cmd) {
    for (i in arr) {
        gsub(/\047/, "\047\\\047\047", i)
        str = str i ORS
    }

    cmd = "printf \047%s\047 \047" str "\047 |sort " args

    i = 0
    while ( (cmd | getline idx) > 0 ) {
        idxs[++i] = idx
    }

    close(cmd)

    return i
}

# create the 2 basic files to be parsed by the awk:
printf 'a b a a a c c d e s s s s e f s a e r r f\ng f r e d e z z c s d r\n' >fileA
printf 's f g r e d f g e z s d v f e z a d d g r f e a\ns d f e r\n'>fileB

for f in fileA fileB ; do
    printf 'for file: %s: ' "$f"
    tr ' ' '\n' < "$f" |
    awk -f tst.awk
done
for file: fileA: a:5 b:1 c:3 d:3 e:5 f:3 g:1 r:4 s:6 z:2
for file: fileB: a:2 d:5 e:5 f:5 g:3 r:3 s:3 v:1 z:2

上記は単に配列インデックスから改行で区切られた文字列を作成し(適切に引用するためにsh)、その文字列にパイプするシェルスクリプトを生成してsortから出力を繰り返します。 sの動作を変更するには、sort関数呼び出しにUnixsort引数文字列を追加するだけですsort(例:)sort(seen,"-fu")sort()返されたときに繰り返されるインデックス配列を埋める代わりに(必要に応じて)印刷するか、関数内で他の目的を実行するように変更することができますが、関数には凝集力があります。

ただし、システムの最大コマンドライン長に制限されます。

\047コードのsは、シェルを区切り文字列またはスクリプト'に含めることができないことを意味します。したがって、上記のようにファイルから読み取るawkスクリプトで直接使用できますが、他の場所で使用したい場合は、コマンドラインを置き換える項目が必要で、スクリプトがコマンドラインとファイルの両方で解釈されると機能するため、-replacementの最も移植性優れたオプションです。''awk 'script' file'\047'

s '\047s)strは、文字列がパイプでソートされたときにシェルが変数を拡張せず、引用符が一致しないことを確認するために存在します。つまり、次のことを行います。

$ echo 'foo'\''bar $(ls) $HOME' | awk '{
    str=$0; gsub(/\047/, "\047\\\047\047", str); print "str="str
    cmd="printf \047%s\047 \047" str "\047"; print "cmd="cmd
}'
str=foo'\''bar $(ls) $HOME
cmd=printf '%s' 'foo'\''bar $(ls) $HOME'

したがって、私たちは壊れやすく、バグのあるものを得ることができず、代わりに次のような結果を得ます。

$ echo 'foo'\''bar $(ls) $HOME' | awk '{
    str=$0; print "str="str
    cmd="printf \"%s\" \"" str "\""; print "cmd="cmd
}'
str=foo'bar $(ls) $HOME
cmd=printf "%s" "foo'bar $(ls) $HOME"

関連情報