2012/10/27 に行ったシェルスクリプト勉強会を自分なりに解析してみました。
- 正直 3 問目から手が止まりました。
- awk とかさっぱりですよ→解析してちょっと学びました。
- パイプ毎に動作を確認すると勉強になります。今回、コマンドの流れに沿ってどういった動作をしているか調べた結果をメモしてみました。(調べた結果なので間違ってるかも)
- 問10が無いのは
心が折れたから忙しかったからです。
勉強会資料
[embedit snippet="html1"]
以下(自分なりの)解析結果
●問題1: ユーザの抽出(初級)
$ awk '{sub(/:.*/,"",$0);print}' /etc/passwd
→ sub は置換、// は正規表現
● 問題2: ユーザの抽出2(中級)
$ cut -d: -f7 /etc/passwd | grep -E '/(ba)?sh$' | sort | uniq -c
→ ? は直前の文字が 0 or 1 でマッチ
$ sed 's;^.*/;;' /etc/passwd | awk '/^bash$|^sh$/' | sort | uniq -c
→ sed の ; は区切り文字、最長マッチするから最後のシェルだけ表示できる
→ awk はパターンマッチしてるだけ(grep -E でもいける)
$ awk -F/ '$NF=="sh"||$NF=="bash" {print $NF}' /etc/passwd | sort | uniq -c
→ $NF はフィールド数=最後のフィールド
$ awk -F/ '{print $NF}' /etc/passwd | awk '/^sh$|^bash$/' | sort | uniq -c
→ 最終フィールドを出してから、パターンマッチかける
●問題3: ファイルの一括変換(中級)
※あらかじめディレクトリは作成しておく
$ grep -r '#!/bin/bash' /etc/ 2> /dev/null | sed 's/:.*$//' | while read f ; do sed 's;#!/bin/bash;#!/usr/local/bin/bash;' $f > ~/hoge/$(basename $f) ; done
→ 一つ目で検索を再帰的にかける(読めないやつは /dev/null へ)
→ sed で : より後ろを削除してファイル名を手に入れる
→ while read f で 1 行づつ処理して、置換する。出力を hoge 配下へ
→ $(basename $f) はファイル名だけに手に入れる
→ $() は `` (バッククオート)と同じで、 コマンドの実行結果が返ってくる
●問題4: 集計(中級)
$ while : ; do echo $*1 ; done > ages
でファイルを作成しておく(数秒たったら Ctrl+D でストップ)
→ "*2" で囲うと、shell が計算してくれる。expr でもできるけど外部コマンドだからこっちの方が早い
# 昔は出来なかったらしい
→ $RANDOM は 0 から 32767 までのランダムな整数が生成される。% で余りを出すことにより指定の数値以下のランダムな数を入手出来る
$ awk '{print int ($1/10)}' ages | sort -n | uniq -c | awk '{print $2*10 "~" $2*10+9,$1}'
→ int で 1 のくらいをなくす
→ sort -n で数値としてソートする(1,10,11,12 とかならない)
→ uniq -c でカウントしてくれる
→ awk で表示を整形する
$ sed 's/.$//' ages | sed 's/^$/0/' | sort -n | uniq -c | awk '{print $2*10"~"$2*10+9,$1}'
→ sed で最後の一文字を削除
→ sed で 0 をつける
→ あとは上と一緒
$ cat ages | sed 's/.$//' | sed 's/^$/0/' | sort -n | uniq -c | awk '{print $2*10,$2*10+9,$1}' | sed 's/ /~/'
→ 前半は上と一緒
→ 見た目の交え方を sed にした
● 問題5: Fizz Buzz (上級)
・素直に if 使う方法
$ seq 1 100 | awk '{if($1%15==0){print "FizzBuzz"}else if($1%5==0){print "Buzz"}else if($1%3==0){print "Fizz"}else{print $1}}' | tr '\n' ','
→ seq 1 100 で 1 ~ 100 までの数を出力 <start num> <end num> の関係。
→ あとは awk で計算。15 で割った余りが 0(= 15 の倍数)は FizzBuzz, 5 で割った余りが 0(= 5 の倍数)は Buzz 以下略
・先に計算を済ませる方法
$ seq 1 100 | awk '{print $1 , $1%3 , $1%5 , $1%15}'| awk '{a=$1;if($4==0){a="FizzBuzz"}else if($3==0){a="Buzz"}else if($2==0){a="Fizz"};print a}' | tr '\n' ','
→ 最初の awk で 4 行出す。文字変換するものは 0 になる
→ 後は 4 行目から置換していく
・意地でも if を使わない
$ seq 1 16 | awk '$1%3==0{printf "Fizz"}$1%5==0{printf "Buzz"}{print " " $1}' | awk '{print $1}' | tr '\n' ','
→ if が省略されてるだけ??よくわからない
・sed で頑張る
$ seq 1 16 | awk '{print $1,$1%3,$1%5}'| sed 's/.*0 0$/FizzBuzz/' | sed 's/.* 0$/Buzz/' | sed 's/.*0 .*$/Fizz/' | awk '{print $1}' | tr '\n' ','
→ 先に計算をしておく
→ 0 0 で終わるのは 15 の倍数なので、FizzBuzz と置換する
→ 3 行目が 0 なのは 5 の倍数なので、Buzz と置換する
→ 2 行目が 0 なのは 3 の倍数なので、Fizz と置換する
→ awk で 1 行目だけだす
●問題6: 日付の計算 (中級)
$ echo 19780216 20121027 | tr ' ' '\n' | date +%s -f - | tr '\n' ' ' | awk '{print $1-$2}' | awk '{print $1/(24*60*60)}'
→ 日付を yyyymmdd で echo し、改行する
→ date の、+ はフォーマット変換、%s は unix time
→ -f はファイルから読み込み、- は標準出力
→ tr で 1行に戻す
→ awk で計算する
→ unix time から日付に戻す(1日何秒か計算してそれで割る)
●問題7: リストにないものを探す(中級)
ファイルの作り方
seq 1 10 | awk '{print rand(),$1}' | sort -n | awk '{print $2}' | head -n 9 > nums
→ rand() で 0 以上 1 未満の一様な乱数値と、整数が並んで出る
→ sort -n ランダムで出力された数字を並べ変える
→ rand() で出した方は消す
→ 頭から 9 行だけ出す
$ seq 1 10 | sort - nums | uniq -u
→ sort は複数ファイルを指定すると結合する。そして - は標準出力から受け取る
→ uniq -u の -u は "1 回しか現われない行だけを出力する"
$ sort nums <(seq 1 10) | uniq -u
→ <() すると、中のコマンド結果をファイルとして見てくれる
$ cat nums | awk '{a+=$1}END{print 55-a}'
→ nums の数値を全部足す
→ 最後に、1 から 10 までの全部足した数(55)から、全部足した数を引く。
●問題8: CPU使用率(上級)
$ top -b -n 1 | tail -n +8 | awk '{print $2,$9}' | sort | awk '{if($1==u){c+=$2}else{print u,c;u=$1;c=$2}}END{print u,c}' | sort -k2,2nr
→ -n => 行数指定。+ をするとファイルの先頭から行数分スキップ。 - (又は何も書かない)を指定するとファイルの最後の行数分だけ表示
→ awk で user と CPU だけ取り出す
→ sort する(ユーザの塊が出来る)
→ awk で同じユーザであれば cpu を足していく。そして結果を出す
→ sort -k2,2 で 2 列目を sort する。nr は数字でリバース。
●問題9: 横に並んだ数字のソート(上級)
$ cat -n file | awk '{for(i=2;i<=NF;i++){print $1,$i}}' | sort -k1,1n -k2,2n | awk '{if($1!=a){a=$1;printf"n"};print $2}' | tr '\n' ' ' | tr 'n' '\n' | awk 'NF!=0'
→ 行数をつけて file を cat する
→ NF(列)の数だけ for で回して、行数:数 と分解する
→ 1,2 行目の順で、数値としてソートする
→ 1行目が用意した変数 a と違えば頭に n を入れる(1行目が変わると n を入れる)
→ 改行を空白一つに変換
→ n を改行に変換
→ awk 'NF!=0' がよく分からない...なんでこうなるんだろう