PHP Tips | 文字列処理:ログ・表示向け - 制御文字除去

PHP PHP
スポンサーリンク

なぜ「制御文字除去」が必要になるのか

業務で外部システムやユーザー入力の文字列を扱っていると、ときどき「見えない変な文字」が紛れ込みます。
コピー&ペーストしたテキスト、外部 API のレスポンス、古いシステムからのデータなどが典型です。

ログに出すとレイアウトが崩れる。
画面に出すと、ブラウザが変な表示になる。
CSV や JSON に混ざると、パースエラーやツール側の不具合になる。

こういう原因になりやすいのが「制御文字」です。
だから、「ログや画面に出す前に制御文字をきれいに落としておく」ユーティリティは、実務でかなり役に立ちます。


制御文字とは何かをざっくり押さえる

ASCII の「0〜31」と「127」が制御文字

一般的に「制御文字」と言うと、ASCII コードのうち次の範囲を指します。

  • 0x000x1F(0〜31)
  • 0x7F(DEL)

ここには、タブや改行も含まれます。

  • 0x09 … タブ \t
  • 0x0A … 改行(LF)\n
  • 0x0D … 復帰(CR)\r

「全部消したい」こともあれば、「改行だけは残したい」こともあります。
なので、ユーティリティを作るときは「改行をどう扱うか」を最初に決めるのが大事です。


パターン① 改行も含めて制御文字を全部除去する

もっともシンプルな正規表現

「とにかく制御文字は全部いらない。改行も含めて消したい」という場合は、
次のような正規表現で一気に削除できます。

/**
 * 改行も含めて、ASCII 制御文字をすべて削除する
 */
function remove_control_chars_all(string $value): string
{
    // [\x00-\x1F\x7F] = 0〜31 と 127(DEL)
    return preg_replace('/[\x00-\x1F\x7F]/', '', $value) ?? '';
}
PHP

使い方の例です。

$text = "Hello\r\nWorld\x07"; // \x07 はベル(BEL)

$clean = remove_control_chars_all($text);

echo $clean; // HelloWorld
PHP

ここでの重要ポイントは、「改行も消える」ということです。
ログや 1 行で扱いたい文字列にする前処理としては、これでスッキリしますが、
複数行テキストとしての構造は失われます。


パターン② 改行だけは残して制御文字を除去する

ログや画面で「行構造」は残したい場合

多くの場面では、「改行は残したいけど、それ以外の制御文字はいらない」というニーズのほうが多いです。
その場合は、「0〜31 のうち、LF(0x0A) と CR(0x0D) だけ除外する」ように書きます。

/**
 * 改行(LF, CR)は残し、それ以外の制御文字を削除する
 */
function remove_control_chars_except_newline(string $value): string
{
    // 0x00-0x09, 0x0B, 0x0C, 0x0E-0x1F, 0x7F を削除
    return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value) ?? '';
}
PHP

使い方の例です。

$text = "Hello\r\nWorld\x07"; // \x07 = BEL

$clean = remove_control_chars_except_newline($text);

echo $clean;
// Hello
// World
PHP

改行はそのまま残り、ベルなどの制御文字だけが消えています。
ログや画面で「行ごとに読みたい」ケースでは、このパターンが扱いやすいです。


実務向けユーティリティとしてまとめる

用途別に 2 本用意しておく

実務では、「どっちの挙動も欲しくなる」ことが多いので、
最初から 2 本の関数を用意しておくと便利です。

/**
 * 制御文字をすべて削除(改行も消える)
 */
function sanitize_control_chars_flat(string $value): string
{
    return preg_replace('/[\x00-\x1F\x7F]/', '', $value) ?? '';
}

/**
 * 改行だけ残して制御文字を削除
 */
function sanitize_control_chars_keep_newline(string $value): string
{
    return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value) ?? '';
}
PHP

ざっくりとした使い分けのイメージはこうです。

ログの 1 行メッセージ、CSV の 1 セルに入れる前 → sanitize_control_chars_flat
複数行コメントを画面に出す前、ログに「複数行で」残したいとき → sanitize_control_chars_keep_newline

どちらも「preg_replace で制御文字の範囲を空文字に置き換える」というシンプルな実装なので、
初心者でもすぐに読めて、挙動も予測しやすいはずです。


例題:外部 API からのレスポンスをログに残す前処理

「見えないゴミ」が混ざる典型パターン

外部 API から JSON やテキストを受け取って、そのままログに書くと、
たまに「ログビューアが崩れる」「grep しづらい」原因になります。

そこで、ログに書く前に制御文字を落としておきます。

$responseBody = $client->getBody(); // 外部 API のレスポンス文字列

$logBody = sanitize_control_chars_keep_newline($responseBody);

error_log("[external-api-response]\n" . $logBody);
PHP

こうしておけば、「改行で行は分かる」「変な制御コードは消えている」という状態になり、
ログがかなり扱いやすくなります。


まとめ:今日からの「制御文字除去」ユーティリティ

制御文字除去の本質は、「人間にとって意味のない“見えないゴミ”を、ログや画面に出す前に落としておく」ことです。

ASCII 制御文字の範囲は \x00-\x1F\x7F
改行も含めて全部消すなら /[\x00-\x1F\x7F]/
改行だけ残したいなら /[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/

この 2 パターンをユーティリティ関数として用意しておけば、
「ログが汚い」「画面が崩れる」といった地味なトラブルをかなり減らせます。

タイトルとURLをコピーしました