文字列中に含まれる特定の文字数を数える awk プログラム

多量のメモリと速い CPU という最近のコンピュータは、 1980 年代当初の 16 bit メモリ空間ミニコン時代とは大違いで、 よほどのことがない限り、 事務処理のほとんどが簡単な awk のスクリプトで間に合いますから、

  1. プログラミングが楽で、デバッグも速い
  2. 初心者でも読めるため、後々のメンテナンスが楽
  3. 良い社内教材になる
といった理由で、WEB サーバーの CGI スクリプトを含めて、 社内の事務処理のほとんどを、 awk で書くようになりました。

最近、そんな事務処理のスクリプトを書いていたら、 "1/23/456/7890" といった文字列に含まれる "/" の数を数えることになって、 c なら、

  /* 文字列 str に含まれる文字 ch の数を数える */
  chcount(str, ch) char *str;
  {
    int i;

    for (i = 0; *str; )
	if (*str++ == ch)
		i++;
    return i;
  }
といったあたりが素直だと思いますが、 こういった発想で awk のプログラムを書くと、 例えば、こんなことになります。
# 文字列 str に含まれる文字 ch の数を数える
function chcount(str, ch,  i) {
  for (i = 0; length(str) > 0; str = substr(str, 2))
	if (index(str, ch) == 1)
		i++
  return i
}
もちろん、これでも動くわけですが、ムダが多すぎます。

そこで、文字列の処理を、もう少し減らすと、

function chcount(str, ch,  i, k) {
  for (i = 0; length(str) > 0; i++)
	if ((k = index(str, ch)) > 0)
		str = substr(str, k + 1)
	else
		break
  return i
}
となって、多少はましですが、すっきりしません。

そこで、c から awk の発想に切替えると、

function chcount(str, ch,  a) {
  return split(str, a, ch) - 1
}
数えなければならない文字をセパレータと考えれば、 すぐ split() が頭に浮かぶはずですが、 そうならなかったのは、c プログラミングの時代が長かったためでしょうか?

さらに、発想の転換を進めると、 gsub() によるパターンの置換を使う方法が閃いて、 最初の例なら、

  str = "1/23/456/7890"
  print gsub(/\//, "/", str)
といったトリッキーなアプローチも生まれます。

素直な発想なら split() だと思いますが、 トリッキーなアイデアはプログラミングを労働から創造に転化させ、 人生に楽しみを与えます。

任意の文字を対象にする関数なら、

function chcount(str, ch) {
  return gsub("[\\" ch "]", ch, str)
}
といったところでしょうか。

gsub() のパターンに直接 ch を使わず、ch を含む文字セットにした理由は、

function chcount(str, ch) {
  return gsub(ch, ch, str)
}
で "|" といった文字を数えてみるとわかると思います。

プログラミングというのは、なかなか面白い仕事で、 文字数を数えるといった簡単な仕事でさえ、 多くの楽しみを生みだします。

平林 浩一, 2007