2016年5月1日

第22回シェル芸勉強会参加報告

2016-04-30(土)に開催された、第22回シェル芸勉強会(正式名称:jus共催、第4回初心者向け詐称疑惑午前のシェル勉強会/第22回ゴールデンウィークの存在疑惑シェル芸勉強会)に参加しました。
ただ、在住地の関係で直接会場へ伺うことは出来なかったため、Youtubeで配信してくださったライブ中継を利用して、遠隔で参加しました。

勉強会は、午前と午後の、2部構成でした。
午前は、シェルに関する勉強会で、コマンドライン処理関連と、OSが使用しているシェルスクリプトを読んでみるという内容でした。
午後は、シェル芸勉強会で、シェル芸力を付けるための問題を解きました。

以下、勉強会の詳細を記します。


実行環境

  • Arch Linux (x86_64, 4.5.1-1-ARCH)

  • zsh (5.2)

  • bash (4.3.42)

  • mksh (52.2)

  • GNU coreutils (8.25)

  • gawk (4.1.3)

  • usp Personal Tukubai (20160402)

シェルに関する勉強会

コマンドライン処理(仮)

USP友の会 鳥海 秀一さんによる、 初心者向け のコマンドラインの仕組み・利用方法を学ぶ内容でした。
内容としては、前回の第3回シェルに関する勉強会の続きで、シェルの内部動作に関する解説等が主でした。

前回の続きで、 初心者向け であるはずでしたが、やっぱり変態的な内容が…​w
一番の目玉(?)は、evalの詳細な動作と、高度な利用方法でした。
正直、何を言っているのか分からなかっ(ry
また、今回もやっぱり新たな危険シェル芸が爆誕しました!!

/etc/rc を読んでみる

日本UNIXユーザ会/USP友の会 龍池 哲也さんによる、OSに付属されているシェルスクリプトを読んで、使い方を学ぶ内容でした。

FreeBSDの/etc/rc/以下にあるシェルスクリプトを参考にして、作法を学んでみましょうという紹介でした。
その他にも、manページや、OSのドキュメント等を読むと、非常に勉強になるとのことでした。

このOS付属のシェルスクリプトを読んでみるというのは、*BSD業界で著名な後藤大地さんも推奨している勉強法です。
(スマートな紳士のためのシェルスクリプト(3):OSに付属するシェルスクリプトを読んで技術を盗む)
(けれど、OSによっては、スクリプトが汚くて逆に参考にならない事もあるとか…​ R*ELとか。)

第22回シェル芸勉強会

復習も兼ねて、コードにコメントを付加したうえで掲載。

問題文および解答は、以下のURLから。
https://blog.ueda.asia/?p=8028

問1

解答。実はコードはどっちも同じ。

#!/bin/sh

cat a \
| # ファイルの出力
sort -n \
| # 各行を数値順にソート
xargs \
| # 行列変換
awk 'NF%2 != 0 ? $0=$(NF/2+1) : $0=($(NF/2)+$(NF/2+1))/2'
  # 中央値の出力
#!/bin/sh

cat b \
| # ファイルの出力
sort -n \
| # 各行を数値順にソート
xargs \
| # 行列変換
awk 'NF%2 != 0 ? $0=$(NF/2+1) : $0=($(NF/2)+$(NF/2+1))/2'
  # 中央値の出力

データの中央値を求める問題。
ソートおよび行列変換した後、awkで中央値を算出。
数値の個数が奇数・偶数によって計算方法が変わるので、三項演算子で。

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

cat a | sort -n | xargs | awk 'NF%2 != 0 ? $0=$(NF/2+1) : $0=($(NF/2)+$(NF/2+1))/2'

問2

解答。

#!/bin/sh

echo カレーライス 醤油ラーメン \
| # 文字列の出力
awk '$0=$2" "$1' \
| # 1,2フィールドの入替え
tr ' ' '\n' \
| # フィールドセパレータを改行に変換
sed 's/.*/echo &/' \
| # コマンドの生成_1(元の文字列の出力)
sed '2s/.*/&\ | grep -o \./' \
| # コマンドの生成_2(2行目の列行変換)
sh \
| # コマンドの実行
gawk '/.+ー/ && sp=index($0,"ー"); !/.+ー/{ORS=""; for(i=1;i<sp;i++){print " "}; print $0"\n"}' \
| # 'ー'の位置に合うように全角スペースを付加
sed '1{h;d}; /ー$/g'
  # 文字列のクロス

文字列を特定位置でクロスさせる問題。
特定フィールドのみを列行変換する必要があったので、コマンドを生成してshに食わせることにした。
意外と複雑…​

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

echo カレーライス 醤油ラーメン | awk '$0=$2" "$1' | tr ' ' '\n' | sed 's/.*/echo &/' | sed '2s/.*/&\ | grep -o \./' | sh | gawk '/.+ー/ && sp=index($0,"ー"); !/.+ー/{ORS=""; for(i=1;i<sp;i++){print " "}; print $0"\n"}' | sed '1{h;d}; /ー$/g'

問3

解答。

#!/bin/sh

cat Q3 \
| # ファイルの出力
awk '$0=$0" "NR' \
| # 2フィールド目に行番号を付加
sort -k 1,1 \
| # 1フィールド目をキーにしてソート
awk 'BEGIN{os="";ORS=""} {s=$1; print s==os ? " "$2 : "\n"$0; os=s}' \
| # 1フィールド目をキーにして、2フィールド目を集計
sed '/^$/d;$s/$/\n/'
  # 出力の整形

usp Tukubaiを使ったやつ。すっごくシンプル!

#!/bin/sh

cat Q3 \
| # ファイルの出力
juni \
| # 1フィールド目に行番号の付加
sort -k 2,2 \
| # 2フィールド目をキーにしてソート
self 2 1 \
| # 1,2フィールドを入替え
yarr num=1
  # 1フィールド目をキーとして、同一のレコードを横に展開

戻す方。

#!/bin/sh

cat Q3.ans \
| # ファイルの出力
awk '{for(i=2;i<=NF;i++){print $1,$i}}' \
| # 各行を(フィールド数-1)分出力
sort -k 2,2n \
| # 2フィールド目をキーにして数値ソート
awk '$0=$1'
  # 余分な2フィールド目の削除

usp Tukubaiを使った戻す方。こちらもシンプル!

#!/bin/sh

cat Q3.ans \
| # ファイルの出力
tarr num=1 \
| #$
awk 'NF==2' \
| # フィールド数が2である行の抽出
sort -k 2,2n \
| # 2フィールド目をキーとして数値ソート
delf 2
  # 2フィールド目を削除

キー毎にレコード数を集計する問題。
せっかくPersonal Tukubaiを契約したのに、勉強会中では使っていなかったorz
しかも、awkを使う方法でも、連想配列を全然使っていなかったり…​
せっかくなので、記事執筆にあたり、Tukubai版も追加。
こういった集計作業は、Tukubaiコマンドを使うとすっごくシンプル!

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

cat Q3 | awk '$0=$0" "NR' | sort -k 1,1 | awk 'BEGIN{os="";ORS=""} {s=$1; print s==os ? " "$2 : "\n"$0; os=s}' | sed '/^$/d;$s/$/\n/'

Tukubai版。

cat Q3 | juni | sort -k 2,2 | self 2 1 | yarr num=1

戻す方。

cat Q3.ans | awk '{for(i=2;i<=NF;i++){print $1,$i}}' | sort -k 2,2n | awk '$0=$1'

戻す方、Tukubai版。

cat Q3.ans | tarr num=1 | awk 'NF==2' | sort -k 2,2n | delf 2

問4

解答。

#!/bin/sh

cat Q4 \
| # ファイルの出力
nl \
| # 1フィールド目に行番号の付加
awk '{print "factor",$1," | awk '\''NF==2{ORS=\"\"; print $2\" \"}'\''; ","echo",$2}' \
| # コマンドの生成(行数が素数の行の抽出、および2フィールド目をそのまま出力)
sh \
| # コマンドの実行
awk 'NF==2 && $0=$2' \
| # 素数(フィールド数が2の行)の抽出
sort \
| # 文字のソート
uniq -c
  # 各項目数のカウント

素数行目のみを出力し、更にキー毎に集計する問題。
またまたややこしいコードになってしまったけれど、pasteコマンドを使えばもっとシンプルになるらしい。
うーん、まだまだ修行が足りない…​

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

cat Q4 | nl | awk '{print "factor",$1," | awk '\''NF==2{ORS=\"\"; print $2\" \"}'\''; ","echo",$2}' | sh | awk 'NF==2 && $0=$2' | sort | uniq -c

問5

解答。

#!/bin/sh

cat Q5 \
| # ファイルの出力
awk '{ORS=""; for(i=1;i<=NF;i++){for(j=i;j<=NF;j++){print $j" "}; print "\n"}}' \
| # フィールドを先頭から減らしつつ、フィールド数の分だけ行数を複製
awk '{s=0; for(i=1;i<=NF;i++){s+=$i; if(s==10){print i,$0}}}' \
| # 和が10となる可能性のある行の抽出
awk '{ORS=""; for(i=1;i<=$1;i++){print $(i+1)" "}; print "\n"}'
  # 和が10となる組み合わせの出力

数列から和が10になる組み合わせを列挙する問題。
組み合わせを全て列挙した後、和が10になる組み合わせを抽出しようと考えたのだけれど…​
予想以上に面倒なことになりそうだったので、純粋に10になる組み合わせを探すことに。
てか、awkしか使ってないぞw

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

cat Q5 | awk '{ORS=""; for(i=1;i<=NF;i++){for(j=i;j<=NF;j++){print $j" "}; print "\n"}}' | awk '{s=0; for(i=1;i<=NF;i++){s+=$i; if(s==10){print i,$0}}}' | awk '{ORS=""; for(i=1;i<=$1;i++){print $(i+1)" "}; print "\n"}'

問6

時間中の解答(実は間違いorz)。

#!/bin/sh

cat Q6_2 Q6_1 \
| # 置換用ファイル \n 文章ファイル の順に出力
awk '$1 ~ /^(X|Y|Z)$/{str[$1]=$2; next} {gsub(/(X|Y|Z)/, str[&]); print $0}'
  # X,Y,Zに対応する文字を連想配列に格納し、文章中の該当部分に当てはめる...つもりだったけど失敗><

終了後に解いた正答。

#!/bin/bash

sed -f <( \
    cat Q6_2 \
    | # 当てはめるファイルの出力
    tr ' ' '/' \
    | # スペースをスラッシュに変換
    sed 's;^;s/;' \
    | # sedスクリプトの出力_1
    sed 's#$#/#' \
      # sedスクリプトの出力_2
) Q6_1
# sedスクリプト(文字列の当てはめ)の実行

ファイルに特定文字を当てはめる問題。
この問題は、時間中に解けなかったorz
awkの正規表現では、マッチした文字列を参照する'&'が利用できなかった事が原因…​
また、sedにファイルをsedスクリプトとして入力する、-fオプションの存在も知らなかった。
sedスクリプトを生成して、-fオプションで食わせる事ができるなんて…​!!
…​なので、復習も兼ねて再回答をば。

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

sed -f <(cat Q6_2 | tr ' ' '/' | sed 's;^;s/;' | sed 's#$#/#') Q6_1

問7

解答、その1。

#!/bin/sh

alias eval='eval eval'
eval echo hoge

解答、その2。

#!/bin/sh

exec <非対話型の任意のコマンド>

端末やシェルを明示的に終了させるコマンドを使わずに、端末を閉じる問題。
午前中の勉強会でシェルを強制終了させる危険シェル芸が考案されたので、さっそく勉強成果を試してみた♥
…​だけだとちょっと面白くないので、execを利用した別解も。
execでコマンドを用いることで、プロセスは子プロセスへとforkされず、親プロセス同じPIDで実行される。
その結果、プロセスが終了した場合、親プロセスであるシェルに戻らず、そのままシェル・端末が終了することになる。
この性質を利用して終了させるという事。

これは、シェルを切り替えたい際にも有効だったりする。
例えば、 $ exec bash -l みたいな感じで。

以下、コピペ実行用のコード。

alias eval='eval eval'
eval echo hoge
exec sl

問8

解答。

#!/bin/bash

cat \
<( \
    cat Q8.cc \
    |
    sed -n '1,/^$/p' \
) \
<( \
    cat Q8.cc \
    |
    grep '^[[:graph:]]\{1,\} [[:graph:]]\{1,\}([[:print:]]*)$' \
    |
    grep -v ' main(' \
    |
    sed 's/$/;/' \
) \
<( \
    cat Q8.cc \
    |
    sed -n '/^$/,$p' \
)

俺々シェル芸フォーマットだと少し分かりにくいので、ワンライナー版も。

cat <(cat Q8.cc | sed -n '1,/^$/p') <(cat Q8.cc | grep '^[[:graph:]]\{1,\} [[:graph:]]\{1,\}([[:print:]]*)$' | grep -v ' main(' | sed 's/$/;/') <(cat Q8.cc | sed -n '/^$/,$p')

C++のコードに、関数プロトタイプ宣言を付加する問題。
この問題も、時間内に終わらなかった…​orz
決め打ちには出来るだけ頼りたくなかったので、関数を抽出し、ヘッダの直後に挿入するようにしてみた。
具体的には、ファイルを3分割(ヘッダ部分、プロトタイプ宣言部分、本体部分)にして、それぞれを結合するという方法。
んが、プロセス置換ばかりに頼ってしまい、予想以上に分かりにくいコードに…​ なんてこったい。

雑記

今回も遠隔での参加。
全体的に、なかなか難易度の高い問題ばかりでした…​

また、せっかくusp Personal Tukubaiを契約したのに、全然活かせていなかったり…​
問題としては、Tukubaiコマンドと相性が良いものもあったので、とても勿体無い事になってしまったorz
もちろん、便利なTukubaiコマンドばかりに頼るのも宜しくないけれど、せっかくの有益なツールをもっと活用できるようにしたいです。

今回は、どの会場も参加者が多く、非常に盛り上がっていたとの事です!
LTでは、非常に面白い(そして変態的な)発表も多くあったとのことで、是非とも見たかった…​!!
とはいえ、遠隔でも参加できるだけでも、本当にありがたいです。
(自宅参加組としては、ライブ配信が頼みの綱だったりするので…​)
勉強会を開催してくださった、上田会長をはじめとした日本UNIXユーザ会とUSP友の会の皆様、ありがとうございました!!

🐺。oO(名古屋でもサテライト会場ができないかな…​ 誰もやらないなら僕がやるしかないか…​…​??)

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