ファイルに記載された文字列の置換

時々 2ch を覗くのですが、このような質問が書かれてあったので、練習として解いてみます。 最近はモダンなプログラム記法というのが Perl を筆頭に流行っているようですが、awk でも gawk 標準のライブラリがあり、include によりこれを有効に活用することで少しだけモダンな awk プログラムになります。

2ch の他の方の解答にもありますが、特に以下の点に少し気を使ってみました。

  • ファイル名をオプションで指定する
  • ファイルの有無をチェック
  • 置換文字列のペアがあることをチェック
  • gawk 用の length() の使い方と delete の使い方で解く
  • それでも awk らしく

完成したものは以下のようなものです。

#! /usr/bin/igawk -f
# 2ch_551.awk
# http://pc11.2ch.net/test/read.cgi/unix/1224085718/551

# require getopt and join library functions
@include getopt.awk

BEGIN {

    # オプションの処理
    while ((option = getopt(ARGC, ARGV, "t:s:d:")) != -1) {
        if (option == "t") {
            input_file = Optarg;
            check_file(input_file);
        }
        if (option == "s") {
            before_file = Optarg;
            check_file(before_file);
        }
        if (option == "d") {
            after_file = Optarg;
            check_file(after_file);
        }
    }

    # 置換前文字列の読み込み
    while (getline < before_file > 0) {
        before_arr[++i] = $0;
    }
    close(before_file);

    # 置換後文字列の読み込み
    while (getline < after_file > 0) {
        after_arr[++j] = $0;
    }
    close(after_file);

    # 置換前後の対象数が同じであることをチェック
    if (is_same_length(before_arr, after_arr) == 0) {
        print "配列 " a " と配列 " b " の長さが異なります" > "/dev/stderr";
        exit 1;
    }

    # ARGV を再設定する
    # input_file を getline で読んでも構わないが、少しだけ awk らしく?
    delete ARGV;
    ARGV[1] = input_file;
}

{

    # 格納した配列で置換を行っていく
    for (i in before_arr) {
        if ($0 == before_arr[i]) {
            sub(before_arr[i], after_arr[i], $0);
        }
    }

    # 置換したものを出力
    print $0;
}

# usage: 使用方法の説明
function usage() {
    print "igawk -f 2ch_551.awk -t input -s before -d after" > "/dev/stderr";
}

# check_file: ファイルの有無をチェック
function check_file(file) {
    if (getline < file <= 0) {
        print file " というファイルがありません" > "/dev/stderr";
        usage();
        exit 1;
    }
    close(file);
}

# is_same_length: 配列 a と配列 b の長さが同じかどうかチェック
function is_same_length(a, b) {
    if (length(a) == length(b)) {
        return 1;
    } else {
        return 0;
    }
}

少し長めのプログラムになっていますが、その分、わかりやすくしているつもりです。 かなりパートに分解していますので、簡略化するのも、仕様を少しくらい複雑にすることも楽なようにしています。

なお、実行は include を含んでいますので、igawk で実行します。

$ cat a.dat
111
AAA
BBB
CCC
222
$ cat b.dat
AAA
BBB
CCC
$ cat c.dat
DDD
EEE
FFF

$ igawk -f 2ch_551.awk -t a.dat -s b.dat -d c.dat
111
DDD
EEE
FFF
222

tag_gawk.pngtag_gawk.png