フィルタを使用した文字列操作 1

フィルタとは?

フィルタとは標準入力からデータを受け取り、そのデータを加工した上で標準出力に出力する機能のことである。この機能には主に sed や awk、tr、 grep などといったコマンドが使用され、主にそれらのコマンドに | (パイプ) でデータを受け渡すことでフィルタ機能を実現する。

  • コマンドの実行結果から次のコマンドのパラメータを生成したい
  • あるファイルを決まったパターンで編集したい
  • 膨大な文字数のログファイル内から必要な情報のみを抜き出したい

などといった処理には、フィルタを使用すると効率的に作業ができる。

command | filter
command | filter1 | filter2
command | filter1 | ... | filterN

→ パイプでフィルタに加工したいデータを受け渡し、目的とする文字列を抽出するフィルタリング処理を行う。

パイプを使用して不必要な文字列を消去する、または適切な位置に文字列を付加する、といったフィルタを挟み込むことで目的とするデータを抽出する。

また、フィルタをパイプで複数連結することで、より複雑なフィルタリング処理を実現することができる。

sed コマンドを使用したフィルタリング

文字列 A を文字列 B に置換する

sed コマンドはさまざまな機能を持ったコマンドだが、主には文字列の置換処理に利用されるコマンドである。置換処理を行う場合には、置換対象と置換後の文字列を指定してコマンドを実行する。置換対象の指定には一部の正規表現を使用することも可能である (一部の正規表現は使用できない。また、sed のバージョン等により使用できる正規表現も異なる)。

各行の最初に一致した文字列のみ置換

sed -e 's/パターン1/パターン2/'

→ 各行の最初に一致したパターン1をパターン2に置換する。

(g の指定なしだと、) 各行の最初に一致した文字列のみが置換対象となる。同一行内に複数の一致があった場合でも、2つめ以降の一致した文字列は置換されない

$ echo "hogehoge" | sed -e 's/hoge/fuga/'
fugahoge

文字列 “hogehoge” の “hoge” を “fuga” に置換。実際に置換されたのは先頭の “hoge” のみ。

一致したすべての文字列を置換

一致したすべての文字列を置換するには g (globally) を指定する。

sed -e 's/パターン1/パターン2/g'

→ sed コマンドの置換パターンに g を指定する。

g を指定すると各行の最初に一致した文字列のみではなく、すべての一致する文字列を置換対象とする。

$ echo "hogehoge" | sed -e 's/hoge/fuga/g'
fugafuga

g を指定して実行することで、すべての “hoge” が置換されていることが確認できる。

アルファベットの大文字・小文字を区別しない
sed -e 's/パターン1/パターン2/i'

i を指定することで大文字・小文字の区別を行わない。

i を指定するとアルファベットの大文字・小文字を区別しない置換処理を行う。例えばパターン1 に “a” を指定すると、“a” と “A” が置換対象となる。

$ echo "HogeHoge" | sed -e 's/hoge/fuga/i'
fugaHoge

i を指定することで、大文字・小文字を区別することなく置換処理が行われることが確認できる。

$ echo "HogeHoge" | sed -e 's/hoge/fuga/gi'
fugafuga

上記のように ig を同時に指定することも可能である。

連続置換
sed -e 's/パターン1/パターン2/' -e 's/パターン3/パターン4/'

→ 複数パターンの置換を連続して行うには、-e オプションで複数の置換パターンを連結して指定する。

各行最初に一致したパターン1をパターン2に置換後、さらにパターン3をパターン4に置換する、といった複数の置換処理を連続して実行することも可能である。そのような場合は、-e オプションで複数の置換パターンを連結して指定する。

$ echo "hogehoge fugafuga" | sed -e 's/hoge/foo/g' -e 's/fuga/bar/g'
foofoo barbar
#↑"hoge" を "foo" に置換した後、さらに "fuga" を "bar" に置換する。

$ echo "hogehoge fugafuga" | sed -e 's/hoge/foo/g' -e 's/fuga/bar/g' -e 's/ /-/g'
foofoo-barbar
#↑上記に加えてスペースをハイフンに置換する。

$ echo "hoge" | sed -e 's/hoge/fuga/' -e 's/fuga/foo/' -e 's/foo/bar/'
bar
#↑特に意味はないが "hoge" を次々に置換して最終的に "bar" にしている。
パターン指定の区切り文字を変更する
sed -e 's%パターン1%パターン2%'
sed -e 's|パターン1|パターン2|'
sed -e 's:パターン1:パターン2:'

→ パターン指定の区切り文字には、任意の文字を使用することが可能。

実はパターン指定を区切る文字は、/ (スラッシュ) 以外でも問題ない。sed コマンドは**「s」の直後に指定した文字を区切り文字として認識する**。したがって、使用する区切り文字は / でなくともよい。

特に / 自体がパターンに含まれる場合は、/ の代わりに %| などを区切り文字に使用すると、/ をエスケープする必要がなくなる。これは非常に便利なテクニックなのでぜひ覚えてほしい

$ echo "hoge/hoge" | sed -e 's/hoge\/hoge/fuga\/fuga/'
fuga/fuga

上記のように / を区切り文字にすると、/ がパターンに含まれる場合にエスケープする必要がある。これを / 以外の区切り文字に変更して実行してみる。

$ echo "hoge/hoge" | sed -e 's%hoge/hoge%fuga/fuga%'
fuga/fuga
$ echo "hoge/hoge" | sed -e 's|hoge/hoge|fuga/fuga|'
fuga/fuga
$ echo "hoge/hoge" | sed -e 's:hoge/hoge:fuga/fuga:'
fuga/fuga

このように / 自体をパターンに含めるときは / 以外の区切り文字を使用するとエスケープの必要がないので、可読性の面で非常に有利である。

単一の置換パターン
sed 's/パターン1/パターン2/'

→ 単一置換パターン指定であれば -e オプションは省略可能。

複数の置換パターンを指定しない場合は -e オプションは省略可能である。

$ echo "hoge" | sed 's/hoge/fuga/'
fuga

省略しても問題なく機能していることが確認できる。しかしながら、後から他のパターンを追加するような場合もあるので、常に指定することを推奨する。

文字列の削除

特定の文字列を削除するには、置換後のパターンに空文字を指定する (つまり何も指定しない)。これにより置換対象パターンと一致する文字列は、空文字に置換されることになるので、結果的に削除と同等の処理となる。

# 各行の最初に一致したパターンを削除する。
sed -e 's/パターン//'

# 全てのパターンを削除する。
sed -e 's/パターン//g'

→ 置換後の文字列を指定しないことで削除処理になる。

文字列の削除も置換処理と同じなので、通常の置換処理と使い方はまったく同じである。

$ echo "hoge-fuga foo-bar" | sed -e 's/-//'
hogefuga foo-bar
#※↑「g」を指定していないので、最初に一致した文字のみ削除される。

$ echo "hoge-fuga foo-bar" | sed -e 's/-//g'
hogefuga foobar
#※↑「g」を指定すると、全ての一致する文字列が削除される。

正規表現を使用した文字列置換

置換対象パターンの指定には、一部の正規表現を使用することも可能である。使用可能な正規表現は、sed のバージョン等により異なる場合がある。

正規表現 正規表現の意味
. (ドット) 空白を含む任意の1文字 (改行を除く)
\* 直前の文字の 0 ~ n 回の繰り返し
\+ 直前の文字の 1 ~ n 回の繰り返し
\{n\} 直前の文字の n 回の繰り返し
\{n,m\} 直前の文字の n ~ m 回の繰り返し
\{n,\} 直前の文字の n 回以上の繰り返し
.* (ドット アスタリスク) 空文字("")を含む任意の文字列
[aA0] a もしくは A もしくは 0
[^aA0] a でも A でも 0 でもない文字
[a-z] a から z までのいずれかの文字
[^0-9] 数字以外の文字
^ 行頭
^a 行頭の a
$ 行末
z$ 行末の z
^$ 空行

「a~e」を「X」に置換する

a から e までのいずれかの文字を、といったあいまいな置換対象には正規表現を使用するのが最適だ。

$ echo "abcdefghij" | sed -e 's/[a-e]/X/g'
XXXXXfghij

数字以外を削除する

「~以外」を表す [^~] を使用すると簡単に実現できる。

$ echo "I am 20 years old." | sed -e 's/[^0-9]//g'
20

行頭に文字を追加する

行頭に文字列を追加するには、行頭を表す を追加したい文字列に置換する。

$ echo "Hello World" | sed -e 's/^/>>>/g'
>>>Hello World

行末に文字列を追加する

行末に文字列を追加するには、行末を表す $ を追加したい文字列に置換する。

$ echo "Hello World" | sed -e 's/$/ !!!/g'
Hello World !!!

連続する複数のスペースを1つにまとめる

連続する複数のスペースは □□* で表される (“□” は半角スペースを表すものとする)。アスタリスクは直前の文字の 0-n 個の繰り返しなので、□* だと 0個のスペースも置換対象とみなされてしまう。

したがって、連続する複数のスペースを表すには「1個のスペース」+「0-n 個のスペース」で □□* となる。

$ echo "hello     world" | sed -e 's/  */ /'
hello world

GNU sed (Linux 上の sed) では□\+ で 1個以上の連続するスペースを表現することが可能である。

$ echo "hello     world" | sed -e 's/ \+/ /'
hello world

変数を使用した文字列置換

sed -e "s/$VAR1/置換後パターン/"
sed -e "s/置換対象パターン/$VAR/"
sed -e "s/$VAR1/$VAR2/"

→ sed コマンド内で変数を使用するには $ を打ち消さないようにダブルクォートを使用する。

置換に使用されるパターンの指定には、変数の値を使用することも可能である。変数は値に展開されてから sed コマンドに渡される必要があるため、' (シングルクォーテーション) で囲んで $ をエスケープしてはいけない。

変数を使用する場合は下記の例のように $ を打ち消さない " (ダブルクォーテーション) を使用する。

$ VAR="hoge"
#↑変数 VAR に "hoge" を設定する。

$ echo "hoge fuga \$VAR" | sed -e "s/$VAR/foo/"
foo fuga $VAR
#↑変数 VAR の値の "hoge" を "foo" に置換

$ echo "hoge fuga \$VAR" | sed -e "s/fuga/$VAR/"
hoge hoge $VAR
#↑変数 VAR の値 "hoge" が置換パターンに使用される。

$ echo "hoge fuga \$VAR" | sed -e 's/$VAR/foo/'
hoge fuga foo
#※↑シングルクォートだと変数の値ではなく、文字列 "$VAR" として置換される。

行を指定した置換

sed コマンドはテキスト全体を対象として処理を行うが、行番号を指定することにより限定された範囲内でのみ処理を行うようにすることが可能である。

# 特定の 1行のみで置換を行う。
sed -e '行番号s/パターン1/パターン2/'
# 開始行~終了行の範囲でのみ置換を行う。
sed -e '開始行,終了行s/パターン1/パターン2/'

s の直前に対象とする行番号を指定する。

ある特定の行のみを置換対象としたい、または n 行目から m 行目のみを置換対象としたい、といった場合には対象とする行の行番号を指定することで、処理が適用される範囲を限定することができる

以下のテキストファイル (test.txt) に対して、実際に置換処理を行ってみる。

$ cat test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 ABC
666 ABC
777 ABC
888 ABC
999 ABC

5行目のみを処理対象とする

$ sed -e '5s/ABC/OK OK OK OK OK/' test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 OK OK OK OK OK
666 ABC
777 ABC
888 ABC
999 ABC

sed コマンドに5行目を指定したので、5行目のみ置換されている。

4~6行目のみを処理対象とする

$ sed -e '4,6s/ABC/OK OK OK OK OK/' test.txt
111 ABC
222 ABC
333 ABC
444 OK OK OK OK OK
555 OK OK OK OK OK
666 OK OK OK OK OK
777 ABC
888 ABC
999 ABC

sed コマンドに4~6行目を指定したので、4~6行目のみ置換されている。

特定の行を削除する

特定の文字列を含む行を削除する

sed -e '/パターン/d'

→ 行そのものを削除するには d を指定する。

n 行目、または n 行目~ m 行目を削除、などのように指定した行を削除するには、削除対象となる行のパターンと d を指定することで、そのパターンを含む行を削除できる。この処理は置換ではなく削除なので、置換後のパターンを指定する必要はない。

以下のテキストファイル (test.txt) に対して、実際に削除処理を行ってみる。

$ cat test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 ABC
「333」を含む行を削除する
$ sed -e '/333/d' test.txt
111 ABC
222 ABC
444 ABC
555 ABC

「333」を含む行が削除されている。ちなみに grep -v でも同様の処理が可能である。

$ grep -v "333" test.txt
111 ABC
222 ABC
444 ABC
555 ABC

sed コマンドと同様に、「333」を含む行が削除されていることが確認できる。

空行を削除する

空行は行頭と行末の間に何も存在しない行のことなので、正規表現の ^$ で表すことができる。これを対象パターンに指定することで、空行を削除することが可能である。

$ cat emp.txt
111 ABC



555 ABC
$ sed -e '/^$/d' emp.txt
111 ABC
555 ABC

行中に含まれる文字列に関係なくn行目を削除する

# 単一行の削除
sed -e '行番号d'
# 複数行の削除
sed -e '開始行,終了行d'

→ パターンを指定せずに d に行番号のみを指定する。

対象パターンを指定せずに行番号のみを指定することで、含まれる文字列に関係なくその行を削除することができる。

$ cat test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 ABC

$ sed -e '3d' test.txt
111 ABC
222 ABC
444 ABC
555 ABC
※↑3行目を指定したので、3行目が削除されている。

$ sed -e '2,4d' test.txt
111 ABC
555 ABC
※↑ 2 と 4 を指定したので、2 ~ 4 行行目が削除されている。
最下行を削除する

最下行を表す $ を行番号の代わりに指定することも可能。

$ cat test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 ABC
$ sed -e '$d' test.txt
111 ABC
222 ABC
333 ABC
444 ABC

特定の行のみを表示する

n 行目、または n~m 行目のみを表示、など指定した行のみを表示する。

# n行目を表示
sed -n '行番号p'
# n行目~m行目を表示
sed -n '開始行,終了行p'

-n オプションと行番号、および print の p を指定する。

指定した行を表示するオプションは、-e ではなく -n となる。

1行目のみを表示する

$ cat test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 ABC

$ sed -n '1p' test.txt
111 ABC

1 を指定して1行目のみを表示。

2~4行目のみを表示する

$ cat test.txt
111 ABC
222 ABC
333 ABC
444 ABC
555 ABC

$ sed -n '2,4p' test.txt
222 ABC
333 ABC
444 ABC

2,4 を指定して 2~4行目を表示。

特定の位置の文字列を切り出す

sed コマンドで切り出す

5文字目から 10文字目までを切り出す、“ABC” の後にに続く 3文字を切り出す、といったように特定の条件に一致する部分的な文字列のみを切り出す。

sed -e 's/パターンA\(切り出し対象パターン\)パターンB/\1/'

→ 文字列を正規表現化し、切り出す部分をエスケープした () (小括弧) で囲む。

全体のパターン (パターンA+対象パターン+パターンB) に一致した文字列のうち、括弧で囲んだ対象パターンに一致する部分を、\1 を使用することで切り出すことができる。

また、括弧で囲む部分を増やすことで切り出す文字列を複数指定可能で、1番目の括弧は \1、2番目の括弧は \2 といったように括弧の順番に対応した番号を指定して切り出す。

なお、切り出し対象を指定する括弧は \( \) のようにエスケープする必要がある。

$ echo "ABC123DEF456" | sed -e 's/...\(...\).*/\1/'
123
$ echo "ABC123DEF456" | sed -e 's/...\(...\)...\(...\)/\1 \2/'
123 456
$ echo "ABC123DEF456" | sed -e 's/...\(...\)/\1 /'
123 DEF456

. (ドット)は任意の一文字を表すので ... は任意の 3文字を表す。

sed コマンドによる文字列の切り出しは、パターンに一致した文字列を括弧の中の文字列で置換するイメージになる。そのため、上記の 3つめの例のようにパターンに一致しなかった部分はそのまま出力される。

expr コマンドで切り出す

同様に expr コマンドでも文字列を切り出すことが可能だ。

expr "文字列" : "パターンA\(対象パターン\)パターンB"

→ 文字列を正規表現化し、切り出す部分をエスケープした () (小括弧) で囲む。

  • expr コマンドでは一度に複数の部分を切り出すことはできない。
  • sed コマンドを用いた場合と同様に括弧はエスケープする必要がある。
  • sed コマンドとは異なり、パターンに一致しなかった部分は出力されない。
$ expr "ABC123DEF456" : "...\(...\)"
123

↑ パターンに一致しない部分 DEF456 は出力されない。

$ expr "ABC123DEF456" : "...\(...\)...\(...\)"
123

↑ 一度に複数の部分を切り出すことはできない。

置換を行う sed コマンドとは異なるので、パターンに一致しない部分は無視される。また、複数箇所を括弧で囲んでも、切り出されるのは最初の括弧の部分のみである。

“^M” などの制御文字を削除する

Windows 上でテキストファイルを作成し、改行コードを LF に変換せずに CR+LF のままLinux(Unix) 上へ転送してしまった、といったミスを経験したことがある人は少なからずいるだろう。

改行コードを変換せずに転送されたファイルを、Linux(Unix) 上で vi などを使用して開いてみると、行末に ^M が表示されていることがある。

これは sed コマンドで削除可能であるが、文字列の指定に少しコツがいる。削除対象となる ^M は、通常の文字列ではなく制御文字 (制御コード) なので、sed コマンドにそのまま ^M と指定しても削除することはできない。

制御文字である ^M を入力するには、 [Ctrl]+[v]、[Ctrl]+[M] と連続して入力する。

実際に削除できるか実験してみよう。まず削除対象となる制御文字の ^M が含まれたテキストファイルを vi で開いてみる。

$ vi CRLF.txt
This is "^M" ->^M
This is "^M" ->^M
This is "^M" ->^M
This is "^M" ->^M
This is "^M" ->^M

~
~
~
~
~
"CRLF.txt" 6L, 86C

This is "^M" は通常の文字列、行末の ^M が削除対象となる制御文字となっている。

まず、sed コマンドに ^M と普通に入力した通常の文字列で置換を実行してみる。

$ sed -e 's/\^M//g' CRLF.txt >LF.txt

^ はエスケープしておくこと。

vi で結果を確認してみる。

$ vi CRLF.txt
This is "" ->^M
This is "" ->^M
This is "" ->^M
This is "" ->^M
This is "" ->^M

~
~
~
~
~
"LF.txt" 6L, 76C

削除されたのは普通の文字列のみで、制御文字は削除されていない。

今度は、[Ctrl]+[v]、[Ctrl]+[M] と入力した文字列で置換を実行してみる。

$ sed -e 's/^M//g' CRLF.txt >LF.txt

今回の “^” は制御文字なのでエスケープは不要。

vi で結果を確認してみる。

$ vi LF.txt
This is "^M" ->
This is "^M" ->
This is "^M" ->
This is "^M" ->
This is "^M" ->

~
~
~
~
~
"LF.txt" 6L, 81C

今度はみごとに制御文字が削除された。

参考サイト

テキストファイルの行末に^Mが表示される