行の中をソートする

オープンソースカンファレンス2012 Tokyo/Fall - オープンソースの文化祭! には多くの方にブースに来ていただきありがとうございました。 その中で、行の中をソートするというお題がありましたので、awk で処理してみます。

ここでは gawk の asort 関数を用いたものと、gawk の双方向パイプを用いたものを紹介しておきます。 前者は asort 関数を使っていますが、awk でソートを組めば nawk などでも対応させることができます。 また、行のフィールド数はバラバラであることを前提にしています。

まずは asort 関数を用いたものです。

#! /usr/local/bin/gawk -f
# sort_rows.awk

{
    for (i = 1; i <= NF; i++) {
        arr[i] = $i;
    }

    asort(arr);

    str = "";
    for (i = 1; i <= NF; i++) {
        str = str " " arr[i];
    }
    delete arr;
    sub(/^[ ]/, "", str);

    print str;
}

使うデータは以下のようなものを準備しました。

$ cat test.txt
3 4 5 6 8 1 9 7 2
1 4 3 2 5
a d b c e
1 2 a c b 3

では、実行してみましょう。

$ gawk -f sort_rows.awk test.txt
1 2 3 4 5 6 7 8 9
1 2 3 4 5
a b c d e
1 2 3 a b c

次に、双方向パイプを用いた例を紹介します。 一度パイプを使い sort コマンドに投げておき、sort が終わったら sort の結果を後から取得するというものです。

#! /usr/local/bin/gawk -f
# sort_rows.awk

BEGIN {
    cmd = "sort";
}

{
    for (i = 1; i <= NF; i++) {
        print $i |& cmd;
    }
    close(cmd, "to");

    str = "";
    while (cmd |& getline > 0) {
        str = str " " $0;
    }
    close(cmd);
    sub(/^[ ]/, "", str);

    print str;
}

パイプを使う場合など、後で close 関数でクローズする必要があるものに関しては、実行するコマンドを変数に一度代入しておくと、クローズ時に長々と記述する必要がないので、便利です。

では、こちらも実行してみます。

$ gawk -f sort_rows.awk test.txt
1 2 3 4 5 6 7 8 9
1 2 3 4 5
a b c d e
1 2 3 a b c

また、gawk4 以降であれば PROCINFO で for ループのソートをコントロールすることができますので、以下のような表現も可能です。

#! /usr/local/bin/gawk -f
# sort_rows.awk

{
    for (i = 1; i <= NF; i++) {
        arr[i] = $i;
    }

    PROCINFO["sorted_in"] = "@val_str_asc";

    str = "";
    for (i in arr) {
        str = str " " arr[i];
    }
    delete arr;
    sub(/^[ ]/, "", str);

    print str;
}

様々な awk を用いたソートをお試しください。

tag_gawk.png tag_gawk.png