2016年10月14日

シェル芸スパルタン演習(第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


シェル芸演習

問題文および模範解答は、以下の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)

Tags: シェル芸 Unix
このエントリーをはてなブックマークに追加