第3章 3-6 / テキスト処理

sedとawkでミニ集計

このページで叩くコマンドと到達点

前提:3-5が完了し、~/practiceの中に3-1〜3-5で作った練習ファイル(fruits.txt、copy.txt、memo.txt、vimtest.txt、nums.txt、dup.txt、shuffled.txt、user.csv、order.txt)が存在する状態から始めます。第3章の最後は、売上データ風のCSVファイルsales.txtを自分の手で作り、文字列を置換するsedと、列の集計ができるawkを使って、簡単な売上合計を出すところまで体験します。最後はrm~/practiceを空に戻し、この章をきれいに締めくくります。

このページではSET 1〜3、合計30行のコマンドを上から順に叩きます。手打ち推奨(コピーは確認用)です。

SET 1 ― 売上データを作り、sedで置換する

ubuntu@lightsail: ~/practice
  1. $cd ~/practice
  2. $ls
  3. copy.txt dup.txt fruits.txt memo.txt nums.txt order.txt shuffled.txt user.csv vimtest.txt
  4. $echo "コーヒー,300" > sales.txt
  5. $echo "紅茶,250" >> sales.txt
  6. $echo "コーヒー,300" >> sales.txt
  7. $cat sales.txt
  8. コーヒー,300
  9. 紅茶,250
  10. コーヒー,300
  11. $wc -l sales.txt
  12. 3 sales.txt
  13. $sed 's/コーヒー/カフェラテ/' sales.txt
  14. $cat sales.txt
  15. $sed 's/紅茶/ほうじ茶/' sales.txt
解説 ― SET 1 で何をしたか

1行目で練習用ディレクトリへ移動し、2行目のlsで、3-1〜3-5で作った練習ファイルがすべてそろっていることを確認しておきます。

35行目で、「商品名,金額」というカンマ区切りの売上データを3行、>>>を使って組み立てます(3-3で学んだ上書きと追記の使い分けを思い出しましょう)。6行目のcatで、出力例のとおり3行のCSV風データができていることを確認し、7行目のwc -lでも行数が3であることを数値で確認しておきます。

8行目のsed 's/コーヒー/カフェラテ/' sales.txtが、このページで新しく登場するsed(stream editorの略)です。s/置換前/置換後/という書き方は置換コマンド※1と呼ばれ、sはsubstitute(置き換える)の頭文字です。ここでは「コーヒー」という文字列を「カフェラテ」に置き換えた結果を画面に表示しています。

ただし9行目で改めてcatすると、sales.txtの中身はコーヒーのまま変わっていません。これは重要なポイントで、sedは初期状態では元のファイルを書き換えず、置換した結果を画面に表示するだけのコマンドだからです。10行目のように別の文字列「紅茶」を「ほうじ茶」に置換してみても、同じく画面に表示されるだけでファイルは変わりません。ファイル自体を書き換えるには、あとのSETで登場する-iオプションが必要になります。

POINT

sed 's/A/B/' ファイルは「Aという文字列をBに置き換えた結果」を画面に表示するだけで、元のファイルはそのままです。3-3の>と組み合わせればsed 's/A/B/' ファイル > 新ファイルのように別名で保存することもできます。

SET 2 ― -n '2p' で行を抜き出し、-i で書き換える

ubuntu@lightsail: ~/practice
  1. $sed -n '2p' sales.txt
  2. 紅茶,250
  3. $sed -n '1,2p' sales.txt
  4. $sed -n '3p' sales.txt
  5. コーヒー,300
  6. $sed -i 's/コーヒー/カフェラテ/' sales.txt
  7. $cat sales.txt
  8. カフェラテ,300
  9. 紅茶,250
  10. カフェラテ,300
  11. $wc -l sales.txt
  12. 3 sales.txt
  13. $sed -i 's/紅茶/ほうじ茶/' sales.txt
  14. $cat sales.txt
  15. カフェラテ,300
  16. ほうじ茶,250
  17. カフェラテ,300
  18. $sed -n '2p' sales.txt
  19. ほうじ茶,250
  20. $wc -l sales.txt
  21. 3 sales.txt
解説 ― SET 2 で何をしたか

1行目のsed -n '2p' sales.txtは、置換とは別の使い方です。-nは「普段は自動で行われる画面表示をいったん止める」指定で、そこに'2p'(2行目をprintする、の意味)を組み合わせることで、2行目だけを狙って表示できます。出力例のとおり「紅茶,250」だけが抜き出されています。2行目の'1,2p'のように範囲を指定すれば、1〜2行目をまとめて表示することもできます。3行目のように'3p'で最後の行だけを狙うこともでき、出力例のとおり「コーヒー,300」が抜き出されます。

4行目でついに-iオプション(in-placeの意味、その場で書き換える)を使います。sed -i 's/コーヒー/カフェラテ/' sales.txtを実行すると、SET 1とは違いファイルの中身そのものが書き換わります。5行目のcatで確認すると、出力例のとおり「コーヒー」だった2行がすべて「カフェラテ」に置き換わって保存されています。6行目のwc -lで、置換をしても行数自体は3のまま変わっていないことも確認しておきましょう。

7行目でも同じように-iを使い、「紅茶」を「ほうじ茶」に置き換えます。-iは便利な反面、元の内容を上書きして消してしまう強力なオプションでもあるため、実務では先に-iなしで置換結果を確認してから-iを付ける、という手順が安全です。8行目のcatで、出力例のとおり2列目の商品名だけが「ほうじ茶」に変わっていることを確認し、9行目のsed -n '2p'でも、2行目がたしかに「ほうじ茶,250」に置き換わっていることをあらためて確認します。最後に10行目のwc -lで、ここまで2回の-i置換を経ても行数は3のままであることを再確認しておきましょう。

ゆみちゃん
ゆみ

sed -iは便利だけど、ファイルを直接書き換えちゃう分ちょっと怖いコマンドでもあるよ。大事なファイルに使うときは、まず-iを付けずに画面で結果を確認してから、「これで大丈夫」って思ったら-iを付けて実行する、っていう2段階を意識するとミスが減るよ!

SET 3 ― awkで列を取り出し、合計する

ubuntu@lightsail: ~/practice
  1. $cat sales.txt
  2. カフェラテ,300
  3. ほうじ茶,250
  4. カフェラテ,300
  5. $awk -F, '{print $1}' sales.txt
  6. $awk -F, '{print $2}' sales.txt
  7. $awk -F, '{print NR, $1}' sales.txt
  8. $awk -F, '$2 >= 300 {print $1}' sales.txt
  9. $awk -F, '{sum += $2} END {print sum}' sales.txt
  10. 850
  11. $rm sales.txt fruits.txt copy.txt memo.txt vimtest.txt
  12. $rm nums.txt dup.txt shuffled.txt user.csv order.txt
  13. $ls
  14. $cd ~
解説 ― SET 3 で何をしたか

1行目でSET 2までの置換結果を確認します。2行目のawk -F, '{print $1}' sales.txtで、いよいよawkを使います。awkは行を列に分割して扱うのが得意なコマンドで、-F,で区切り文字をカンマに指定し(3-5のcut -dと似ています)、{print $1}で1列目だけを表示します。$1は1列目、$2は2列目を指す特別な書き方で、3行目のように$2にすれば金額の列だけが取り出せます。

4行目のawk -F, '{print NR, $1}' sales.txtに登場するNRは「今何行目を読んでいるか」を表すawkの組み込み変数で、行番号付きの一覧が表示されます。5行目のawk -F, '$2 >= 300 {print $1}' sales.txtは、{ }の前に条件を書くと「条件に合う行だけを処理する」というawkのパターン指定です。2列目が300以上の行、つまりカフェラテの2行だけが表示されます。

6行目のawk -F, '{sum += $2} END {print sum}' sales.txtが、このページのゴールである合計集計です。{sum += $2}の部分は「読み込んだ行ごとに、2列目の金額をsumという変数に足し込んでいく」処理で、ファイルの全行に対して繰り返されます。すべての行を読み終わったあとに実行されるEND {print sum}で、最終的にたまった合計値を表示します。出力例のとおり、300+250+300の合計である850が表示されれば成功です。

最後に78行目のrmで、第3章を通じて~/practiceに作ってきた練習ファイルをすべて削除します。9行目のlsで何も表示されなければ、~/practiceが空の状態に戻り、第2章末と同じサーバー状態(yumi・devteamは存続、~/practiceは空)に戻ったことになります。10行目のcd ~でホームディレクトリへ戻り、これで第3章は完了です。

POINT

awk -F区切り文字 '{print $列番号}'は列の抜き出し、{変数 += $列番号} END {print 変数}'は列の合計です。この2つの型を覚えておくだけで、CSV風のデータからかなりの集計ができるようになります。

ゆみちゃん
ゆみ

第3章、お疲れさま! nano・vimでファイルを書けるようになって、リダイレクトとパイプで水道管をつなげるようになって、grep・sort・uniq・cut・tr・sed・awkでデータを検索したり加工したり集計したりできるようになったよ。これだけ武器が揃えば、もうテキストファイルを前にして固まることはないはず。次の第4章では、サーバーの中で今何が動いているかを覗いていこう!

まとめ

3-6では、sedによる文字列の置換・行の抜き出し・ファイルの直接書き換えと、awkによる列の抜き出し・合計集計を体験し、第3章の練習ファイルをすべて削除して章を締めくくりました。このページで叩けるようになったコマンドを一覧にまとめます。

コマンド何をするか覚え方
sed 's/A/B/' ファイルAを含む部分をBに置換した結果を表示する(元は変わらない)substitute(置き換える)
sed -n '2p' ファイル指定した行番号だけを表示する-n(自動表示OFF)+p(print)
sed -i 's/A/B/' ファイルファイルの中身そのものを置換して保存する-i = in-place(その場で書き換え)
awk -F, '{print $1}'カンマ区切りで1列目を取り出す-Fで区切り文字、$列番号で列指定
awk -F, '{print NR, $1}'行番号付きで1列目を表示するNR = Number of Record(今何行目か)
awk -F, '$2 >= 300 {print $1}'条件に合う行だけを処理する条件 {アクション} の順に書く
awk -F, '{sum+=$2} END{print sum}'2列目を全行分合計して表示するEND = 全行読み終わったあとの処理
rm ファイル名...指定したファイルを削除する第1章で学習済み。章末の片付けに使用

次のページ「4-1. プロセスを見る」からは第4章に入り、ps auxtopを使ってサーバーの中で動いているプロセスを観察していきます。

脚注 ─ 用語解説
  1. 置換コマンドsedで使うs/置換前/置換後/という書き方の名称。sはsubstitute(置き換える)の頭文字。