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

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

特定のフィールドを切り出す

テキストファイルなどから n フィールド目を切り出す、n-m フィールド目を切り出す、といった処理には cut コマンドを使用する。

# numフィールド目を切り出す。
cut -d'デリミタ' -fnum

# num1フィールド目とnum2フィールド目を切り出す。
cut -d'デリミタ' -fnum1,num2

# num1-num2フィールド目を切り出す。
cut -d'デリミタ' -fnum1-num2

→ cut コマンドの -d オプションで特定のフィールドを切り出す。

cut コマンドにフィールド間の区切り文字となるデリミタと切り出すフィールド番号を指定することで、指定したデリミタによって区切られた特定のフィールドを切り出すことができる。

デリミタを指定しない場合は、デフォルトでタブが区切り文字として使用される。また、デリミタに , (カンマ) を指定し、CSV ファイルから特定のフィールドを切り出す、といった処理を簡単に行うことができる。

スペース区切りの2フィールド目を切り出す

cut -d' ' -f2
$ echo 'field1 field2 field3 field4 field5' | cut -d' ' -f2
field2

カンマ区切りの1フィールド目と3フィールド目を切り出す

cut -d',' -f1,3
$ echo "field1,field2,field3,field4,field5" | cut -d"," -f1,3
field1,field3

カンマ区切りの2フィールド目から4フィールド目を切り出す

cut -d',' -f2-4
$ echo "field1,field2,field3,field4,field5" | cut -d"," -f2-4
field2,field3,field4

カンマ区切りの3フィールド目以降を切り出す

cut -d',' -f3-
$ echo "field1,field2,field3,field4,field5" | cut -d"," -f3-
field3,field4,field5

タブ区切りの2フィールド目から5フィールド目を切り出す

cut -f2-5

-d オプションでデリミタを指定しない場合は、デフォルトでタブが使用される

$ echo -e "field1\tfield2\tfield3\tfield4\tfield5" | cut -f2-5
field2  field3  field4  field5

cut コマンドはデフォルトでタブがデリミタとして使用されるので、タブを区切り文字に使用したい場合は -d オプションを省略する。

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

テキストファイルなどから n 文字目の文字を切り出す、n-m 文字目の文字を切り出す、といった処理には cut コマンドを使用する。

# Num文字目の文字を切り出す。
cut -cNum

# Num1文字目とNum2文字目の文字を切り出す。
cut -cNum1,Num2

# num1-num2文字目の文字を切り出す。
cut -cNum1-Num2

→ cut コマンドの -c オプションで特定の位置の文字を切り出す。

cut コマンドに切り出す文字の位置を指定することで、文字列の中から特定の位置の文字のみを切り出すことができる。

3文字目を切り出す

cut -c3
$ echo "1234567" | cut -c3
3

1文字目と3文字目を切り出す

cut -c1,3
$ echo "1234567" | cut -c1,3
13

2-4文字目を切り出す

cut -c2-4
$ echo "1234567" | cut -c2-4
234

3文字目以降を切り出す

cut -c3-
$ echo "1234567" | cut -c3-
34567

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

特定の文字列を含む行のみを表示する

コマンドの出力結果や複数または単一のファイルから、特定の文字列を含む行を検索しその行を表示する、といった処理には grep コマンドを使用する。

grep WORD

→ grep コマンドで特定の文字列を含む行のみを抽出する。

grep コマンドは指定した文字列を含む行を検索し、その文字列を含む行のみを表示する。指定した文字列が見つからなかった場合は、何も表示されない。

実際に grep コマンドを使用してみる。以下は検索に使用するテキストファイル (test.txt)。

$ cat test.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz

検索結果は以下のとおり。

$ grep 's' test.txt
stu
$ echo $?
0

s が含まれる行が見つかったので、その行 stu が表示され、終了ステータスは 0 になる。

$ grep 'S' test.txt
$ echo $?
1

大文字の S は見つからないので、なにも表示されず、終了ステータスは 1 になる。

※ 以下、前述の test.txt ファイルと同一内容のファイルを使用するものとする。

“a” もしくは “z” を含む行を表示する (正規表現)

$ grep '[az]' test.txt
abc
yz

“f” から “l” のいずれかの文字を含む行を表示する (正規表現)

$ grep '[f-l]' test.txt
def
ghi
jkl

[] (大括弧) の中に - (ハイフン) で検索する文字の範囲を指定する。

行全体が特定のパターンと一致する行を表示する (正規表現)

$ grep '^abc$' test.txt
abc
$ grep '^ab$' test.txt
$ grep -x 'abc' test.txt
abc
$ grep -x 'ab' test.txt
$

行頭を意味する正規表現 ^ と、行末を意味する正規表現 $ でパターンを囲むことにより、指定したパターンと行全体が完全一致する行のみを表示することができる。

また、パターンと行全体が完全一致する行の検索は、-x オプションを指定することでも可能である。

OR 条件を使用した検索を行う (正規表現)

$ grep -E 'a|z' test.txt
abc
yz
$ grep -E 'a|m|z' test.txt
abc
mno
yz

-E オプションを指定することで拡張正規表現が使用可能になる。正規表現を使用する場合は、常に -E オプションを指定するようにしたほうがよい

次は複数ファイルを対象とした検索を行ってみる。検索対象となるテキストファイルは、以下の2つ (test1.txt、test2.txt) を使用する。

$ cat test1.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz
$ cat test2.txt
ABC
DEF
GHI
JKL
MNO
PQR
STU
VWX
YZ

複数のファイルからパターンを含む行を検索し表示する

$ grep -i 'abc' test*
test1.txt:abc
test2.txt:ABC

対象ファイルにワイルドカードを指定し、検索対象に複数ファイルを指定する。この場合は単一ファイルからの検索とは異なり、結果表示に該当ファイル名が含まれる。

また、以下のように引数に複数ファイルを指定することも可能である。

$ grep -i 'abc' test1.txt test2.txt
test1.txt:abc
test2.txt:ABC

なお、-i オプションは大文字・小文字を区別せずに検索を行うオプションである。

特定の文字列を含まない行のみを表示する

コマンドの出力結果や複数または単一のファイルから、特定の文字列を含まない行を検索しその行を表示する、といった処理には grep コマンドの -v オプションを使用する。

grep -v WORD

→ grep コマンドと -v オプションで特定の文字列を含まない行のみを抽出する。

grep コマンドは -v オプションを指定することにより、通常の grep コマンドの動作とは逆に、指定した文字列を含まない行のみを検索し表示する。

$ cat test.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz

$ grep -v 'a' test.txt
def
ghi
jkl
mno
pqr
stu
vwx
yz

-v オプションに指定した a が含まれない行のみ表示されていることが確認できる。

行番号を付加して表示する

grep コマンドは -n オプションを指定することにより、実行結果の先頭に行番号を付加して表示することができる。

grep -n WORD

→ grep コマンドと -n オプションで検索結果に行番号を付加する。

-n オプションを指定することにより、文字列に一致した行の行番号が付加された状態で結果が表示される。

$ cat test.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz

$ grep -n 'j' test.txt
4:jkl

検索ではなく単純にテキストファイルに行番号を付加したい場合は、grep コマンドに -n オプションと文字列 '' (空文字) を指定する。空文字で検索すると全ての行と一致するため、テキストファイルの全行に行番号が付加された状態で出力される。

$ grep -n '' test.txt
1:abc
2:def
3:ghi
4:jkl
5:mno
6:pqr
7:stu
8:vwx
9:yz

同様のことは cat コマンドに -n オプションを指定することでも可能だが、grep コマンドのほうがシンプルに表示される。

$ cat -n test.txt
     1  abc
     2  def
     3  ghi
     4  jkl
     5  mno
     6  pqr
     7  stu
     8  vwx
     9  yz

cat コマンドでは、上記の例のように全体がインデントされてしまう。

A行からB行までを切り出す

テキストファイルなどから、 “hogehoge” の行から “fugafuga” の行までを表示する、といった処理には grep コマンドの -A オプションと -B オプションを組み合わせて使用する。

grep -A 1000000 'hogehoge' textfile | grep -B 1000000 'fugafuga'

→ grep コマンドの -A オプションと -B オプションで特定の2行の行間を表示する。

  • -A オプションは、指定した文字列と一致した行から下の n 行を取得するオプションである。
  • -B オプションは -A オプションとは逆に、指定した文字列と一致した行から上の n 行を取得するオプションである。

上記のコマンドラインでは、最初の -A オプションで ‘hogehoge’ の行から最下行までを切り出し、次の -B オプションで ‘fugafuga’ の行から最上行、つまり最初に切り出した ‘hogehoge’ の行までを切り出している。

結果的にこの処理により残るのは、‘hogehoge’ 行から ‘fugafuga’ 行までのみとなる。

$ cat test.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz

$ grep -A 1000000 'ghi' test.txt | grep -B 1000000 'vwx'
ghi
jkl
mno
pqr
stu
vwx

最下行までと最上行までを取得するためにオプションに 1000000 と指定しているので、対象となるデータが 1000000 行を超えている場合は対応できない。ただ、1000000 行を超えるテキストファイルを扱う機会は稀なので、ほぼ問題はないと言ってもよいだろう。

厳密に処理を行いたい場合は、事前に wc -l で対象となるテキストファイルの総行数を取得し、その値を指定するとよい (取得する行間の行数が総行数を超えることはないので)。

また、対象となるデータに範囲指定に使用する文字列が複数行含まれている場合も対応できない

head コマンドと tail コマンドを使用したフィルタリング

先頭行から n 行を表示する

テキストファイルの先頭から n 行のみを表示したい、といった処理には head コマンドを使用する。

head -n 行数

→ head コマンドで先頭から指定した行数分表示する。

ファイルやコマンド実行結果などから最初の数行のみを表示したい場合は、head コマンドを使用することで、先頭行から数えて指定行数までを表示することができる。

$ seq 1 10
1
2
3
4
5
6
7
8
9
10
#↑seq コマンドで 1 ~ 10 までの連番を出力する。

$ seq 1 10 | head -n 3
1
2
3
#↑head コマンドで上3行を表示する。

連番を表示するのに使用している seq コマンドは、指定範囲で連番を作成するコマンドである。seq コマンドは for 文に組み込んで使用すると便利なので活用してほしい e.g. for i in $(seq 0 9)

先頭行から下から n 行目までを表示する

head コマンドにマイナスの行数を指定することで、「先頭行」から「最下行から n 行目」までを表示することができる。

head -n -末尾からの行数

→ head コマンドにマイナスの行数指定で末尾 n 行を除いて表示。

マイナスの行数は、最下行から数えた行数を意味し、head コマンドは先頭行からその行までを表示する。

このときの表示に n 行目は含まれない。つまり head コマンドへのマイナス行数指定は、最下行から n 行を消去するという処理になる。

head コマンドにマイナスを付けて行数指定した場合は、下の n 行を排除して表示すると考えると分かりやすい。

$ seq 1 10
1
2
3
4
5
6
7
8
9
10
$ seq 1 10 | head -n -3
1
2
3
4
5
6
7

下 3 行が消去されているのが確認できる。

最下行から n 行を表示する

テキストファイルの最下行から n 行のみを表示したい、といった処理には tail コマンドを使用する。

tail -n 行数

→ tail コマンドで下から n 行を表示する。

ファイルやコマンド実行結果などから下から数行のみを表示したい場合は、tail コマンドを使用することで、最下行から数えて指定行数までを表示することができる。

ログファイルの最新のログのみを参照したいときには、この tail コマンドを使用すると便利だ。

$ seq 1 10
1
2
3
4
5
6
7
8
9
10
$ seq 1 10 | tail -n 3
8
9
10

最下行から3行が表示されているのが確認できる。

先頭行 n 行目から最下行までを表示する

tail コマンドにプラス付き行数を指定することで、先頭 n 行目から最下行までを表示することができる。

tail -n +上からの行数

→ tail コマンドにプラスの行数指定で先頭 n 行を除いて表示。

プラスなし指定だと最下行から数えて n 行目から最下行までが表示されるが (マイナス指定でもこれと同じになる)、プラス付き指定では先頭行から数えて n 行目から最下行までが表示されるようになる。このときの表示には n 行目も含まれる。

$ seq 1 10
1
2
3
4
5
6
7
8
9
10
$ seq 1 10 | tail -n +3
3
4
5
6
7
8
9
10

3行目から最下行までが表示されているのが確認できる。

上から N 行目および下から M 行目までを表示する

テキストファイルの「最上行から N 行目」から「最下行から M 行目」までを表示したい、といった処理には head コマンドと tail コマンドを組み合わせて使用する。

# 下から M 行目が消えないように、コマンドには 1 を引いた値で指定する必要がある。
tail -n +N | head -n -(M-1)

→ head コマンドと tail コマンドの処理を合わせて使用する。

テキストファイルからヘッダ部分とフッタ部分を削除するといった処理は、この head コマンドと tail コマンドの組み合わせで実現可能である。

ヘッダの行数を N 行、フッタの行数を M 行として実行すると、全体からヘッダ・フッタを除いた部分を取得できる。

$ seq 1 10
1
2
3
4
5
6
7
8
9
10

$ seq 1 10 | tail -n +2 | head -n -4
2
3
4
5
6

「上から2行目」から「下から5行目」までを表示しているのが確認できる。

最初に tail コマンドで N 行目(2行目) から最下行までを出力し、次にパイプで受け取ったその出力から head コマンドで下から数えて M 行目(5行目) までを表示している。

head コマンドに M の値である 5 ではなく、1 引いた 4 を指定するのは、5 を指定すると下から 5 行目も削除されてしまうため、それを避けるためである。

以下は、tail コマンドおよび head コマンドが行う処理を視覚的に表した表である。

N=2, M=5 で実行した場合 (tail -n +2 | head -n -4)

行番号 【処理 1】 tail -n +N 【処理 2】 head -n -(M-1) 結果
1 消去 ←で消去済み 消去
2
3
4
5
6
7 消去 消去
8 消去 消去
9 消去 消去
10 消去 消去

上記の表から、tail コマンドと head コマンドのフィルタリング処理により、最終的に残されるのは、「結果」列のとおり 2-6 行のみとなる。

その他のコマンドを使用したフィルタリング

アルファベットの小文字を大文字に変換する

アルファベットの小文字を一括で大文字に変換したい、もしくは大文字を小文字に変換したい、といった処理には tr コマンドを使用する。

# 小文字から大文字へ変換する
tr '[a-z]' '[A-Z]'

# 大文字から小文字へ変換する
tr '[A-Z]' '[a-z]'

→ tr コマンドに正規表現を指定することで大文字・小文字の変換を行う。

tr コマンドと正規表現を使用することで、アルファベットを小文字から大文字へ、または小文字から大文字へと変換することができる。

小文字から大文字に変換する

$ echo "abcdefghijklmnopqrstuvwxyz" | tr '[a-z]' '[A-Z]'
ABCDEFGHIJKLMNOPQRSTUVWXYZ

大文字から小文字に変換する

$ echo "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | tr '[A-Z]' '[a-z]'
abcdefghijklmnopqrstuvwxyz

tr コマンドへの入力

tr コマンドは標準入力からデータを読み込み変換を行う。したがって以下のように引数にファイル名を指定して処理を行うことはできない

$ tr '[a-z]' '[A-Z]' test.txt
tr: extra operand `test.txt'
詳しくは `tr --help' を実行して下さい.

ファイル内容を処理対象としたい場合は、以下のように cat コマンドとパイプを使用して tr コマンドに変換対象となるデータを標準入力から渡す必要がある。

$ cat test.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz
$ cat test.txt | tr '[a-z]' '[A-Z]'
ABC
DEF
GHI
JKL
MNO
PQR
STU
VWX
YZ

このようにファイル内容を処理対象とする場合は、cat コマンドとパイプを使用すること。

空白で区切られた特定のフィールドを切り出す

awk '{ print $フィールド番号 }'

→ awk コマンドの組み込み変数で対象フィールドのみを出力する。

awk の組み込み変数 $x には x フィールド目に存在する文字列が設定されている。これを awk の print 文で出力することで、特定のフィールドのみを切り出すことができる。

空白区切りのフィールドを切り出すことは cut コマンドでも可能だが、フィールド間の空白数が一定でない場合は awk コマンドを使用する。

空白数が一定であるないに関わらず、空白区切りのフィールドを切り出すには awk コマンドを使用した方が無難だ

実際に特定のフィールドを切り出してみる。

$ cat awktest.txt
111    ABC         abc
            222 DEF   def
   333           GHI ghi
 444   JKL               jkl
555  MNO   mno
     666 PQR pqr
 777                STU  stu
                       888 VWX vwx
999 YZ yz

以下、上記を処理対象テキストファイル (awktest.txt) とする。

1フィールド目を切り出す
$ awk '{ print $1 }' awktest.txt
111
222
333
444
555
666
777
888
999
2フィールド目を切り出す
$ awk '{ print $2 }' awktest.txt
ABC
DEF
GHI
JKL
MNO
PQR
STU
VWX
YZ
3フィールド目を切り出す
$ awk '{ print $3 }' awktest.txt
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz

対象となる行を特定してフィールドを切り出す

awk '(NR == 行番号){ print $フィールド番号 }'

→ awk コマンドに処理対象とする行番号を指定する。

行番号を指定することで、処理対象となる範囲を限定することができる。また、行番号指定の際には == 以外にも< > <= >= != が使用可能である。

さらに || (OR条件) や && (AND条件) で複数の条件を指定することも可能である。ちなみに NR は行番号を表す awk コマンドの組み込み変数である。

1行目のみを処理対象とする
$ awk '(NR == 1){ print $1 }' awktest.txt
111
1行目および9行目を処理対象とする
$ awk '(NR == 1 || NR == 9){ print $1 }' awktest.txt
111
999
5行目より大かつ8行目以下、つまり6行目から8行目までの行を処理対象とする
$ awk '(NR > 5 && NR <= 8){ print $1 }' awktest.txt
666
777
888

空白で区切られたフィールドの順番を入れ替える

“hoge fuga foo bar” のように空白で区切られた各フィールドを、“bar hoge bar foo” のように順番を入れ替えて出力する、といった処理には awk コマンドを使用する。

awk '{ print $フィールド番号" "$フィールド番号" "..." "$フィールド番号 }'

→ awk コマンドで各フィールドの番号を出力したい順に指定する。

awk コマンドにおいて $x は x 番目のフィールドを意味する。つまりフィールドの順番を入れ替えるには、出力したい順番で $x を指定すればよい。

例えば3つのフィールドの順番を逆にしたい場合は、$3" "$2" "$1 と指定する。間にある " " はフィールド間のスペースを意味する。これを指定しない状態で実行すると、各フィールドが hogefugafoobar のように、各フィールドが結合した状態で出力されてしまう。

$ cat shuffle.txt
111 ABC abc
222 DEF def
333 GHI ghi
444 JKL jkl
555 MNO mno
666 PQR pqr
777 STU stu
888 VWX vwx

$ awk '{ print $3 $1 $2 }' shuffle.txt
abc111ABC
def222DEF
ghi333GHI
jkl444JKL
mno555MNO
pqr666PQR
stu777STU
vwx888VWX
#↑区切りのスペース " " を指定しなかったので、全て結合された状態で出力されている。

$ awk '{ print $3" "$1" "$2 }' shuffle.txt
abc 111 ABC
def 222 DEF
ghi 333 GHI
jkl 444 JKL
mno 555 MNO
pqr 666 PQR
stu 777 STU
vwx 888 VWX
#↑今度は $x の間に " " を指定したので、フィールド間がスペースで区切られている。

$ awk '{ print $3" "$1" "$1" "$2 }' shuffle.txt
abc 111 111 ABC
def 222 222 DEF
ghi 333 333 GHI
jkl 444 444 JKL
mno 555 555 MNO
pqr 666 666 PQR
stu 777 777 STU
vwx 888 888 VWX
#↑同じフィールドを2回出力することも可能。

ここまでは awk に直接処理対象ファイルを指定してきたが、実際にシェルスクリプトに組み込んで使用する場合は、以下のようにパイプでデータを引き渡して実行することの方が多いだろう。

$ head -n 3 shuffle.txt | awk '{ print $2 }'
ABC
DEF
GHI

このように処理対象データからあらかじめ不要な部分を取り除いた上で awk に引き渡すと、余計な処理を省けるので、awk スクリプトを簡素に書くことができる。

【補足】awk に関して

awk (オーク) は正確にはコマンドではなく、インタプリタでありシェルスクリプトとは異なるスクリプト言語である。 もともとはテキスト処理のために開発されたスクリプト言語なので、シェルスクリプトよりも高速にテキスト処理を行うことができる。

awk はシェルスクリプトに組み込むことが容易であるため、awk スクリプト単独で使用するよりもシェルスクリプト内でフィルタとして使用されることが多い。 シェルスクリプトで行数の多いテキストファイルを処理する場合は、一部に awk スクリプトを用いることで高速なプログラムを書くことが可能である。

テキストファイルの処理には Perl 等を使用した方が便利かもしれないが、awk ほとんどの環境で使用可能であり、 Perl 等が使用できない環境 (古い UNIX 環境など) では必須の技術であるため、覚えておいて損はないスクリプト言語である。