シェル芸スパルタン演習(第09回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第09回シェル芸勉強会の設問を用いた。
実行環境
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から。
http://blog.ueda.asia/?p=2024
模範解答は、こちらから。
http://blog.ueda.asia/?p=1955
問1
ファイルを、ファイル名の頭文字に該当するディレクトリに移動する問題。
sedで頭文字を抜き出しつつ、コマンド列を生成して、実行する。
01 #!/bin/sh
02
03 ls |
04 sed 's/\(^.\).*/mkdir \1 2>/dev/null; mv & \1/' |
05 sh
以下、コピペ実行用のワンライナーコード。
ls | sed 's/\(^.\).*/mkdir \1 2>/dev/null; mv & \1/' | sh
問2
ファイル名に含まれるスペースを、アンダーバーにリネームする問題。
これも問1と同様に、コマンド列を生成して実行すればOK。
01 #!/bin/sh
02
03 ls |
04 sed 's/\(.*\) \(.*\)/mv "&" \1_\2/' |
05 sh
以下、コピペ実行用のワンライナーコード。
ls | sed 's/\(.*\) \(.*\)/mv "&" \1_\2/' | sh
問3
日付連番でファイルを作成する問題。
coreutilsだけだと、けっこう大変…
一度YYYYMMDDをUnix時間に変換し、Unix時間を一日ずつずらしていき、再度YYYYMMDDに変換する。
後は、日付に対応したdateコマンドを生成し、リダイレクトで書き込むだけ。
01 #!/bin/sh
02
03 seq $(date --date 20140101 +%s) $((60*60*24)) $(date --date 20141231 +%s) |
04 sed 's/^/@/' |
05 date -f - +%Y%m%d |
06 sed 's/.*/date --date & > &/' |
07 sh
Tukubai版。
Tukubaiには、日付を扱う超強力なmdateコマンドがあるので、すっごくシンプルに。
01 #!/bin/sh
02
03 mdate -e 20140101/+364 |
04 tr ' ' '\n' |
05 sed 's/.*/date --date & > &/' |
06 sh
以下、コピペ実行用のワンライナーコード。
seq $(date --date 20140101 +%s) $((60*60*24)) $(date --date 20141231 +%s) | sed 's/^/@/' | date -f - +%Y%m%d | sed 's/.*/date --date & > &/' | sh
mdate -e 20140101/+364 | tr ' ' '\n' | sed 's/.*/date --date & > &/' | sh
問4
ファイルの中身を入れ替える問題。
単純そうで、実はかなり難しい…
grepでファイル名付きで中身を出力した後、awkで中身を入れ替えるコマンドを頑張って生成する。
01 #!/bin/sh
02
03 grep '' * |
04 awk -F: '$1 ~ /curry/{print "echo カレー >",$1} $1 ~ /ramen/{print "echo ラーメン >",$1} $1 ~ /apple/{print "echo リンゴ >",$1} $1 ~ /tomato/{print "echo リンゴ >",$1}' |
05 sh
Tukubai版。
大まかな方針は上と同じだけれど、Tukubaiコマンドのお陰で、かなりシンプルに。
01 #!/bin/sh
02
03 grep '' * |
04 tr ':' ' ' |
05 tateyoko |
06 awk 'NR==1{print $2,$1,$4,$3; next} 1' |
07 tateyoko |
08 self 2 1 |
09 awk '{print "echo",$1,">",$2}' |
10 sh
以下、コピペ実行用のワンライナーコード。
grep '' * | awk -F: '$1 ~ /curry/{print "echo カレー >",$1} $1 ~ /ramen/{print "echo ラーメン >",$1} $1 ~ /apple/{print "echo リンゴ >",$1} $1 ~ /tomato/{print "echo リンゴ >",$1}' | sh
grep '' * | tr ':' ' ' | tateyoko | awk 'NR==1{print $2,$1,$4,$3; next} 1' | tateyoko | self 2 1 | awk '{print "echo",$1,">",$2}' | sh
問5
問3で生成した日付ファイルを、月毎にアーカイブする問題。
seqでYYYYMMを生成した後、コマンド列を生成して実行する。
…のだけれど、xargsを入れ子にする、すっごくややこしい事になってしまった。
01 #!/bin/sh
02
03 seq 201401 201412 |
04 xargs -I@ sh -c 'mkdir @; find . -type f -name "*@*" |xargs -I{} mv {} @; tar zcf @.tar.gz ./@'
以下、コピペ実行用のワンライナーコード。
seq 201401 201412 | xargs -I@ sh -c 'mkdir @; find . -type f -name "*@*" |xargs -I{} mv {} @; tar zcf @.tar.gz ./@'
問6
ファイル・ディレクトリ操作に関する問題。
小問1
ディレクトリを100階層作る問題。
mkdir -pに与えるパスを生成すれば、あとは一発。
01 #!/bin/sh
02
03 yes a |
04 head -n 100 |
05 xargs |
06 tr ' ' '/' |
07 sed 's;^;./;' |
08 sed 's/.*/mkdir -p &/' |
09 sh
小問2
小問1で作ったディレクトリの最下層に、ファイルを作る問題。
これも、パスを生成できれば、後はtouchするだけ。
01 #!/bin/sh
02
03 yes a |
04 head -n 100 |
05 xargs |
06 tr ' ' '/' |
07 sed 's;^;./;' |
08 sed 's;$;/b;' |
09 sed 's/.*/touch &/' |
10 sh
小問3
ディレクトリの最下層に移動する問題。
これも、パスを生成できればOK。
今回は、コマンド置換を使ってみた。
01 #!/bin/sh
02
03 cd $(yes a |head -n 100 |xargs |tr ' ' '/' |sed 's;^;./;')
以下、コピペ実行用のワンライナーコード。
小問1
yes a | head -n 100 | xargs | tr ' ' '/' | sed 's;^;./;' | sed 's/.*/mkdir -p &/' | sh
小問2
yes a | head -n 100 | xargs | tr ' ' '/' | sed 's;^;./;' | sed 's;$;/b;' | sed 's/.*/touch &/' | sh
小問3
cd $(yes a |head -n 100 |xargs |tr ' ' '/' |sed 's;^;./;')
問7
問6の続き。
最下層にあるファイルを、50階層目に移動する問題。
生成するのが大変だけれど、これもパスさえ作ればOK。
01 #!/bin/sh
02
03 mv $(yes a |head -n 100 |xargs |tr ' ' '/' |sed 's;^;./;' |sed 's;$;/b;') $(yes a |head -n 50 |xargs |tr ' ' '/' |sed 's;^;./;' |sed 's;$;/;')
以下、コピペ実行用のワンライナーコード。
mv $(yes a |head -n 100 |xargs |tr ' ' '/' |sed 's;^;./;' |sed 's;$;/b;') $(yes a |head -n 50 |xargs |tr ' ' '/' |sed 's;^;./;' |sed 's;$;/;')
問8
問6、7の続き。
後始末をする問題。
rm -rは禁止ということなので、代わりにrmdir -pを使った。
ただし、ファイルが存在しているとrmdir出来ないので、予め消しておく。
一応ワンライナーだけれど、単にサブシェルで括っているだけなので、あんまりエレガントじゃないなぁ…
1 #!/bin/sh
2
3 (find a -type f | xargs rm; cd a; yes a | head -n 99 | xargs | tr ' ' '/' | sed 's/.*/rmdir -p &/' | sh)
以下、コピペ実行用のワンライナーコード。
(find a -type f | xargs rm; cd a; yes a | head -n 99 | xargs | tr ' ' '/' | sed 's/.*/rmdir -p &/' | sh)
雑記
(書くネタが無い…)
せっかくなので、動物写真を貼っておく。
物思いにふける猫神様。ひなたさんの優しい表情が好きです。 2016.10.01(土) #東山動物園 #ツシマヤマネコ ひなた #ツシマヤマネコの日 pic.twitter.com/JgpVEEsKT5
— ginjiro (@gin_135) 2016年10月8日