コマンドの出力結果からANSIエスケープシーケンスのカラーリングを取り除く方法
はじめに
ターミナル操作時、コマンドの実行結果が色付けされていることがあります。
僕がプログラミングを始めた頃はもう少し淡白なコマンドの出力結果ばかりだったのですが、最近のツールは出力結果がカラフルなものも多いです。
ターミナル上の出力結果をカラフルに色付けしてくれるための制御文字は ANSI エスケープシーケンス と呼ばれます。
( 厳密に言うと、ANSI エスケープシーケンス には色付けだけでなく、カーソル位置、フォントスタイル、その他のオプションなどの制御文字が含まれているそうです。 )
コマンドによっては、出力結果をパイプで繋いだ場合にカラーリングの ANSI エスケープシーケンスを除去してくれ、パイプ先で文字列操作する際に大変便利です。
例えば、 ls
コマンドは ls -l
を実行すると色付けされますが、 ls -l | cat
のようにパイプでつなぐと制御文字が除去されます。cut
で切り取る場合は制御文字が邪魔になるので大変便利な挙動です。
しかし、コマンドによってはこのような色付けの制御文字を除去しません。
そんなときに ANSI エスケープシーケンスのカラーリング制御文字を取り除く方法について紹介します。
検証環境
$ uname -moi
aarch64 aarch64 GNU/Linux
$ head -n 3 /etc/os-release
PRETTY_NAME="Ubuntu Kinetic Kudu (development branch)"
NAME="Ubuntu"
VERSION_ID="22.10"
$ bash -version | head -n 1
GNU bash, バージョン 5.2.0(1)-release (aarch64-unknown-linux-gnu)
$ sed --version | head -n 2
sed (GNU sed) 4.8
パッケージ作成者: Debian
[...]
で囲まれた部分の文字列だけを取り出したいが ANSI エスケープシーケンスが邪魔をする場合
JavaScript 関連のツールを起動した際の文言として、以下のようなものがあります。
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;42m OK \u001b[0;39m]"
hoge (pid 12321) is starting... [ OK ]
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;41m NG \u001b[0;39m]"
hoge (pid 12321) is starting... [ NG ]
文字列だけだとわかりにくいので、スクリーンショットを貼っておきます。
echo
コマンドは -e
オプション付きで実行すると、引数に指定された文字列内の ANSI エスケープシーケンスを使ってターミナル上の表示を変更してくれます。
ここで、 [ OK ]
または [ NG ]
の部分だけを抽出したいとします。
grep
で抽出
grep
で抽出してみます。 -o
オプションをつけることで一致した部分のみを出力できます。
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;42m OK \u001b[0;39m]" | grep -o "\[.*\]"
[60G[ OK ]
想定していた文字列以外にも、おまけが付いてきました。 [60G
の部分は想定していない文字列です。
sed
で抽出
sed
で抽出してみます。[...]
以外の部分を置換し取り除いてみます。
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;42m OK \u001b[0;39m]" | sed "s/.*\(\[.*\]\).*/\1/"
[0;39m]
grep
のときよりも悪化しました。
sed
で ANSI エスケープシーケンスを取り除く
今回は echo
に引き渡す文字列を変更すれば解決はしますが、問題になるのは echo
で出力している部分が他のツールによって出力されているケースです。
自分ではツールに手を加えることができない場合は、 ANSI エスケープシーケンスの制御文字を取り除いてしまいましょう。
sed -E "s/\x1b\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[mGK]//g"
という呪文のような sed
コマンドを通すことで、制御文字が除去できます。
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;42m OK \u001b[0;39m]" | sed -E "s/\x1b\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[mGK]//g"
hoge (pid 12321) is starting...[ OK ]
やはり文字だけではわかりにくいのでスクリーンショットを貼っておきます。
先程のスクリーンショットとは異なり、[ OK ]
の部分が色付けされていません。
除去したあとの文字列に対して、 grep
あるいは sed
を適用してみます。
grep
で抽出
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;42m OK \u001b[0;39m]" | sed -E "s/\x1b\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[mGK]//g" | \grep -o "\[.*\]"
[ OK ]
sed
で抽出
$ echo -e "hoge (pid 12321) is starting...\u001b[60G[\u001b[0;42m OK \u001b[0;39m]" | sed -E "s/\x1b\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[mGK]//g" | sed "s/.*\(\[.*\]\).*/\1/"
[ OK ]
呪文のような sed
について
呪文のような sed
について。
sed -E "s/\x1b\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[mGK]//g"
\x1b\[
は \x1B\[
と大文字でも問題有りません。これはエスケープシーケンスの始まりを表します。[
は sed
内では特殊な意味を持つため、 \[
としてバックスラッシュでエスケープしています。
つまり、 \x1b[
はエスペープシーケンスの始まりを表現しています。
ちなみに echo
でエスペープシーケンスの始まりを指定する場合は、 \u001b[ 、 \033[ 、 \e[ のいずれかが利用でき、いずれも同じ意味になります。
$ echo -e '\u001b[38;5;123mHELLO'
HELLO
$ echo -e '\e[38;5;123mHELLO'
HELLO
$ echo -e '\033[38;5;123mHELLO'
HELLO
この後に 0;33
、 38;5;123
のように、 :
区切りの数字が 2 つ以上並んだ後、最後に m
が続きます。
$ echo -e '\033[0;33mHELLO'
HELLO
$ echo -e '\033[38;5;123mHELLO'
HELLO
カラーリングの制御だけであれば最終の文字が m
であるケースを除去すれば十分なのですが、 [mGK]
という置換条件が書かれており、 G
と K
も対象としています。
いずれもカーソル移動を制御するための文字列で、プログレスバーの制御で利用されることがあり、念の為除去しています。
コマンドラインツール ansifilter
を使うと簡単
sed
で頑張る方法を紹介してきたわけですが、実は便利なツールも提供されています。
ジャストフィットなツール名ですね。
macOS 環境の場合はインストールも brew install ansifilter
を実行するだけです。
こちらを使うとこねくり回していた呪文のような sed
命令から開放されます。
(スクリーンショットを貼るのが面倒になってきたので) cat -A
を実行することで、ANSI シーケンス文字列が視覚化できるため、これを利用します。
# $ は行末の改行を表す制御文字です
$ echo -e '\u001b[38;5;123mHELLO' | cat -A
^[[38;5;123mHELLO$
ansifilter
というツールを通した結果を確認します。
$ echo -e '\u001b[38;5;123mHELLO' | ansifilter | cat -A
HELLO$
行末の改行を表す $
以外は消えていることがわかります。
ただし、いくつかのコマンドのカラーリングされた出力を ansifilter
でフィルタリングしてみましたが、完全に ANSI シーケンスが除去しきれないケースがあり、 sed
方式のほうが確実かと思いました 。
ひとこと
改めて、入力あるいは出力がパイプされているかどうかの判定が行われているツールは素晴らしい。
ディスカッション
コメント一覧
まだ、コメントがありません