Linux
Linuxコマンド

Linuxコマンドのお勉強 (3)grep, sed, awk

Linux commands (Normal)

正規表現 grep [オプション] <検索パターン> <ファイル名>

grep

(ex)

$ ps x | grep bash
53224 s000  S      0:00.47 -bash
60350 s000  S+     0:00.00 grep bash
54487 s001  S+     0:00.16 -bash

grep -n: 行数を表示

(ex)

$ ps x | grep -n bash
223:53224 s000  S      0:00.47 -bash
225:60353 s000  R+     0:00.00 grep -n bash
227:54487 s001  S+     0:00.16 -bash

grep -i: 大文字・小文字を区別しない

(ex. 1) -iをつけないとヒットしない

$ ps x | grep BASH
60363 s000  S+     0:00.00 grep BASH

(ex. 2) -iをつけるとヒットする

$ ps x | grep -i BASH
53224 s000  S      0:00.48 -bash
60371 s000  S+     0:00.00 grep -i BASH
54487 s001  S+     0:00.16 -bash

文字にマッチ

  • .: 任意の一文字にマッチ
  • [ ]: カッコ内のいずれかの1文字
  • [^ ]: カッコ内に含まれない、いずれかの1文字
  • \: 直後のメタ文字の意味を打ち消す

(ex) grep 'b..sh'

$ ps x | grep 'b..h'
53224 s000  S      0:00.52 -bash
60521 s000  R+     0:00.00 grep b..h
54487 s001  S+     0:00.16 -bash

位置にマッチ

  • ^: 行頭
  • $: 行末

(ex) grep '^bash$'

$ ps x | grep 'bash$'
53224 s000  S      0:00.53 -bash
54487 s001  S+     0:00.16 -bash

繰り返しマッチ

以下は正規表現 (拡張正規表現)示す.拡張表現を用いる場合はgrep -Eを用いる.
* * (*): 0回以上の繰り返し
* なし (+): 1回以上の繰り返し
* なし (?): 0または1回の繰り返し
* \{m, n\} ({m, n}): m以上n回以下の繰り返し
* \{m\} ({m}): ちょうどm回の繰り返し
* \{m,\} ({m,}): m以上の繰り返し

(ex)

$ echo 'Hellooooo' | grep -E 'Hello{3,6}'
Hellooooo
$ echo 'Hello' | grep -E 'Hello{3,6}' # 3 <= n <= 6なので今回はoが1回しかなく出力されない

その他

  • \( \) (( )): グループ化
  • なし (|): ORで繋ぐ

(ex)

$ echo 'Swift \
Ruby\
Python' | grep -E '(Swift|Ruby)'
Swift \
Ruby\

Stream Editor sed [オプション] <スクリプト> <対象ファイル>

以下のファイルを用意

sample.txt
Swift
SwiftyJSON
Ruby
rbenv
Rails
Parallels
Javascript
Sails
Python
pyenv
Numpy

d: 行を削除

(ex .1) 1行目を削除

$ sed 1d sample.txt
SwiftyJSON
Ruby
rbenv
Rails
Parallels
Javascript
Sails
Python
pyenv
Numpy

(ex. 2) sed n,md <ファイル名>: n-m行目を削除

$ sed 2,8d sample.txt
Swift
Python
pyenv
Numpy

(ex. 3) sed 'n,$d' <ファイル名>: n-最終行まで削除

$ sed '2,$d' sample.txt
Swift

(ex. 4) sed d <ファイル名>: 全文削除

(ex. 5) 正規表現との組み合わせ: Swから始まるものを削除

$ sed /^Sw/d sample.txt
Ruby
rbenv
Rails
Parallels
Javascript
Sails
Python
pyenv
Numpy

p: 行の表示

(ex) 普通にpを指定してもパターンスペースの自動表示がされてしまう

$ sed 2p sample.txt
Swift #パターンスペースにコピー&出力
SwiftyJSON #2pコマンド出力
SwiftyJSON #パターンスペースコピー&出力.以下同様
Ruby
rbenv
Rails
Parallels
Javascript
Sails
Python
pyenv
Numpy

これを防ぐには-nオプションで特定の行だけ表示するようにする

$ sed -n 2p sample.txt
SwiftyJSON

s: 行の置換.s/<置換前文字列>/<置換後文字列>/<フラグ>.ちなみに区切り文字は/でなくても!%で良い.

(ex .1) SwiftyJSONRealmに置換

$ sed 's/SwiftyJSON/Realm/' sample.txt
Swift
Realm
Ruby
rbenv
Rails
Parallels
Javascript
Sails
Python
pyenv
Numpy

実はsコマンドでは行頭から探し,各行で最初に見つかったものを変えて終了するという処理が行われる(以下参照).

$ sed 's/a/Z/' sample.txt
Swift
SwiftyJSON
Ruby
rbenv
RZils
PZrallels #2個目のaからは変わっていない(以下も同じ)
JZvascript
SZils
Python
pyenv
Numpy

(ex .2) sed s/<文字>/<文字>/g: gフラグで見つかった文字を全て置換

$ sed 's/a/(^_^)/g' sample.txt
Swift
SwiftyJSON
Ruby
rbenv
R(^_^)ils
P(^_^)r(^_^)llels
J(^_^)v(^_^)script
S(^_^)ils
Python
pyenv
Numpy

正規表現も使用可能

$ sed 's/^Sw/(^_^)/g' sample.txt
(^_^)ift
(^_^)iftyJSON
Ruby
rbenv
Rails
Parallels
Javascript
Sails
Python
pyenv
Numpy

"env"を削除

$ sed 's/env//g' sample.txt
Swift
SwiftyJSON
Ruby
rb
Rails
Parallels
Javascript
Sails
Python
py
Numpy

(ex. 3) sed s/xxx/yyy/gp 置換の発生した行のみ表示

$ sed -n 's/env//gp' sample.txt
rb
py

(ex. 4) sed -r s/...: 拡張正規表現 (macOSだと-E)

(ex. 5) \( \)でグループ化 -> \1で参照.もしくは拡張なら( ) -> \1

$ sed -E 's/(ails$)/(^o^)\1(^o^)/' sample.txt
Swift
SwiftyJSON
Ruby
rbenv
R(^o^)ails(^o^) #該当グループ全体が置換されている
Parallels
Javascript
S(^o^)ails(^o^)
Python
pyenv
Numpy

(ex. 6) アドレス指定

$ sed -n '3,8s/a/(^o^)/gp' sample.txt
R(^o^)ils
P(^o^)r(^o^)llels
J(^o^)v(^o^)script
S(^o^)ils

awk 'パターン {アクション}' <ファイル名>

"パターン": アクションを実行するかどうかの条件を記述 (パターンを省略すると、全レコードに対しアクションが実行される).
"アクション": テキスト処理を記述."パターン"にマッチするとアクションが実行される.

(ex. 1) 第1, 4フィールドのみ表示

$ ps -x | awk '{print $1, $4}'
PID CMD
275 /usr/sbin/cfprefsd
280 /usr/libexec/UserEventAgent
282 /usr/sbin/distnoted
284 /System/Library/Frameworks/CoreTelephony.framework/Support/CommCenter
286 /usr/libexec/trustd
287 /usr/libexec/lsd
...(省略)

アクション内のフィールドをカンマでなくスペースで区切ると,出力がスペース無しになる

$ ps -x | awk '{print $1 $4}'
PIDCMD
275/usr/sbin/cfprefsd
280/usr/libexec/UserEventAgent
282/usr/sbin/distnoted
284/System/Library/Frameworks/CoreTelephony.framework/Support/CommCenter
...(省略)

(ex .2) 第4フィールドに"bash"を含む場合のみ,行番号付きで表示

$ ps -x | awk '$4 ~ /bash$/ {print NR,$1,$4}'
211 53224 -bash
213 54487 -bash

*$0は全フィールドを指す.また一番最後のフィールドは$NFでも表せる

$ ps -x | awk '$4 ~ /bash$/ {print NR,$NF-1,$NF}'
211 -1 -bash
213 -1 -bash

(ex. 3) アクションの省略 = {print $0} = {print}

(ex. 4) awk -F<区切る文字>: 区切り文字の指定

(ex. 5) 応用例
以下のようなファイルを用意する.Yの平均値を算出する.

sampleData.txt
X    Y
0    4.91112E-17
0.5    2.97347E-15
1    5.4554E-17
1.5    9.08954E-17
2    3.19285E-17
2.25    1.35987E-17
2.5    2.33288E-17
2.5094    2.40615E-17
2.5187    2.48654E-17
2.5375    2.6695E-17
2.5563    2.88471E-17
2.575    3.13759E-17
2.5938    3.43611E-17
2.6125    3.79115E-17
...(省略)
9.9766    2.04148E-10
9.9844    2.03353E-10
9.9922    2.02976E-10
10    2.02976E-10

まずはじめにsampleData.txtをsampleData.csvへ

$ sed 1d sampleData.txt | awk '{print $1,$2}' | sed 's/ /,/g' >> sampleData.csv
$ cat sampleData.csv | head
0,4.91112E-17
0.5,2.97347E-15
1,5.4554E-17
1.5,9.08954E-17
2,3.19285E-17
2.25,1.35987E-17
2.5,2.33288E-17
2.5094,2.40615E-17
2.5187,2.48654E-17
2.5375,2.6695E-17

Yの総和を表示してみる

$ awk -F, '{sum += $NF} END{print "Sum(Y):"sum}' sampleData.csv #ENDは全入力ファイルを処理し終えた最後に実行されることを意味する
Sum(Y):7686.46

Yの平均値を表示してみる

$ awk -F, '{sum += $NF} END{print "Sum(Y):"sum,"Average(Y):"sum/NR}' sampleData.csv #NRはこれまで読み込んだ入力レコード数が代入されている組み込み変数
Sum(Y):7686.46 Average(Y):3.83748

本awkスクリプトを保存し(' '内の部分),実行してみる.

averageY.awk
{sum += $NF} END{print "Sum(Y):"sum,"Average(Y):"sum/NR}

実行

$ awk -F, -f averageY.awk sampleData.csv
Sum(Y):7686.46 Average(Y):3.83748