View on GitHub

Today I Learned

Software Engineering Blog

第1章 Linux環境

1.2.c 1+1の計算

bcは計算を処理するコマンド。

$ echo '1+1' | bc
2

1.3.a sedによる置換の練習

最初に発見した対象を置換

$ echo クロロエチルエチルエーテル | sed 's/エチル/メチル/'
クロロメチルエチルエーテル

すべて置換したい場合は後ろにgをつける

$ echo クロロメチルメチルエーテル | sed 's/メチル/エチル/g'
クロロエチルエチルエーテル

&で検索対象の文字列を再利用する

$ echo クロロエチルエーテル | sed 's/エチル/エチルエチル/g'
クロロエチルエチルエーテル
$ echo クロロエチルエーテル | sed 's/エチル/&&/g'
クロロエチルエチルエーテル

検索対象の文字列をかっこで囲むと順番に番号が与えられ、置換後の文字列のところで\1, \2として検索対象を再利用できる。後方参照と呼ばれる。

$ echo クロロメチルエチルエーテル | sed -E 's/(メチル)(エチル)/\2\1/'
クロロエチルメチルエーテル

1.3.b grepによる検索の練習

先頭が1で始まり、その後0が0個以上最後まで続く数を抽出

$ seq 100 | grep "^10*$" | xargs
1 10 100

最後が0でも2でも4でも6でも8でもない数を抽出

$ seq 100 | grep "[^02468]$" | xargs
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99

1桁と2桁目が同じ数を抽出

$ seq 100 | grep -E "^(.)\1$"| xargs
11 22 33 44 55 66 77 88 99

1.3.c grepによる検索&切り出しの練習

-oはマッチした部分のみを出力する

$ echo 中村 山田 田代 上田 | grep -o "[^ ]"田
山田
上田

1.3.d awkによる検索と計算の練習

awk '/正規表現/'grep '正規表現'と同じ意味になる

$ seq 5 | awk '/[24]/'
2
4

読み込んだ値は$1に入る

$ seq 5 | awk '$1%2==0'
2
4

awk '条件{処理}条件{処理}'のように2つ以上の条件と処理を書くことができる。

以下の2つ目のルールは$1%2==1と書いてもいいが、C言語と同様、非ゼロは真なので省略。

$ seq 5 | awk '$1%2==0{print $1, "Even"}$1%2{print $1,"Odd"}'
1 Odd
2 Even
3 Odd
4 Even
5 Odd

BEGINはawkが1行目の処理を始める前、ENDはawkが最終行の処理を終えた後、の状況にマッチする。

awk 'BEGIN{事前処理}条件{処理}条件{処理}END{事後処理}'

$ seq 5 | awk 'BEGIN{a=0}$1%2==0{print $1, "Even"}$1%2{print $1,"Odd"}{a+=$1}END{print "Sum", a}'
1 Odd
2 Even
3 Odd
4 Even
5 Odd
Sum 15

1.3.e sortとuniqによる集計

奇数と偶数の数を出力する例。

$ seq 5 | awk '$1%2==0{print "Even"}$1%2{print "Odd"}' | sort | uniq -c | sort -nr | awk '{print $2, $1}'
Odd 3
Even 2

1.3.f xargsによる一括処理

dir_1, dir_2, dir_3, dir_4という名前のディレクトリを作成する。

-Iオプションの後ろに指定した文字列(以下では@)に受け取った文字列が入る。

$ seq 4 | xargs -I@ mkdir dir_@

1.3.g bashによるメタプログラミング

パイプから受け取ったコマンドをbashで実行できる。

以下の例は、odd_1, odd_3, even_2, even_4のディレクトリを作るコマンド

$ seq 4 | awk '$1%2==0{print "mkdir even_"$1}$1%2{print "mkdir odd_"$1}'
mkdir odd_1
mkdir even_2
mkdir odd_3
mkdir even_4

$ seq 4 | awk '$1%2==0{print "mkdir even_"$1}$1%2{print "mkdir odd_"$1}' | bash

$ ls
even_2		even_4		odd_1		odd_3

1. ファイル名の検索

files.txtから.exeの拡張子を持つファイルだけを抜き出す

実行

$ grep "\.exe$" files.txt
test.exe
画面仕様書.xls.exe

2. 画像ファイルの一括変換

PNG形式の画像をconvertコマンドでJPEG形式に変換する

実行

$ ls *.png | sed 's/\.png$//' | xargs -I FILE convert FILE.png FILE.jpg

xargsの-I(iのupper case)オプションで指定した文字列に受け取った値が入る。上記の場合はFILE

3. ファイル名の一括変換

ファイル名の先頭に0をつけて4桁に揃える。本では7桁だがファイル数が多いので4桁にした

準備

ファイルを作成

$ mkdir tmp && cd tmp

$ seq 1000 | xargs touch

$ ls | wc -l
    1000

$ ls | head
1
10
100
1000
101
102
103
104
105
106

実行

ファイル名を変更。 awkで rename前後のファイル名を用意してxargsに渡す。

$ ls | sed 's/^\.\///' | awk '{print $1, sprintf("%04d", $1)}' | xargs -n2  mv

確認

$ ls | head
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010

4. 特定のファイルの削除

ファイルの中身が10のファイルを削除する

準備

100000個のファイルを作成。BashのRANDOM変数は0-32767の中からランダムで1つの整数が選ばれる。

$ mkdir tmp && cd tmp

$ seq 100000 | sed 's/^/echo $RANDOM> /' | bash

$ grep -r "^10$" .
./34854:10

ファイルの中身が10のファイルが1つ作られていた。

実行

マッチしたファイル名をxargsに渡してrmする

$ grep -rl "^10$" . | xargs rm

grepの-lオプションを使えばファイル名だけを出力できるので、これを使えばわざわざawkなどでファイル名を抽出する必要がなくなる。

man grepの結果

 -l, --files-with-matches
        Only the names of files containing selected lines are written to standard output.  grep will only search a file until a match has been found, making
        searches potentially less expensive.  Pathnames are listed once per file searched.  If the standard input is searched, the string ``(standard
        input)'' is written.

確認

マッチしたファイルが削除されている。

$ ls 34854
ls: 34854: No such file or director

$ ls | wc -l
   99999

5. 設定ファイルからの情報抽出

ntp.confからpool項目にあるサーバ名を抽出する

準備

用意されたntp.confをpoolでgrepすると以下の通り。pool項目は、先頭がpoolで始まる行を表している。

$ cat ntp.conf | grep pool
# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
pool 0.ubuntu.pool.ntp.org iburst
pool 1.ubuntu.pool.ntp.org iburst
pool 2.ubuntu.pool.ntp.org iburst
pool 3.ubuntu.pool.ntp.org iburst
pool ntp.ubuntu.com
# Needed for adding pool entries

実行

先頭がpoolで始まる行の2列目を抽出

$ cat ntp.conf | grep "^pool" | awk '{print $2}'
0.ubuntu.pool.ntp.org
1.ubuntu.pool.ntp.org
2.ubuntu.pool.ntp.org
3.ubuntu.pool.ntp.org
ntp.ubuntu.com

grepの代わりにawkで行抽出するときは、awk '$n=="STRING"'が使える。これはn列目がSTRING文字列の行を抽出している。

$ cat ntp.conf | awk '$1=="pool"' | awk '{print $2}'
0.ubuntu.pool.ntp.org
1.ubuntu.pool.ntp.org
2.ubuntu.pool.ntp.org
3.ubuntu.pool.ntp.org
ntp.ubuntu.com

6. 端末に模様を描く

ワンライナーで以下を出力する

    x
   x
  x
 x
x

実行

seqで5から1まで順に渡し、awkはその分だけスペースを出力する。

$ seq 5 1 | awk '{for (i=1; i<=$1; i++){printf " "}; print "x"}'
     x
    x
   x
  x
 x

tacを使えば出力を逆順にできる。

$ seq 5 | awk '{for (i=1; i<$1; i++){printf " "}; print "x"}' | tac
    x
   x
  x
 x
x

7. 消費税

2019/10以前、もしくは、商品名の先頭に*が付いている時は消費税8%、それ以外は10%として計算し、合計金額を求める。

実行

先頭が*の文字列を抽出する条件は$2~/^\*/。税率のカラムを追加して最後に税率を掛けた分の合計を計算。

$ cat kakeibo.txt | awk '{tax=($1<"20191001"||$2~/^\*/)?1.08:1.1;print $0,tax}' | awk 'BEGIN{sum=0}{sum+=int($3*$4)}END{print sum}'
53612

8. ログの集計

access.logから午前と午後のそれぞれの行数を求める。

実行

タイムスタンプが含まれている4列目を抽出し、:でsplitして時間ごとにグルーピングしてカウント。

$ cat access.log | awk '{print $4}' | awk -F: '$2<12{print "Morning"}$2>=12{print "Evening"}' | sort | uniq -c
   3 Evening
   2 Morning

NF(Number of Fields)で列数を取得できるので、後ろから時間を抽出するのも良い

$ cat access.log | awk -F: '{print $(NF-2)}' | awk '$1<12{print "Morning"}$1>=12{print "Evening"}' | sort | uniq -c
   3 Evening
   2 Morning

9. ログの抽出

2016/12/24 21:00:00 ~ 2016/12/25 03:59:59の時間帯のログを抽出する

実行

grepのor条件を使って指定の時間にマッチする行を抽出

$ cat log_range.log | grep -e '24\/Dec\/2016 2[123]:' -e '25\/Dec\/2016 0[0123]:'
192.168.77.248 - - [24/Dec/2016 21:12:20] "GET / HTTP/1.0" 200 4294
192.168.152.143 - - [24/Dec/2016 22:06:19] "GET / HTTP/1.0" 200 7255
192.168.6.132 - - [24/Dec/2016 23:00:42] "GET / HTTP/1.0" 200 4298
192.168.222.3 - - [25/Dec/2016 00:03:23] "GET / HTTP/1.0" 200 8547
192.168.101.95 - - [25/Dec/2016 01:01:40] "GET / HTTP/1.0" 200 8488
192.168.141.18 - - [25/Dec/2016 02:15:52] "GET / HTTP/1.0" 200 4533
192.168.110.169 - - [25/Dec/2016 03:06:54] "GET / HTTP/1.0" 200 3461

awkで実行する場合に、条件を日付の開始終了をawk '/開始パターン/,/終了パターン/'で指定できる。

これは開始パターンでマッチする行から終了パターンにマッチする行までを抽出できる。

$ cat log_range.log | awk '/24\/Dec\/2016 21:..:../,/25\/Dec\/2016 03:..:../'
192.168.77.248 - - [24/Dec/2016 21:12:20] "GET / HTTP/1.0" 200 4294
192.168.152.143 - - [24/Dec/2016 22:06:19] "GET / HTTP/1.0" 200 7255
192.168.6.132 - - [24/Dec/2016 23:00:42] "GET / HTTP/1.0" 200 4298
192.168.222.3 - - [25/Dec/2016 00:03:23] "GET / HTTP/1.0" 200 8547
192.168.101.95 - - [25/Dec/2016 01:01:40] "GET / HTTP/1.0" 200 8488
192.168.141.18 - - [25/Dec/2016 02:15:52] "GET / HTTP/1.0" 200 4533
192.168.110.169 - - [25/Dec/2016 03:06:54] "GET / HTTP/1.0" 200 3461

sedも同様にsed -n '/正規表現1/,/正規表現2/p'で範囲検索ができる

$ cat log_range.log | sed -n '/24\/Dec\/2016 21:..:../,/25\/Dec\/2016 03:..:../p'
192.168.77.248 - - [24/Dec/2016 21:12:20] "GET / HTTP/1.0" 200 4294
192.168.152.143 - - [24/Dec/2016 22:06:19] "GET / HTTP/1.0" 200 7255
192.168.6.132 - - [24/Dec/2016 23:00:42] "GET / HTTP/1.0" 200 4298
192.168.222.3 - - [25/Dec/2016 00:03:23] "GET / HTTP/1.0" 200 8547
192.168.101.95 - - [25/Dec/2016 01:01:40] "GET / HTTP/1.0" 200 8488
192.168.141.18 - - [25/Dec/2016 02:15:52] "GET / HTTP/1.0" 200 4533
192.168.110.169 - - [25/Dec/2016 03:06:54] "GET / HTTP/1.0" 200 3461

10. 見出しの記法の変換

Markdownの###の見出しを===---に変換する

元のファイル

$ cat 10/headings.md
# AAA

これはAAAです

# BBB

これはBBBです。
楽しいですね。

## CCC

これはCCCCです

## DDD

これはDDDです

実行

先頭が###で始まる行を改行文字を含めて置換する。Macではgsedを使わないと改行がめんどくさい。

$ cat 10/headings.md | gsed -E 's/^# (.*)/\1\n===/' | gsed -E 's/^## (.*)/\1\n---/'
AAA
===

これはAAAです

BBB
===

これはBBBです。
楽しいですね。

CCC
---

これはCCCCです

DDD
---

これはDDDです

11. 議事録の整理

以下の議事録を整形する。

$ cat 11/gijiroku.txt
すず
あばばあばば

さと
あばばばばばばば!

やま
びっくりするほどユートピア!びっくりするほどユートピア!

すず
うひょひょひょwwwwwやまwwやまwww

さと
ひょおお?ひょおお???

すず
それでは会議を終わります

実行

xargsの-nオプションで文字列を2つずつ並べて表示する。

sedの条件が複数ある場合は;でつなぐ。

$ cat 11/gijiroku.txt | xargs -n2 | sed 's/^すず/鈴木:/;s/^さと/佐藤:/;s/^やま/山田:/'
鈴木: あばばあばば
佐藤: あばばばばばばば!
山田: びっくりするほどユートピア!びっくりするほどユートピア!
鈴木: うひょひょひょwwwwwやまwwやまwww
佐藤: ひょおお?ひょおお???
鈴木: それでは会議を終わります