「第21回シェル芸勉強会 大阪サテライト」にてsh行三昧の一日

はじめに

2月13日(土)に東京で開催された「jus共催、第3回今度はたぶん初心者向けに嘘はないでしょう午前のシェル勉強会/第21回未経験者大歓迎!誰でも働けるアットホームな職場ですシェル芸勉強会」の大阪サテライト「第21回シェル芸勉強会 大阪サテライト」に参加しました。

勉強会当日の様子ならびに問題・模範解答は次の通りです:


講義前半(正規表現の濃い話):

USP友の会 今泉光之(@bsdhack)さんによるスライド「正規表現」を視聴しました。

正規表現を書くときに基本正規表現と拡張正規表現を混同して書いてしまうことが多いので、このスライドのようなまとまった説明は非常に参考になります。

拡張正規表現で後方参照「\n」が使えないことを知ったのも収穫のうちの一つでした。

講義後半(シェルと猜疑心と好奇心):

続いて、USP友の会 鳥海秀一さんによる「シェルがコマンドを実行する前にしていること」を視聴しました。

ユーザが入力したコマンドライン文字列を、シェルがどのように解釈してコマンドに渡しているのかについて学ぶことができました。

次のような紛らわしい名前のファイルを作ったり、

$ mkdir test1 && cd test1
$ ls -a
.  ..
$ touch '. '
$ ls -a
.  .   ..

こっそりとbcコマンドの「*」演算子に細工をするのも楽しいかと思います。

$ mkdir test2 && cd test2
$ echo 2 * 3 | bc
6
$ touch +
$ echo 2 * 3 | bc
5

ほのかに危険シェル芸の香りが……。


A1(PDFからテキストを抽出):

ラベル印刷業という仕事柄、PDFの中身を確認する手段の一つとしてpdftotextを使うことがありますので、この問題だけは楽に解くことができました。

$ pdftotext -q -raw bba.pdf -
hoge.txt[2016/02/09 22:30:32]
群馬のシャブばばあ

抽出したテキストを標準出力に送るために、出力用ファイル名として「-」を指定しておきます。

A2(固定長データの漢字コード変換):

Shift_JIS(CP932)からUTF-8に変換するのですが、

$ cat anydata.cp932 | wc -c
214
$ cat anydata.cp932 | nkf -xw | wc -c
352

この例のようにデータサイズが変わってしまうので、必ずnkfの前にfoldを実行しておく必要があります。

$ cat anydata.cp932 | fold -b35 | nkf -xw
00000001ハナモゲギンコウ*******2144130511
00000002ハードバンク*********1144130188
00000003コドモギンコウ********2104130931
00000004ハタンギンコウ*********2344130008
00000005アンダーグラウンドギンコウ3314130900
00000006バミューダメンゼイギンコウ1234130981

あと、nkfの実行時に-xオプションを指定して、半角カナから全角カナへの自動変換をオフにしておく必要もあります。

A3(日曜日のみ抽出):

講義後半で紹介されていた(bashの)ブレース展開を利用してみました。

$ echo 2016{01..12}{01..31} | xargs -n1 date -d 2>/dev/null | grep '日曜' | sed 's/ 日曜.*$//'
2016年  1月  3日
2016年  1月 10日
2016年  1月 17日
(...中略...)
2016年 12月 11日
2016年 12月 18日
2016年 12月 25日

おおよその手順としては、

  1. 日付のような文字列を生成
  2. 有効な日付だけ残しつつ、曜日をチェック
  3. 整形

といった感じです。

なお、エラーメッセージは/dev/nullに捨てておきます。

A4(レコードの変更):

当日はsortにある2つのオプション

-m, –merge
  ソートされたファイルを併合する。ソート自体は行わない

-s, –stable
  前の比較結果に頼らない安定的な並び替えを行う

の使い方を知らなかったため、苦しまぎれにdiffで得た差分を利用して解答しました。

$ diff -u <(cat data) <(cat newdata) | tail -n+3 | sed 's/^./& /' | sort -k2,2n -k1,1r | getlast 2 2 - | sed 's/^[+-] //' | tail -n+2
001 あみだばばあ
002 *******
003 群馬のシャブばばあ
004 尾崎んちのババア
005 純愛ババア学園

途中、Tukubaiコマンドのgetlast <m> <n>で、第mフィールドから第nフィールドまでをキーフィールドにし、キーフィールドの値が同じであれば最後のレコードのみを抽出しています。

sortのオプション、-mならびに-sを使った解答です。

$ sort -ms -k1,1 data newdata | getlast 1 1
001 あみだばばあ
002 *******
003 群馬のシャブばばあ
004 尾崎んちのババア
005 純愛ババア学園

USP友の会 会長 上田隆一さんの著書『シェルプログラミング実用テクニック』(技術評論社の書籍案内ページ)の200〜205ページ「4.3.3 レコードを変更する」の冒頭部分を参考にしています。

ところで、「あみだばばあ」で『オレたちひょうきん族』(フジテレビ系列のお笑い番組)を思い出す人間は、今どれだけいるのだろうか……。

A5(シェルスクリプトのデバッグ):

まず、ファイルの中身が実際にどうなっているのかxxdで確認します。

$ xxd a.bash
0000000: efbb bf23 212f 6269 6e2f 6261 7368 0a0a  ...#!/bin/bash..
0000010: 6563 686f 2048 656c 6c0a                 echo Hell.

a.bashの先頭に、Unicodeテキストの符号化形式を判別するためのバイトオーダーマーク(BOM)が付いています。

$ xxd b.bash
0000000: 2321 2f62 696e 2f62 6173 680a 0a6c 7320  #!/bin/bash..ls
0000010: cb9c 2f0a                                ../.

b.bashでは、ホームディレクトリを示す「~」(16進数で0x7e)が「˜」(16進数で0xcb9c)になっています。

$ echo 'ls ~/' | xxd
0000000: 6c73 207e 2f0a                           ls ~/.
$ printf '%s' $'\x7e\n\xcb\x9c\n'
~
˜

sedでファイルの中身を掃除しますが、念の為-iオプションを指定して元のファイルを保存しておきます。

$ ls *.bash | xargs -I@ sed -i'*.orig' 's/˜/~/g;s/[^#!\/ \~\n[:alnum:]]//g' @
$ ls
a.bash  a.bash.orig  b.bash  b.bash.orig

それでは実行してみます。

$ ./a.bash
Hell
$ ./b.bash
BACKUP.SH  doom      junk  openbve        ダウンロード  ビデオ        公開
Git        dwhelper  lmms  recStudio.ini  テンプレート  プロジェクト
Perl       firefox   mame  rosegarden     デスクトップ  音楽
R          godot     mess  sketchbook     ドキュメント  画像

無事実行できました。

A6(EREからBREへの変換と量指定子の操作):

基本的にこの問題は、

  1. a+」を「aa*」に置き換える
  2. [0-9]+」を「[0-9][0-9]*」に置き換える
  3. h{5}」を「hhhhh」に置き換える
  4. (ho){10}」を「hohohohohohohohohoho」に置き換える

というふうに量指定子から必須要素をくくり出すものなのですが、当日は問題の意味を上手く捉えることができませんでした。

そういうわけで帰宅後、宿題として解いてみたのが以下の解答です。

$ cat extended | perl -nlE 's/\((.+)\)\{(\d+)\}/$1x$2/ge;s/(.)\{(\d+)\}/$1x$2/ge;s/(\[.+\]|.)\+/$1$1*/g;say'
aa*hhhhhhohohohohohohohohoho[0-9][0-9]*

Perlにはxという文字列の乗算(繰り返し)用の演算子がありますので、置換演算子(sの後ろに「/」で区切って正規表現と置き換え文字列を置いたもの)の末尾にeオプションを付けて、置き換え文字列をPerlコードとしてevalできるようにします。また、マッチ可能な部分を全て置き換えるためにgオプションを使います。

さらにコンパクトにまとめると以下のようになりました。

$ perl -nlE 's/(?:\((.+)\)|(.))\{(\d+)\}/($1||$2)x$3/ge;s/(\[.+\]|.)\+/$1$1*/g;say' extended
aa*hhhhhhohohohohohohohohoho[0-9][0-9]*

少しだけですが短くなりました。

A7(各段落の文字数を数える):

段落の最初にある全角スペースと段落内の改行を取り除けば、各段落の文字数を数えることができます。

$ cat text | sed -rz 's/\n/〓/g;s/〓 /\n /g;s/[〓 ]//g' | awk '{print length($1)}'
15
353
103

sedのオプション、-z

-z, –null-data
  NUL 文字で行を分割する

は新しめのバージョンでないと使えませんが、改行が絡んでくる正規表現を扱うときには非常に便利です。

A8(添付ファイルを抽出):

基本的には、各ファイルについて添付ファイルの部分を抜き出し、その部分をbase64 -dでデコードすればOKです。

$ cat -n 1350369599.Vfc03I4682c8M940114.remote | sed '65,665!d' | awk '{print $2}' | base64 -d > CHINJYU.JPG
$ cat -n 1350369599.Vfc03I4682c8M940114.remote | sed '672,77341!d' | awk '{print $2}' | base64 -d > IMG_0965.JPG

ちなみに、mpackをインストールしてmunpackコマンドを使えば一撃で抽出できます(例はDebian系ディストリビューションの場合)。

$ sudo apt-get install mpack
$ munpack 1350369599.Vfc03I4682c8M940114.remote
CHINJYU.JPG (image/jpeg)
IMG_0965.JPG (image/jpeg)

面倒くさがり屋には、ぴったりなコマンドだと思います。


ライトニングトーク(深く、そして濃く):

勉強会後のビアバッシュでは、以下の3つのネタが披露されました。

  1. cdコマンドを読んでみよう(@kunst1080さん)
  2. ExSQell = Excel + SQL + Shell(@nmrmsysさん)
  3. systemd-nspawnについて実演(@nogiro_iotaさん)

マ、マニアックすぎる(特に2つめの「ExSQell」が)……。

普段使っているツールについて、目に見える部分だけでなく内側もきちんと知っておくことの大切さを感じたビアバッシュのひとときでした。


おわりに

今回気づいたこと:

  1. 普段の仕事に救われることもある
  2. sort -sによる「安定ソート」の意味を知る
  3. あいかわらず正規表現に弱い
  4. 長時間の勉強会では体力も重要
  5. 今回も「初心者向け」はたぶん嘘

最後に、上田会長ならびに大阪サテライト主催のくんすと氏をはじめとする参加者の皆さん、当日はお疲れ様でした。

Written on February 29, 2016