シェル芸スパルタン演習(第08回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第08回シェル芸勉強会の設問を用いた。
実行環境
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/20131222-8
問1
二つずつ数字をフリップしてください。 例 - 入力: 1 2 3 4 5 6 7 8 9 10 - 出力: 2 1 4 3 6 5 8 7 10 9
問題分の通り。
awkでフィールドを決め打ちすれば楽ちんだけれど、それだと面白くないので、ちょっと変なやり方で。
まずはsedのホールドスペースを活用して。
01 #!/bin/sh
02
03 seq -s ' ' 1 10 |
04 tr ' ' '\n' |
05 sed -n 'h; n; G; p' |
06 xargs
お次はxargsで1レコード2フィールドにして、左右を入れ替え。
01 #!/bin/sh
02
03 seq -s ' ' 1 10 |
04 xargs -n 2 |
05 awk '{print $2,$1}' |
06 xargs
以下、コピペ実行用のワンライナーコード。
seq -s ' ' 1 10 | tr ' ' '\n' | sed -n 'h; n; G; p' | xargs
seq -s ' ' 1 10 | xargs -n 2 | awk '{print $2,$1}' | xargs
問2
「ユニケージ」「ユニゲージ」「UPS」「USP」の各個数を 数えてください。 - ユニゲージユニケージユニゲージUSP友の会USP友の会UPS友の会UPS友の会
文字列から単語の個数を求める問題。
正規表現で切り出した後、集計するだけ。
01 #!/bin/sh
02
03 echo ユニゲージユニケージユニゲージUSP友の会USP友の会UPS友の会UPS友の会 |
04 sed -r 's/(ユニ(ケ|ゲ)ージ|U(SP|PS)友の会)/&\n/g' |
05 sed '/^$/d' |
06 sort |
07 uniq -c
以下、コピペ実行用のワンライナーコード。
echo ユニゲージユニケージユニゲージUSP友の会USP友の会UPS友の会UPS友の会 | sed -r 's/(ユニ(ケ|ゲ)ージ|U(SP|PS)友の会)/&\n/g' | sed '/^$/d' | sort | uniq -c
問3
次の4個のファイルを作り、同じ内容のものを探しだすワンライナーを作成してください。 - echo 12345 > file1 - echo 23456 > file2 - echo 12345 > file3 - echo 45678 > file4
中身が同じファイルを探す問題。
awkの特殊変数FILENAMEを使って、ファイル名と中身を1レコードに出力すれば、判別できる。
01 #!/bin/sh
02
03 awk '{print FILENAME,$0}' file? |
04 sort -k 2,2n |
05 uniq -f 1 -D
別解。md5sumでハッシュ値を出力し、比較する。
こちらの方が、実践的なやり方かも。
01 #!/bin/sh
02
03 ls file? |
04 xargs md5sum |
05 sort -k 1,1 |
06 awk '{print $2,$1}' |
07 uniq -f 1 -D
以下、コピペ実行用のワンライナーコード。
awk '{print FILENAME,$0}' file? | sort -k 2,2n | uniq -f 1 -D
ls file? | xargs md5sum | sort -k 1,1 | awk '{print $2,$1}' | uniq -f 1 -D
問4
数字の列を偶数と奇数に分けてください。 ただし、偶数、奇数の列内での数字の並びを変えないように。 例 - 入力: 3 8 2 10 1 8 9 - 出力: 8 2 10 8 3 1 9
数字の順序を維持しつつ、偶数・奇数でグループ分けする問題。
とりあえず、まずは列行変換をしておく。
そして、元の並び順を復元できるように、行番号を付けて、偶数奇数に分ける。
後はソート、行番号の除去、行列変換をすればOK。
01 #!/bin/sh
02
03 echo 3 8 2 10 1 8 9 |
04 tr ' ' '\n' |
05 awk '{print $0%2, NR, $0}' |
06 sort -k 1,1n -k 2,2n |
07 awk '$0=$3' |
08 xargs
以下、コピペ実行用のワンライナーコード。
echo 3 8 2 10 1 8 9 | tr ' ' '\n' | awk '{print $0%2, NR, $0}' | sort -k 1,1n -k 2,2n | awk '$0=$3' | xargs
問5
次のように連続した0と1の数を数え、次のように変換してください。 - 0と1が連続したときは0 or 1の後ろに個数をつけてスペースを挿入 - 0と1が連続していないときはスペース区切りでそのまま出力 例 - 入力: 000001111111111001010 - 出力: 05 110 02 1 0 1 0
数値列を圧縮する問題。
grepの後方参照を利用して、連続する部分を抽出すれば、後は好きなように。
01 #!/bin/sh
02
03 echo 000001111111111001010 |
04 grep -o '\(.\)\1*' |
05 awk 'BEGIN{FS=""} 1<NF{print $1NF; next} 1' |
06 xargs
以下、コピペ実行用のワンライナーコード。
echo 000001111111111001010 | grep -o '\(.\)\1*' | awk 'BEGIN{FS=""} 1<NF{print $1NF; next} 1' | xargs
問6
数字の列を次のように整形してください。 - 小さい順に空白区切りで並んだ自然数に対し、2個以上の数字が連続した場合は両端の数字だけを残して間にハイフンを入れる。 例 - 入力: 1 2 3 5 6 8 10 11 12 15 - 出力: 1-3 5-6 8 10-12 15
シンプルそうで、少し厄介な問題。
awkで順番に走査し、前の値と連続しているかどうかを判別する。
その後、出力を整形していく。
01 #!/bin/sh
02
03 echo 1 2 3 5 6 8 10 11 12 15 |
04 awk 'BEGIN{ov=0} {for(i=1;i<=NF;i++){printf("%s%s", ($i-1==ov ? " " : "\n"), $i); ov=$i}}' |
05 awk 'NF==1{print $0} 2<=NF{print $1"-"$NF}' |
06 xargs
以下、コピペ実行用のワンライナーコード。
echo 1 2 3 5 6 8 10 11 12 15 | awk 'BEGIN{ov=0} {for(i=1;i<=NF;i++){printf("%s%s", ($i-1==ov ? " " : "\n"), $i); ov=$i}}' | awk 'NF==1{print $0} 2<=NF{print $1"-"$NF}' | xargs
問7
次の文字列は、数字3桁の安直なパスワードをMD5ハッシュしたものです。 パスワードを破ってください。 「250cf8b51c773f3f8dc8b4be867a9a02」 – 注意:MD5値を求めるときには改行記号を入れていません
MD5ハッシュから、元のパスワードを求める問題。
ただし、MD5ハッシュは、一方向関数(逆変換できない)なので、ハッシュ値から直接求めることは出来ない。
そこで、任意の数字3桁のMD5ハッシュ値を求め、問題のハッシュ値と一致するかどうかで、パスワードを推測する。
…のだけれど、全パターンを試すのは面倒臭いので、様々な文字列のMD5ハッシュ値が記録されているWebサイトを活用する。
01 #!/bin/sh
02
03 echo 250cf8b51c773f3f8dc8b4be867a9a02 |
04 xargs -I{md5sum} sh -c 'curl -s http://hashtoolkit.com/reverse-hash/?hash={md5sum} |w3m -T text/html -dump |grep {md5sum}'
横着せずに、全パターンのハッシュ値を出して、推測する方法。
01 #!/bin/sh
02
03 seq -w 1 999 |
04 tr '\n' ' ' |
05 sed 's/[0-9]\{3\}/echo -n & |md5sum;/g' |
06 sh |
07 awk '{printf("%03d %s\n", NR, $1)}' |
08 grep '250cf8b51c773f3f8dc8b4be867a9a02'
以下、コピペ実行用のワンライナーコード。
echo 250cf8b51c773f3f8dc8b4be867a9a02 | xargs -I{md5sum} sh -c 'curl -s http://hashtoolkit.com/reverse-hash/?hash={md5sum} |w3m -T text/html -dump |grep {md5sum}'
seq -w 1 999 | tr '\n' ' ' | sed 's/[0-9]\{3\}/echo -n & |md5sum;/g' | sh | awk '{printf("%03d %s\n", NR, $1)}' | grep '250cf8b51c773f3f8dc8b4be867a9a02'
問8
/usr/share/dict/words でしりとりを完成させてください - 8つ以上の単語を並べること
しりとりをする問題。
単純に、あるレコードの末尾1字が、次以降のレコードの末尾1字と一致するかどうかを見れば良い。
/usr/share/dict/wordsは元からソート済なので、shufを使って毎回ランダムに並び変えておく。
01 #!/bin/sh
02
03 cat /usr/share/dict/words |
04 shuf |
05 awk -vFS= '$1=$1' |
06 awk 'NR==1{print; onf=$NF} NR!=1 && $1==onf{print; onf=$NF}' |
07 tr -d ' '
以下、コピペ実行用のワンライナーコード。
cat /usr/share/dict/words | shuf | awk -vFS= '$1=$1' | awk 'NR==1{print; onf=$NF} NR!=1 && $1==onf{print; onf=$NF}' | tr -d ' '
参照Webサイト
https://hashtoolkit.com/
→ 問7で利用したサイト。様々な文字列のMD5やSHA1のハッシュ値が、DBとして記録されている。 → 悪い事に使っちゃダメ、絶対。
雑記
ごめんなさい、コメントのネタ切れです…w