2016年10月28日

シェル芸スパルタン演習(2015年末年始回シェル芸勉強会編)

ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。

今回は、問題として、2015年末年始シェル芸勉強会の設問を用いた。


実行環境

  • 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

  • curl 7.50.3

  • GNU Wget 1.18

  • t 2.10.0 ( https://github.com/sferik/t )


シェル芸演習

問題文は、以下のURLから。
http://blog.ueda.asia/?p=4852

模範解答は、こちらから。
http://blog.ueda.asia/?p=4821

問1

重複しているファイルを削除する問題。
画像だけとは言わず、全ファイルを対象にするぞ…​!!

まず、findでファイルリストを出力する。
次に、各ファイルに対し、MD5ハッシュ値を出力する。
その後、ハッシュ値でsort, uniqをして、内容が重複しているファイルリストを得る。
最後に、得られたファイルリストを、xargsを経由でrmに渡してやれば、完了。

01 #!/bin/sh
02
03 find . -type f          |
04 xargs -n 1 md5sum       |
05 awk '{print $2,$1}'     |
06 sort -k 2,2             |
07 uniq -f 1 -d            |
08 awk '$0=$1'             |
09 xargs -n 1 rm

以下、コピペ実行用のワンライナーコード。

本当に実行して、大事な エロ画像 ファイルを消さないように注意。

find . -type f | xargs -n 1 md5sum | awk '{print $2,$1}' | sort -k 2,2 | uniq -f 1 -d | awk '$0=$1' #| xargs -n 1 rm

問2

羽田空港の緯度経度を求める問題。

シェル上だけではどうしようも無いので、とりあえずWebからデータを取得する。
今回は、Google Mapを利用した。
Google Mapから羽田空港の位置を示したHTMLソースを取得し、その中から緯度経度を削り出していけばOK。

01 #!/bin/sh
02
03 curl -sL 'https://www.google.co.jp/maps/place/羽田空港+(HND)/'     |
04 grep -o 'cacheResponse([^)]*)'                                     |
05 awk -F, '{print $2,$3}'                                            |
06 tr -d ']'

以下、コピペ実行用のワンライナーコード。

curl -sL 'https://www.google.co.jp/maps/place/羽田空港+(HND)/' | grep -o 'cacheResponse([^)]*)' | awk -F, '{print $2,$3}' | tr -d ']'

問3

任意の級数からネイピア数を求める問題。
ネイピア数を求める級数は沢山あるので、一番計算しやすそう(?)な、次の級数を利用する。

1から順に自然数を延々と出力し、その値を上記の式に当てはめて、計算していく。
計算していく内に、計算結果が0に収束するので、sedを利用して出力を打ち切る。
その後、得られた計算結果を全て足し合わせれば、(できるだけ)正確なネイピア数が得られる。

01 #!/bin/sh
02
03 seq 1 inf                                                                                                                    |
04 awk 'BEGIN{print "scale=1000"} {printf $0"/(2*("; for(i=1;i<=$0 - 1;i++){printf i" "}; print $0-1 == 0 ? $0"))" : "))"}'     |
05 sed 's/ )/)/'                                                                                                                |
06 tr ' ' '*'                                                                                                                   |
07 BC_LINE_LENGTH=0 bc -l                                                                                                       |
08 sed '/^0$/q'                                                                                                                 |
09 tr '\n' '+'                                                                                                                  |
10 sed 's/\+$/\n/'                                                                                                              |
11 BC_LINE_LENGTH=0 bc -l

以下、コピペ実行用のワンライナーコード。

seq 1 inf | awk 'BEGIN{print "scale=1000"} {printf $0"/(2*("; for(i=1;i<=$0 - 1;i++){printf i" "}; print $0-1 == 0 ? $0"))" : "))"}' | sed 's/ )/)/' | tr ' ' '*' | BC_LINE_LENGTH=0 bc -l | sed '/^0$/q' | tr '\n' '+' | sed 's/\+$/\n/' | BC_LINE_LENGTH=0 bc -l

問4

多重にbase64エンコードが掛けられたファイルに対して、ワンライナーで復号する問題。
base64は、デコードに失敗すると終了ステータス1を返す事がポイント。
base64を延々と実行し、失敗したらexit → ファイルをcatすれば良い。
ただし、1つのファイルだけで処理をおこなうのは困難なので、seqで生成した自然数を、作業ファイル名に使うことにする。
解読が終わり、base64のデコードが失敗したら、exitで直前の行番号(=最後のファイル名)を終了ステータスとして返してやるのがコツ。
最後に、特殊シェル変数'$?'を利用して、解読完了したファイルをcatしてやればOK。

01 #!/bin/sh
02
03 wget -q https://blog.ueda.asia/misc/message2015.txt
04 seq 1 inf                                                                                                                          |
05 awk '{printf "cat message2015.txt"; for(i=1;i<=$0;i++){printf " |base64 -d"}; print " 2>/dev/null > "$0".txt || exit "$0-1""}'     |
06 sh
07 cat $?.txt

以下、コピペ実行用のワンライナーコード。

wget -q https://blog.ueda.asia/misc/message2015.txt; seq 1 inf | awk '{printf "cat message2015.txt"; for(i=1;i<=$0;i++){printf " |base64 -d"}; print " 2>/dev/null > "$0".txt || exit "$0-1""}' | sh; cat $?.txt

問5

円周率を精度良く求める問題。

seqで自然数を生成し、bcコマンドの変数scaleにその値を設定することで、徐々に精度の高い円周率を出力させる。

01 #!/bin/sh
02
03 seq 1 inf                       |
04 sed 's/.*/scale=&; 4*a(1)/'     |
05 BC_LINE_LENGTH=0 bc -l

以下、コピペ実行用のワンライナーコード。

seq 1 inf | sed 's/.*/scale=&; 4*a(1)/' | BC_LINE_LENGTH=0 bc -l

問6

部分集合を列挙する問題。

…​実はこれ、分からなくて解答を見てしまったorz
bashのブレース展開を利用するのが、ポイントなのね。

01 #!/bin/bash
02
03 echo {,a}{,b}{,c}{,d}{,e}

以下、コピペ実行用のワンライナーコード。

echo {,a}{,b}{,c}{,d}{,e}

問7

与えられた数値が完全数であることを示す問題。

手前味噌だけれど、以前Project Eulerでそっくりな問題を解いたので、そちらを参考に。

01 #!/bin/sh
02
03 echo 8128                                                                                             |
04 awk '{print $0; for(i=1;i*i<=$0;i++){if($0%i==0){print i; if(i!=$0/i && $0!=$0/i){print $0/i}}}}'     |
05 awk 'BEGIN{sum=0} NR==1{num=$0} NR!=1{print sum,$0; sum+=$0} END{print "--"; print sum,num}'

以下、コピペ実行用のワンライナーコード。

echo 8128 | awk '{print $0; for(i=1;i*i<=$0;i++){if($0%i==0){print i; if(i!=$0/i && $0!=$0/i){print $0/i}}}}' | awk 'BEGIN{sum=0} NR==1{num=$0} NR!=1{print sum,$0; sum+=$0} END{print "--"; print sum,num}'

14474011154664524427946373126085988481573677491474835889066354349131199152128の証明は、上記と同じコードで出来るけれど…​
僕が所持している一番ハイスペックなマシンで、3日間実行してみたけれど、それでも処理が終わらなかった/(^o^)\

問8

Twitterから任意のキーワードを含んだツイートを収集する問題。

…​面倒臭いので、 Unixコマンドと親和性の良いTwitterコマンド、tで済ませるw

01 #!/bin/sh
02
03 t search all --long -n 3200 'シェル芸'

以下、コピペ実行用のワンライナーコード。

t search all --long -n 3200 'シェル芸'

雑記

  • スパルタン演習のラストを飾るこの回は、 ド変態 上級者向けの問題ばかりだった。

  • 変態的な問題ばかりだけれど、数値計算系の問題は好きなので、個人的にはかなり楽しめた。

  • ということで、第25回勉強会開催までに間に合わなかったけれど、これにてシェル芸スパルタン演習完了!

  • 全21回のシェル芸勉強会の問題を解いたけれど、これで少しはシェル芸力が上がっているといいなぁ…​

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