sedとawk

記事の内容


  • sedを使う
    • 基本的な使い方
    • 正規表現を使う
    • コマンドファイルを使う
    • フラグを使う
  • awkを使う
    • 基本的な使い方
    • レコードとフィールド
    • 正規表現を使う
    • コマンドファイルを使う
    • 算術演算・変数・関数
    • forとifの利用
    • 連想配列の利用

sedとawkについて簡単にまとめました. 動作環境は, MacBookPro+fishシェルです.

sed(streme editor, "セド"とか"セドゥ"と読む)とは, テキスト編集に特化したエディタのことです. sedを使うと, 大量のテキストデータであっても高速に編集することができます. awk(Aho,Weinberger,Kernighanに由来, "オーク"と読む)とは, データ操作に特化したプログラミング言語です. awkを使うと, データに対して何らかの操作(演算など)を行うことができます. sedとawkは次のような共通点を持ちます.

  • データを1行ずつ読み込む.
  • 編集作業は1行に対して全て行ってから, 次の行に移る. (ストリーム指向)
  • 編集作業は全ての行に対して行うが, アドレスにより編集を適用する行を指定できる.
  • 正規表現を利用できる.
  • 編集内容はコマンドファイルで指定できる.
  • 編集結果は標準出力に書き出す.
  • 編集しても, 入力ファイルは変化なし. 結果が出力されるのみ.

sedを使う

まずはsedからです. 次のファイルを使います.

【ファイル作成: testfile1.txt】
Beethoven	1770-1827	Symphony No.9	Germany
  Mozart	1756-1791	Requiem	Germany
  Brahms	1833-1897	A German Requiem	Germany
  Bach	1685-1750	St Matthew Passion	Germany

  Wagner	1813-1883	The Ring of the Nibelung	Germany
  R.Strauss	1864-1949	Also sprach Zarathustra	Austria
  J.StraussII	1825-1899	The Blue Danube		Austria
  Bruckner	1824-1896	Symphony No.8		Germany
  Tchaikovsky	1840-1893	Violin Concerto		Russian
  Dvořák	1841-1904	Cello Concerto 			Czech

基本的な使い方

sedの基本的な使い方は以下の通りです. は空白を表します.

sedの基本】
sed␣アドレス␣コマンド␣フラグ␣ファイル名

アドレスには, 編集したい行の番号を指定します. コマンドには, 編集内容を指定します. 例えば, 置換, 削除, 追加, 表示などが適用でき, アルファベット1文字で指定できます. フラグには, 1つの行内の編集内容の適用範囲を指定します. ファイル名には, 編集したいファイルを指定します. 最低限, "sed␣コマンド"が必要です. 例を挙げます. testfile1.txtを編集します.

コマンドsは置換コマンドです. ある特定の文字列を別の文字列で置換できます. s/置換前/置換後/のように記述します. 次のコマンドにより, ファイル中の文字列"Strauss"を全て"Strauß"で置換します.

【置換コマンドの利用1】
~> sed 's/Strauss/Strauß/' testfile1.txt
【置換コマンドの利用1; 実行結果(変化した部分を赤く表示)】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Paßion	Germany

Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauß	1864-1949	Also sprach Zarathustra	Austria
J.StraußII	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Rußian
Dvořák	1841-1904	Cello Concerto 			Czech

特に指定がなければ, 置換は全ての行に対して適用されます. 特定の行にのみ置換を適用したいならば, 行番号または文字パターンを指定します. まず, 行番号を指定する方法です. 1行目から9行目まで, 置換を適用します.

【置換コマンドの利用2】
~> sed '1,9 s/Strauss/Strauß/' testfile1.txt
【置換コマンドの利用2; 実行結果(変化した部分を赤く表示)】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Paßion	Germany

Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauß	1864-1949	Also sprach Zarathustra	Austria
J.Strauß	II	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Russian
Dvořák	1841-1904	Cello Concerto 			Czech

1行だけを指定することもできます. 下のコマンドにより, 置換は7行目にだけ適用され, 8行目には適用されません.

【置換コマンドの利用3】
~> sed '7 s/Strauss/Strauß/' testfile1.txt 
【置換コマンドの利用3; 実行結果(変化した部分を赤く表示)】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Passion	Germany

Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauß		1864-1949	Also sprach Zarathustra	Austria
J.StraussII	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Russian
Dvořák	1841-1904	Cello Concerto 			Czech

次に, 文字パターンを指定する方法です. 次のように入力すると, 文字列"Austria"を含む行にのみ, 置換が適用されます.

【置換コマンドの利用4】
~> sed '/Austria/ s/Strauss/Strauß/' testfile1.txt 
【置換コマンドの利用4; 実行結果(変化した部分を赤く表示)】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Passion	Germany

Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauß		1864-1949	Also sprach Zarathustra	Austria
J.Strauß	II	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Russian
Dvořák	1841-1904	Cello Concerto 			Czech

ここまでは, 置換コマンドのみを使ってきました. 他のコマンドも使ってみます.

コマンドdは削除です. 行をまるごと削除します. 以下のコマンドにより, 5行目の空行を削除します.

【削除コマンドの利用】
~> sed '5 d' testfile1.txt
【削除コマンドの利用; 実行結果】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Passion	Germany
Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauss	1864-1949	Also sprach Zarathustra	Austria
J.StraussII	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Russian
Dvořák	1841-1904	Cello Concerto 			Czech

他に追加コマンドもあります. 後ほど紹介します.

なお, 一度に複数の処理も可能です. eオプションを使います. 以下のコマンドにより, 次の3つの処理が実行されます.

  • 文字列"Strauss"を"Strauß"に置換する.
  • 文字列"-"を"~"に置換する.
  • 空行を削除する.

7行目と8行目では, 上2つの置換が実行されます. これらの置換は, 1行読み込むごとに, 2つの置換を両方行うのでしょうか. それとも, 1行読み込んで置換するという作業を2周分繰り返すのでしょうか. sedは1行読み込むごとに, 2つの置換を両方行います.

【複数コマンドの実行】
~> sed -e 's/Strauss/Strauß/' -e 's/-/~/' -e '5d' testfile1.txt
【複数コマンドの実行; 実行結果】
Beethoven	1770~1827	Symphony No.9	Germany
Mozart	1756~1791	Requiem	Germany
Brahms	1833~1897	A German Requiem	Germany
Bach	1685~1750	St Matthew Passion	Germany
Wagner	1813~1883	The Ring of the Nibelung	Germany
R.Strauß	1864~1949	Also sprach Zarathustra	Austria
J.StraußII	1825~1899	The Blue Danube		Austria
Bruckner	1824~1896	Symphony No.8		Germany
Tchaikovsky	1840~1893	Violin Concerto		Russian
Dvořák	1841~1904	Cello Concerto 			Czech

正規表現を使う

先ほど挙げた例で, 空行の削除にわざわざ行番号を指定していました. これではファイルが大きくなったとき大変です. そもそも, 空行の行番号の把握にも一苦労です. そこで正規表現を用います. 次のように書くと空行を削除できます.

【正規表現を利用した空行の削除】
~> sed '/^$/d' testfile1.txt
【正規表現を利用した空行の削除; 実行結果】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Passion	Germany
Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauss	1864-1949	Also sprach Zarathustra	Austria
J.StraussII	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Russian
Dvořák	1841-1904	Cello Concerto 			Czech

行の指定だけではなく, 他にも使えます. 以下のコマンドにより, 文字列"ss"が"ß"で置換されます.

【正規表現を利用した置換】
~> sed 's/s\{2\}/ß/' testfile1.txt
【正規表現を利用した置換; 実行結果(変化した部分を赤く表示)】
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Paßion	Germany

Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauß	1864-1949	Also sprach Zarathustra	Austria
J.StraußII	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Rußian
Dvořák	1841-1904	Cello Concerto 			Czech

コマンドファイルを使う

複数の処理や, 汎用的なコマンドをファイルに保存しておくと便利です. ここではコマンドファイルから編集を行います. まず, 次のファイルを作成します.

【ファイル作成: modify】
s/Strauss/Strauß/
s/-/~/
/^$/d 

コマンドファイルを利用するには, fオプションを使います. 以下のコマンドにより, 次の3つの処理が実行されます.

  • 文字列"Strauss"を"Strauß"に置換する.
  • 文字列"-"を"~"に置換する.
  • 空行を削除する.

次のように実行します.

【コマンドファイルの実行】
~> sed -f modify testfile1.txt
【コマンドファイルの実行; 実行結果】
Beethoven	1770~1827	Symphony No.9	Germany
Mozart	1756~1791	Requiem	Germany
Brahms	1833~1897	A German Requiem	Germany
Bach	1685~1750	St Matthew Passion	Germany
Wagner	1813~1883	The Ring of the Nibelung	Germany
R.Strauß	1864~1949	Also sprach Zarathustra	Austria
J.StraußII	1825~1899	The Blue Danube		Austria
Bruckner	1824~1896	Symphony No.8		Germany
Tchaikovsky	1840~1893	Violin Concerto		Russian
Dvořák	1841~1904	Cello Concerto 			Czech

他の例も試してみます. 次は追加・変更コマンドを利用します. 追加コマンドにはi(insert)とa(append)の2種類があり, 変更コマンドはcです. 次のようなファイルを作ります.

【ファイル作成: add-text】
1i\
Monteverdi	1567-1643	Vespro della Beata Vergine	Italy
11a\
Shostakovich	1906-1975	Symphony No.5	Russian
/^$/c\
Paganini	1782-1840	Violin Concerto No.2	Italy\

以下のコマンドにより, 次の3つの処理が実行されます.

  • 1行目の上に, 指定された行"Monteverdi..."が挿入される.
  • 11行目の下に, 指定された行"Shostakovich..."が追加される.
  • 空行が指定された行"Paganini..."に変更される.
【コマンドファイルの実行】
~> sed -f add-text testfile1.txt
【コマンドファイルの実行; 実行結果】
Monteverdi	1567-1643	Vespro della Beata Vergine	Italy
Beethoven	1770-1827	Symphony No.9	Germany
Mozart	1756-1791	Requiem	Germany
Brahms	1833-1897	A German Requiem	Germany
Bach	1685-1750	St Matthew Passion	Germany
Paganini	1782-1840	Violin Concerto No.2	Italy
Wagner	1813-1883	The Ring of the Nibelung	Germany
R.Strauss	1864-1949	Also sprach Zarathustra	Austria
J.StraussII	1825-1899	The Blue Danube		Austria
Bruckner	1824-1896	Symphony No.8		Germany
Tchaikovsky	1840-1893	Violin Concerto		Russian
Dvořák	1841-1904	Cello Concerto 			Czech
Shostakovich	1906-1975	Symphony No.5	Russian

フラグを使う

一つの行に, 置換したい文字列パターンが2つ以上ある場合, 次の2つの状況が考えられます.

  • 複数のパターンのうち1つだけを置換したい.
  • 複数のパターンの全てを置換したい.

デフォルトでは, 1つ目のパターンだけが置換されます. n番目を置換したい場合, パターンの後にnと記述します. これをフラグと言います. 例えば, 次のように書きます.

【フラグの指定1】
~> echo "スモモもモモもモモのうち"|sed 's/モモ/桃/3'
【フラグの指定1; 実行結果】
スモモもモモも桃のうち

デフォルトでは, 1番目のパターンだけが置換されます.

【フラグの指定2】
~> echo "スモモもモモもモモのうち"|sed 's/モモ/桃/'
【フラグの指定2; 実行結果】
ス桃もモモもモモのうち

全てのパターンを置換したければ, グローバルフラグgを指定します.

【フラグの指定3】
~> echo "スモモもモモもモモのうち"|sed 's/モモ/桃/g'
【フラグの指定3; 実行結果】
ス桃も桃も桃のうち

フラグは他にも何種類か存在します.

awkを使う

次に, awkを扱います. 次のファイルを使います.

【ファイル作成: testfile2.txt】
name	quantity	price
caffeine free coffee	1	1000
mixed nuts    1		500
chocolate     3		100
egg	      1		150
broccoli      2		100
bread	      3		200

基本的な使い方

awkの基本的な使い方は以下の通りです. は空白を表します.

awkの基本】
awk␣パターン␣アクション␣ファイル名

パターンには, 行を特定するためのパターンを記述します. アクションには, 指定した行に対する操作内容を記述します. ファイル名には, 操作したいファイル名を記述します. パターンはデフォルトで全ての行, アクションはデフォルトでprintです. 例を挙げます.

【特定の行の処理を指定】
~> awk '/egg/{print}' testfile2.txt
【特定の行の処理を指定; 実行結果】
egg  1 150

文字列"egg"を含む行を表示します. {print}を省略しても同じ結果が返ります.

レコードとフィールド

awkは次のような形式のファイルを扱います. 1行分を示します.

awkが扱うファイル1行分】
フィールド1  フィールド2 ...   フィールドn

この1行分をレコードと言います. また, レコードの成分をフィールドと言います. awkでは, 組み込み変数NRでレコードの番号を管理します. 以下のように書くと, レコードの番号にアクセスできます.

NRを使う】
~> awk '{print NR}' testfile2.txt
NRを使う; 実行結果】
1
2
3
4
5
6
7

行が更新されるたびに, NRは1つずつ繰り上がります. 現在処理しているレコードが何番目か把握するのに役立ちます.

各フィールドは, $nのような変数で表します. これは番号ではなく, ファイルに格納されている値を保持します. フィールドの個数はNFで取得できます.

【フィールド演算子$nを使う】
~> awk '{print $1}' testfile2.txt
【フィールド演算子$nを使う; 実行結果】
name
caffeine-free-coffee
mixed-nuts
chocolate
egg
broccoli
bread

これらの変数を用いてデータを操作する例を挙げます.

$nNRを使う】
~> awk 'NR==2{print $1,$3}' testfile2.txt
$nNRを使う; 実行結果】
caffeine-free-coffee 1000

正規表現を使う

sedと同様, awkは正規表現を利用できます. 次のコードにより, "c"で始まるレコードを参照できます.

【正規表現1】
~> awk '/^c/{print $1,$3}' testfile2.txt
【正規表現1; 実行結果】
caffeine-free-coffee 1000
chocolate 100

次のコードにより, "b"または"c"で始まるレコードを参照できます.

【正規表現2】
~> awk '/^c|^b/{print $1,$3}' testfile2.txt
【正規表現2; 実行結果】
caffeine-free-coffee 1000
chocolate 100
broccoli 100
bread 200

コマンドファイルを使う

awkはsedと同様, コマンドファイルに保存できます. 一般に, コマンドファイルは次のような構造をしています.

【コマンドファイルの構造】
BEGIN{
  #データを読み込む前に一度だけ実行したいコード
}
{
  #メインの入力ループ
}
END{
  #全てのデータを読み込んだ後で一度だけ実行したいコード
}

BEGINENDは省略可能です. sedと同様, データは1行ずつ読み込まれ, データ操作は1行ごとに全て実行されます.

次のようなファイルを作成します. BEGINENDは省略し, メイン部分だけを書きます.

【ファイル作成: print-price】
{
                print $2,$3
}

fオプションを指定して実行します. 全てのレコードに対して, 2つのフィールドquantityとpriceの値が返されます.

【コマンドファイルを使う】
~> awk -f print-price testfile2.txt
【コマンドファイルを使う; 実行結果】
quantity price
1 1000
1 500
3 100
1 150
2 100
3 200

算術演算・変数・関数

awkはプログラミング言語なので, 変数に代入したり, 計算したりできます. 次のようなファイルを作成します.

【ファイル作成: total】
NR>1{
	quantity=$2
	price=$3
	payment=quantity*price
	total+=payment
	print payment,total
}

print以外は, 変数への代入です. 各レコードに対してpaymentが計算され, 共通の変数totalに加算されます. 結果として表示されるのは, 各商品の小計とそのときの合計金額です. なお, 1行目はフィールド名であり, 演算の対象外です.

【変数・演算】
~> awk -f total
【変数・演算; 実行結果】
1000 1000
500 1500
300 1800
150 1950
200 2150
600 2750

awkのはsinやlog, expなどの関数も用意されています. また, 関数を自分で定義することもできます. 次のようなファイルを作成します.

【ファイル作成: total-funciton】
function comp_total(){
	 quantity=$2
	 price=$3
	 payment=quantity*price
	 return payment
}
{
	p=comp_total()
	total+=p
}
END{
	print total
}

関数を定義し, 小計の計算は関数内で行います. また, ENDを用いて, 最終結果のみを表示します.

【変数・演算】
~> sed '1d' testfile2.txt | awk -f total-function
【変数・演算; 実行結果】
2750

forとifの利用

awkではfor文やif文が利用できます. まずはforを使います. forは次のように書きます. Nは反復回数です. i++で, 繰り返し変数iを1ずつ大きくします.

forの基本的な形】
for(i=1; i<=N; i++)
    #ループ処理

例として, 次のようなファイルを作成します.

【ファイル作成: for-test】
{
	for(i=1; i<=NF; i++)
   		  print i,"=>",$(i)
}

各レコードにおいて, フィールドの番号をforの繰り返し変数として, フィールド値を表示させます.

【forの利用】
~> awk -f for-test testfile2.txt
【forの利用; 実行結果】
1 => name
2 => quantity
3 => price
1 => caffeine-free-coffee
2 => 1
3 => 1000
1 => mixed-nuts
2 => 1
3 => 500
1 => chocolate
2 => 3
3 => 100
1 => egg
2 => 1
3 => 150
1 => broccoli
2 => 2
3 => 100
1 => bread
2 => 3
3 => 200

次にifを利用します. ifの基本的な形は以下の通りです.

forの基本的な形】
if(#条件1)
  #条件1が成り立つときの処理
else if(条件2)
  #条件1が成り立たない, かつ条件2が成り立つときの処理
else
  #いずれの条件も成り立たないときの処理

合計金額を表示させるプログラムを書きます.

【ファイル作成: if-test】
function comp_total(){
	 quantity=$2
	 price=$3
	 payment=quantity*price
	 return payment
}
{
	if(NR>1)
		p=comp_total()
		total+=p
}
END{
	print "total: ",total
}

先ほどは, sedを使って計算の対象外である1番目のレコードは除外していました. if文を用いても除外できます.

【ifの利用】
~> awk -f if-test testfile2.txt
【ifの利用; 実行結果】
total: 2750

連想配列の利用

awkで扱う配列は連想配列です. インデックスとして文字列や数値が使えます. 以下のプログラムにおいて, インデックスは数値です. しかし普通のプログラミング言語の配列とは異なり, インデックス番号と順序は無関係です. インデックスと要素をペアで記憶します. 配列の要素の並びはインデックス順とは限りません.

【ファイル作成: array-test】
function sum(array){
	 s=0
	 for(i=1; i<=length(array); i++)
	    s+=array[i]
	 return s
}
{
	if(NR>1)
		quantity=$2
		price=$3
		tmp=$2*$3
		array[NR]=tmp
}
END{
	print "total: ",sum(array)
}

配列に各商品の代金を格納します. 各要素を足し合わせることで, 合計金額を算出します. length関数で, 配列の長さが取得できます.

【連想配列の利用】
~> awk -f array-test testfile2.txt
【連想配列の利用; 実行結果】
total: 2750
参考文献
      [1] ジョン・マスター, ピーター・バーンズ, UNIXコマンド活用ハンドブック, パーソナルメディア, 1992
      [2]Dale Dougherty, Aenold Robbins, sed&awk プログラミング 改訂版, オライリージャパン, 2017