なぜ「制御文字除去」が必要になるのか
業務で外部システムやユーザー入力の文字列を扱っていると、ときどき「見えない変な文字」が紛れ込みます。
コピー&ペーストしたテキスト、外部 API のレスポンス、古いシステムからのデータなどが典型です。
ログに出すとレイアウトが崩れる。
画面に出すと、ブラウザが変な表示になる。
CSV や JSON に混ざると、パースエラーやツール側の不具合になる。
こういう原因になりやすいのが「制御文字」です。
だから、「ログや画面に出す前に制御文字をきれいに落としておく」ユーティリティは、実務でかなり役に立ちます。
制御文字とは何かをざっくり押さえる
ASCII の「0〜31」と「127」が制御文字
一般的に「制御文字」と言うと、ASCII コードのうち次の範囲を指します。
0x00〜0x1F(0〜31)0x7F(DEL)
ここには、タブや改行も含まれます。
0x09… タブ\t0x0A… 改行(LF)\n0x0D… 復帰(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 パターンをユーティリティ関数として用意しておけば、
「ログが汚い」「画面が崩れる」といった地味なトラブルをかなり減らせます。
