第49回
ユーティリティを作る~文字列置換プログラム

文字列の置換~strchg関数

文字列を置換するstrchg関数を作りましょう。このプログラムの中心的な機能です。

関数の仕様

strchg関数では、
1行(改行まで)の文字列を対象に
その中から指定された文字列を見つけては置換する
という処理を行います。

この処理のためには、以下の3つの情報が必要です。

(1)読み込まれた1行の内容
(2)置き換えられる文字列(旧文字列)
(3)置き換える文字列(新文字列)

これら3つの情報は、プログラムの起動時に入力されるコマンドラインから取得できます。コマンドラインの内容は、main関数の引数――char型ポインタの配列であるargvが指し示しているので、main関数からstrchg関数を呼び出すときに、それらの情報を渡すことができます。

そこでstrchg関数では、引数として『それぞれの文字列の先頭を示すポインタ』を受け取ることにします。プロトタイプ宣言は以下のようになるでしょう。
void strchg(char *buf, const char *str1, const char *str2);

bufが「読み込まれた1行」、str1が「旧文字列」、str2が「新文字列」へのポインタです。読み込まれた1行もポインタで受け取るので、この関数内で内容を書き換えれば、関数を呼び出した側の文字列が直接書き換えられることになります。

置換する文字列がまったく存在しなかった場合には、「読み込まれた1行」は書き換えられません。そのため、特にエラーなどの情報を戻り値として返す必要はありません。

文字列置換の仕組み

では、文字列を置き換える仕組みを考えていきましょう。以下のような処理手順になります。

1.旧文字列を見つける
2.旧文字列を削除する
3.削除した場所に新文字列を挿入する

各処理を具体的に検討していきましょう。

1.旧文字列を見つける

文字列を見つける処理には、strstr関数を使います。その後、そこに新文字列を挿入する必要があるため、strstr関数から返ってくる「見つけた文字列の先頭を示すポインタ」を利用することになります。

たとえば、文字列"facabcxbabb"を指すポインタstrのn文字目に置き換えられる旧文字列"abc"が見つかり、それを新文字列"xyz"に置き換えるとします(図1)。


2.旧文字列を削除する

旧文字列を削除するには、元の文字列(ポインタstr)の先頭から旧文字列の先頭直前までと、旧文字列の直後から元の文字列の最後尾までに二分します。

それには、以下のような処理を行います(図2)。

2-1.元の文字列strの「n+旧文字列の文字数」の位置から最後までを一時的なバッファtempに保存する

2-2.元の文字列strのn文字目のところに'\0'を置く

'\0'は文字列の終わりを示す記号なので、元の文字列strは(旧文字列の直前である)n文字目のところで終了します。それ以降の(元からあった)文字列は「ごみ」として残りますが、処理上の問題はありません。


3.削除した場所に新文字列を挿入する

ここまでで旧文字列は削除され、文字列strに前半部分、tempに後半部分が保存されています。

文字列strの後に新文字列"xyz"をつなぎ、さらにその後に、文字列tempに保存しておいた後半部分の文字列をつなぎます(図3)。


1行の中に旧文字列が複数入っている場合もあります。そのため、strstr関数で旧文字列を見つけて新文字列を入れ替える処理は、1回きりでは不完全です。whileループで「旧文字列が見つからなくなるまで」繰り返さなければなりません。

  while ((p = strstr(buf, str1)) != NULL) {
                        :

strchgに必要な標準関数

文字列処理のための関数をおさらいしておきましょう。

以上の処理を実現するためには、以下の3つの機能を持つ関数が必要です。

  文字列の長さ(バイト数)を返す
  文字列をコピーする
  2つの文字列をつないで1つにする

これらは、以下のような標準関数として用意されています。これらの関数を使うには、string.hを取り込みます。

・size_t strlen(char *str);
strの示す文字列のバイト数を返します。size_tというデータ型は文字列の長さなどを表わすために定義されており、実質はint型です。

・char * strcpy(char *d, char *s);
dで示す文字列にsで示す文字列の内容をコピーします。dで示される文字列は、sの文字列より長くなければなりません。戻り値はd(コピー先のポインタ)です。

・char * strcat(char *d, char *s);
dで示す文字列の最後に、sで示す文字列を連結します。dの文字列には連結後の文字列を格納できるだけのサイズが必要です。戻り値はd(連結先のポインタ)です。

これで、strchg関数はリスト2のように記述できます。仕組みは複雑に見えますが、ソースにすると案外シンプルです。

文字列の後半部分を保存するchar型配列"tmp"の長さ(バイト数)は、とりあえず『1024バイト+1』としておきます。最終的には#defineプリプロセッサ指令によって"BUFSIZE"という記号定数を定義することにします。

リスト2:文字列を置換するstrchg関数
void strchg(char *buf, const char *str1, const char *str2)
{
  char tmp[1024 + 1];
  char *p;

  while ((p = strstr(buf, str1)) != NULL) {
      /* 見つからなくなるまで繰り返す
            pは旧文字列の先頭を指している */
    *p = '¥0'; /* 元の文字列を旧文字列の直前で区切って */
    p += strlen(str1);	/* ポインタを旧文字列の次の文字へ */
    strcpy(tmp, p);		/* 旧文字列から後を保存 */
    strcat(buf, str2);	/* 新文字列をその後につなぎ */
    strcat(buf, tmp);	/* さらに残りをつなぐ */
  }
}