シェル芸スパルタン演習(第07回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第07回シェル芸勉強会の設問を用いた。
実行環境
Arch Linux 4.8.4-1-ARCH
GNU bash 4.3.46
GNU coreutils 8.25-2
GNU diffutils 3.5-1
GNU findutils 4.6.0-2
util-linux 2.28.2-1
grep (GNU grep) 2.26
GNU bc 1.06.95
sed (GNU sed) 4.2.2
gawk 4.1.4
usp Personal Tukubai 20160402 ( Open usp Tukubaiで代用可能)
シェル芸演習
問題文および模範解答は、以下のURLから。
https://www.slideshare.net/ryuichiueda/20131102-27846323
問1
ドットで区切られた4つの数字について、左側の数字を優先に数字の小さい順に並べてください。 $ cat ip 123.45.6.78 1.9.8.4 1.119.8.4 199.9.31.123 20.123.123.123 19.345.6.78
IPv4アドレスを下位バイトからソートする問題。
sortの-kオプションで一発。
01 #!/bin/sh
02
03 cat ip |
04 sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n
以下、コピペ実行用のワンライナーコード。
cat ip | sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n
問2
次の文について、taxi、1km、10000の前後に半角スペースを挿入してください。 - このtaxiは1kmあたり10000円
シンプルな問題。
sedのsコマンドと正規表現でゴリゴリと。
01 #!/bin/sh
02
03 echo 'このtaxiは1kmあたり10000円' |
04 sed 's/[a-z0-9]\{1,\}/ & /g'
以下、コピペ実行用のワンライナーコード。
echo 'このtaxiは1kmあたり10000円' | sed 's/[a-z0-9]\{1,\}/ & /g'
問3
次の文についてspaceの前・invaderの後ろ・4の後ろのスペースを除去してください。 私の名前は space invader です。4 歳です。 ※次のようになればOK 私の名前はspace invaderです。4歳です。
問2と似た問題。
文字列がスペース区切りになっている事を利用して、awkのフィールド指定出力で整形する。
01 #!/bin/sh
02
03 echo '私の名前は space invader です。4 歳です。' |
04 awk '{print $1$2,$3$4$5}'
以下、コピペ実行用のワンライナーコード。
echo '私の名前は space invader です。4 歳です。' | awk '{print $1$2,$3$4$5}'
問4
次のマーク付きの文について「<b>hoge</b>」が重複していることをワンライナーで示してください。 $ cat hogefuga <b>hoge</b>は<b>fuge</b>なので<b>hoge</b>fugeだよね。
HTMLソースの中から、重複しているものを探す問題。
HTMLタグ毎に改行した後、煮るなり焼くなり。
01 #!/bin/sh
02
03 cat hogefuge |
04 sed 's;<b>....</b>;\n&\n;g' |
05 grep '<b>....</b>' |
06 sort |
07 uniq -c
以下、コピペ実行用のワンライナーコード。
cat hogefuge | sed 's;<b>....</b>;\n&\n;g' | grep '<b>....</b>' | sort | uniq -c
問5
00001~20000という名前の空ファイルを作ってください。
シンプルな問題。
ブレース展開でも良いけれど、ここはあえてシェル依存でない方法で。
01 #!/bin/sh
02
03 seq -w 1 20000 |
04 xargs touch
以下、コピペ実行用のワンライナーコード。
seq -w 1 20000 | xargs touch
問6
a) さきほど(問題5)作った2万個のファイルのリストをなるべく短い時間で作ってください。 b) ファイルを消してください。
問5の発展系。
なるべく早くとの事なので、xargsの並列実行オプションを解禁。
a)
01 #!/bin/sh
02
03 seq -w 1 20000 |
04 xargs -P 0 touch
消す方も同様に。
xargsが渡すコマンドを、touchからrmに変えるだけ。
b)
01 #!/bin/sh
02
03 seq -w 1 20000 |
04 xargs -I@ -P 0 rm @
以下、コピペ実行用のワンライナーコード。
seq -w 1 20000 | xargs -P 0 touch
seq -w 1 20000 | xargs -I@ -P 0 rm @
問7
次の図のようなディレクトリ、ファイルを作り、例えば ./a/001 なら ./a_001 ./b./c./123 なら ./b_c_123 のようにパスの「/」をアンダースコアに変えて、./ にコピーを置いてください。 ./test ├── a │ ├── 001 │ └── 002 └── b ├── c │ └── 123 ├── xxx └── yyy できる人はwhileやforを使わないで。
やや難しい問題。
まず、findを使って、全ファイルをパス付きで出力する。
その後、awkを使って、パスをファイル名に変換しつつ、コピーを実行するコマンド列を生成する。
最後に実行して完了。
01 #!/bin/sh
02
03 find . -type f |
04 awk -F/ '{printf("cp %s %s/", $0,$1); for(i=2;i<NF;i++){printf("%s_", $i)}; print $NF}' |
05 sh
以下、コピペ実行用のワンライナーコード。
find . -type f | awk -F/ '{printf("cp %s %s/", $0,$1); for(i=2;i<NF;i++){printf("%s_", $i)}; print $NF}' | sh
問8
/etc/ 下の読み込み可能な各ファイルについて、fooと書いてある行の数を数えてください。
難しそうに見えて、実はシンプルな問題。
最大のポイントは、再帰的に探索をおこなうgrepの-rオプション。
このオプションを使えば、マッチしたレコードに、ファイル名が付加される。
後は、awkで集計するだけ。
(僕の環境では、/etc/以下にバイナリも混じっていたため、それを無視する-Iオプションも利用した。)
01 #!/bin/sh
02
03 grep -rI 'foo' /etc 2>/dev/null |
04 awk -F: '{file[$1]++} END{for(n in file){print n,file[n]}}'
以下、コピペ実行用のワンライナーコード。
grep -rI 'foo' /etc 2>/dev/null | awk -F: '{file[$1]++} END{for(n in file){print n,file[n]}}'
問9
今度は、/etc/ 下の読み込み可能な各ファイルについて、fooの数を数えてください。
問8の変形型。
これもポイントは、列行変換でも使われる、grepの-oオプション。
後は問8と同じ。
01 #!/bin/sh
02
03 grep -rIo 'foo' /etc 2>/dev/null |
04 awk -F: '{file[$1]++} END{for(n in file){print n,file[n]}}'
以下、コピペ実行用のワンライナーコード。
grep -rIo 'foo' /etc 2>/dev/null | awk -F: '{file[$1]++} END{for(n in file){print n,file[n]}}'
問10
以下の内容を、日付の古い順にソートしてください。 $ cat days Jun 4, 2012 Jul 19, 2013 Jul 2, 2013 Apr 9, 2010 Oct 30, 2012
日付をソートする問題。
ただし、年月日のフィールドの並びが逆になっている上に、月名は略称になっている。
そのため、まずは月の略称名を、数値に置換する。
その後、sortのフィールド指定オプション-kで、並び変えるだけ。
01 #!/bin/sh
02
03 cat days |
04 sed 's/Jun/6/' |
05 sed 's/Jul/7/' |
06 sed 's/Apr/4/' |
07 sed 's/Oct/10/' |
08 tr -d ',' |
09 sort -k 3,3n -k 1,1n -k 2,2n
また、GNU sort限定だけれど、月の略称名でソートできる-Mオプションを使えば、とてもシンプルに。
01 #!/bin/sh
02
03 cat days |
04 sort -k 3,3 -k 1,1M -k 2,2r
以下、コピペ実行用のワンライナーコード。
cat days | sed 's/Jun/6/' | sed 's/Jul/7/' | sed 's/Apr/4/' | sed 's/Oct/10/' | tr -d ',' | sort -k 3,3n -k 1,1n -k 2,2n
cat days | sort -k 3,3 -k 1,1M -k 2,2r
雑記
この回は、シンプルだけれど、なかなか実用的な問題が多かった印象。
(だんだんとコメントする事が無くなってきちゃった…w)