日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

「第31回シェル芸勉強会:福岡サテライト」レポート

前回に引き続き鬼の忙しさが続いていましたが、忙しさに若干の切れ目が出来たのでシェル芸勉強会の福岡サテライトを開催いたしました。会場は今回もAIP Cafeです。問題出題と解説の上田さん、ありがとうございます。参加者の皆様もお疲れ様でした。

午前中は会社の会議の後、昼からはシェル芸地獄というなかなかハードな一日でした。10月になったけど昼はまだまだ暑いです。福岡サテライトでは総勢4人の参加者が集いました。久しぶりの参加の方もいらっしゃいました。

勉強会の情報

勉強会主催者上田さんが公開されているリンク集をご覧ください。

b.ueda.tech

午後の部

今回は午後の部のみ。今回もイントロのスライドとか準備出来ませんでした。 せっかくなので持ってきたSoftware Design 9月号と10月号の宣伝をさせていただきました。

問題が難しい場合は関連情報の解説や補足を重視して、今回も福岡サテライトなりのペースで進めていく所存です。

Q1

行をまたいだ置換の問題。工夫すればsedで出来そうだが難しい・・・ 解答例を後から見ることにして、福岡では補足や解説が重要なのでsedの後方参照やyコマンド、拡張正規表現などの説明をしました。 後方参照出来る数は9個までだよとか。

# こんな感じで\( \)で囲った部分を\1\2で順番に参照出来るよ
$ echo 123-45 | sed 's/^\(...\)-\(..\)/\1\2/'
12345

# GNU sedの-rオプションで拡張正規表現が使える。参照したい部分の囲みにバックスラッシュが不要。
$ echo 123-45 | gsed -r 's/^(...)-(..)/\1\2/'
12345

# yコマンドで変換だぞ
$ echo 12345 | sed 'y/12345/abcde/'
abcde
$ echo 12345 | sed 's/12345/a/'
a

# trコマンドでも出来る。sedより高速だが動きが違う部分も。
$ echo 12345 | tr '12345' 'abcde'
abcde
$ echo 12345 | tr 12345 a
aaaaa

出題者上田さんの解答例では、Perl正規表現のルックアラウンドアサーションを使っていました。復習せにゃ、、 Perlの正規表現はPCREとしてライブラリで提供されており、PHPやApache HTTP Serverなどでも利用可能だよとか補足解説しました。

PCRE - Perl Compatible Regular Expressions

Q2

上の行に合わせたインデントをする。Vimのインデントコマンド=の出番?と思いましたが、出来ませんでした。

Vimでやっている人はいらっしゃいました。

ちなみにJavaScriopt(node)で解いた方もいらっしゃって、私には理解が難しく。

Q3

問題の図形を見て素数がすぐに思いつく方々、、、 シェル芸界隈では定番になってきていますが、福岡サテライトではまだあまり知られていないので、素数列挙の定番ワンライナーを解説しました。

$ seq 1 20 | gfactor | awk 'NF==2{print $2}'
2
3
5
7
11
13
17
19

後はawkで頑張ればなんとかなりそうです。

Q4

福岡サテライトの参加者は、ASCIIコードやバイナリデータの扱いに慣れていない方が多かったので補足解説しました。

# まずはマニュアルを見て見る
$ man ascii

# odコマンドでASCIIコードを16進数表記
$ echo -n ABCabc | od -t x1c
0000000    41  42  43  61  62  63                                        
           A   B   C   a   b   c                                        
0000006

# BSD版だとマルチバイトに対応してるよ。
$ echo -n おはようございます | od -t x1c
0000000    e3  81  8a  e3  81  af  e3  82  88  e3  81  86  e3  81  94  e3
          お  **  **  は  **  **  よ  **  **  う  **  **  ご  **  **  ざ
0000020    81  96  e3  81  84  e3  81  be  e3  81  99                    
          **  **  い  **  **  ま  **  **  す  **  **                    
0000033

# vim付属のxxdコマンドで16進数表記。
$ echo -n おはようございます | xxd
00000000: e381 8ae3 81af e382 88e3 8186 e381 94e3  ................
00000010: 8196 e381 84e3 81be e381 99              ...........

# -uオプションで10進数表記
$ echo -n おはようございます | od -t u1c
0000000   227 129 138 227 129 175 227 130 136 227 129 134 227 129 148 227
          お  **  **  は  **  **  よ  **  **  う  **  **  ご  **  **  ざ
0000020   129 150 227 129 132 227 129 190 227 129 153                    
          **  **  い  **  **  ま  **  **  す  **  **                    
0000033

# -bオプションで2進数表記。ビット列が見えるよ。
$ echo おはようございます | xxd -b
00000000: 11100011 10000001 10001010 11100011 10000001 10101111  ......
00000006: 11100011 10000010 10001000 11100011 10000001 10000110  ......
0000000c: 11100011 10000001 10010100 11100011 10000001 10010110  ......
00000012: 11100011 10000001 10000100 11100011 10000001 10111110  ......
00000018: 11100011 10000001 10011001 00001010                    ....

Q5

Q4でのASCIIコードやバイナリデータに解説してたら、時間が無くなっちゃいました。 解答例に出てきたプロセス置換について解説をしました。

# 2つのコマンドの出力結果をdiffで比較
1d0
< 1
3a3
> 4

シェルの本質的な機能は、コマンドの実行とプロセス管理、ファイルをベースとしたデータ入出力の管理、だと思いますといった話をしました。 補足として/dev/urandom/procファイル、bashのtcp通信機能など、ファイルをベースとした機能も解説しました。

# 乱数取得のワンライナー。trの-dcオプションについても補足解説。
$ cat /dev/urandom | LANG=C tr -dc '0-9' | fold -w 10 | head
5185330642
4941724968
8040182629
4953975155
7776003351
2268161449
3516241602
2661394339
7045619931
7999489264

Q6

UTF-8な日本語ひらがなを小文字に変換する問題。福岡サテライトの参加者から文字コードをズラせばいいんじゃない?という意見が出ました。 じゃあ確認してみましょう。

$ echo -n あぁ | xxd -p | fold -w 6
e38182
e38181

ということで、3バイト毎にまとめてコード番号を1引けば良さそう。ということで考えた解答例がこちらです。ご意見ありがとうございました。

$ echo -n あいうえお | xxd -p -u | fold -w 6 | sed 's/$/ - 1/' | (echo 'obase=10;ibase=16'; cat) | bc | (echo 'obase=16'; cat) | bc | xxd -p -r
ぁぃぅぇぉ

bcコマンドについては知られていなかったので補足解説しておきました。 bashbc自体の履歴機能(readline)が使えるので、繰り返し計算する時の電卓替わりにも便利だったりします。

Q7

アニメーションを作る問題。ギブアップでした。

そもそも端末でアニメーションを作るってどういう意味?という疑問が出たので、下記のような事例の解説をしました。 例えば1秒毎に@が一つずつ伸びていくにはこんな感じとか。

# まずはコマンド列を作成する
$ seq 3 | awk '{for(i=1;i<$1;i++){printf "@"}print ""}' | sed 's/^/clear; echo /' | sed 's/$/; sleep 1;/'
clear; echo ; sleep 1;
clear; echo @; sleep 1;
clear; echo @@; sleep 1;

# 上記のコマンドをシェルに流し込んで実行。
$ seq 3 | awk '{for(i=1;i<$1;i++){printf "@"}print ""}' | sed 's/^/clear; echo /' | sed 's/$/; sleep 1;/' | sh

Q8

全角5文字以内で折り返す問題。ギブアップでした・・・

Q9

スクレイピングの問題。CSVにするのは諦め元素記号だけを抽出するものを作りました、敗北感。

$ curl -s http://www.gadgety.net/shin/trivia/ptable/ | sed -n '/<tr /,/<\/tr>/p' | grep font | grep '<a href="' | grep -o '<a href="[^"][^"]*\.html">[^<>][^<>]*</a>' |  sed 's/<[^<>]*>//g'
H
He
Li
Be
(省略)
Fm
Md
No
Lr

午後の部終了後

スライドの準備などはしていませんでしたが、参加者の方でxargsコマンドの使い方を色々悩んでた方がいらっしゃいました。 改めてxargsコマンドについて基本から解説させていただきました。

xargsについて

配列的なデータを扱う

  • 配列的な文字列データに対して、コマンド処理をまとめて実行する時に使う
  • 区切り文字はスペース、タブ、改行、EOF

使い方の基本例です。

$ find dir -type f | xargs file

$ echo {1..5} | xargs printf '%05d\n'
00001
00002
00003
00004
00005

コマンドを指定しない場合は、暗黙的にechoを実行します。

$ seq 10 | xargs -n 2
1 2
3 4
5 6
7 8
9 10

どんな処理をするコマンドか?

xargsはHaskellやRuby、JavaScriptのmapに似ており、配列的なデータに対して繰り返し同じ処理を施します。 配列内の数字を2倍にする処理がこちらです。

# xargsコマンド
$ seq 1 5 | xargs -I@ expr @ \* 2
2
4
6
8
10

# Ruby
$ pry
[1] pry(main)> [1,2,3,4,5].map do |i| i*2 end
=> [2, 4, 6, 8, 10]

# JavaScript
$ node
> [1,2,3,4,5].map((n)=>{return n*2})
[ 2, 4, 6, 8, 10 ]

# PHP
$ psysh
>>> array_map(function($num){return $num*2;},[1,2,3,4,5])
=> [
     2,
     4,
     6,
     8,
     10,
   ]

オプション

  • -nオプション
    一度に処理する数を指定します。

  • -Iオプション
    通常はコマンド列を最後に渡しますが、埋め込む文字列の指定が可能です。

$ seq 5 | xargs -I@ gdate -d '@ day' +%F
2017-10-08
2017-10-09
2017-10-10
2017-10-11
2017-10-12
  • -0オプション
    区切りをヌル文字\0にします。ディレクトリ名やファイル名に空白などがある場合に便利です。
$ find 'VirtualBox VMs' -print0 | xargs -0 file

複数のコマンドを実行するには?

複数のコマンドやパイプなどは実行出来ませんが、shbash-cオプションを使うことで実現可能です。 1秒毎に1カウントアップするワンライナーがこちらです。

$ seq 10 | xargs -I@ sh -c 'echo @; sleep 1;'
1
2
(省略)
9
10
  • -Pオプション
    並列処理が可能です。「真・マイナンバーシェル芸」の記事を紹介しました。

ImageMagickとの実用的な組み合わせで、画像ファイルを再帰的に選択してフォーマットの変換やサムネイル作成の処理をするワンライナーを紹介しました。

# PNG=>JPGへフォーマット変換
$ find * -type f -name '*.png' | sed 's/\.png//' | xargs -I@ convert @.png @.jpg

# 長辺が100x100ピクセル以内のサムネイル画像を作成。ファイル名に-sを追加。
$ find * -type f -name '*.png' | sed 's/\.png//' | xargs -I@ convert -geometry 100x100 @.png @-s.png

他にはcurlコマンドでデカイデータをPOSTする際にコマンドライン長制限の問題が出て困った、という話題が上がりました。 これについてはデータをファイルから読んで出来るのでは?と予測。

curlのマニュアルを見るとファイル名に@を付けて指定すれば良さそうです。

今回も参加者の方々お疲れ様でした。