2016年10月16日

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


シェル芸演習

問題文は、以下の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)

雑記

  • (書くネタが無い…​)

  • せっかくなので、動物写真を貼っておく。

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