読者です 読者をやめる 読者になる 読者になる

hkoba blog

プログラマーです。プログラミング言語ミーハーです。よろしくどうぞ(能代口調)

zparseopts で posix style long option と一文字オプションを両立させる

備忘録 zsh

Zshzparseopts を使って

  • -t TYPE
  • --type TYPE
  • --type=TYPE

全部の書き方をサポートしたい場合にどう書くか。 (代入先の配列名を同じにするだけ、だけど)

値の取り出しの書き方を忘れがちなので、メモ。

(我流なので、ツッコミ歓迎です)

zparseopts がどう働くか

% set -- -t foo
% zparseopts -D -K h=o_help t:=o_type
% typeset o_type
o_type=( -t foo )
%
% set -- --type foobar
% zparseopts -D -K h=o_help t:=o_type -type:=o_type
% typeset o_type
o_type=( --type foobar )
%
% set -- --type=foobar
% zparseopts -D -K h=o_help t:=o_type -type:=o_type
% typeset o_type
o_type=( --type '=foobar' )
%

オプションの検査

配列のサイズを見るのが、良いのではないかと…

if (($#o_type)); then
   # …オプションが指定されている時の処理…
fi

値の取り出し

値の先頭が = で始まる =なんとか みたいな文字列を渡すことを諦めれば ↓この書き方で行ける。

% print -- $o_type[2]
=foobar
% print -- ${${o_type[2]}#=}
foobar
%

.oO(…もっと短く覚えやすい書き方があれば、是非教えて下さい…)

YAP(achimon)C::Asia Hachioji 2016 にボランティア参加してきました

yapcasia8oji-2016mid.hachiojipm.org

最初は予定が合わなくて諦めてたけれど、自分の予定が変わったタイミングで ボランティア足りないって話を聞いたので、思い切ってお手伝いさせてもらいました。

担当は A部屋の第3の司会(交代制)でした。とはいえ喉の調子が悪かったので、 二日目は部屋内遊撃みたいな役目にさせてもらってました。

(…失礼がなかったか、内心ドキドキしています…)

全体感想

開催中にスタッフのある人が『yapc は良いよな、ここで笑いが出るもの…他だと笑ってくんないんだぜ…(意訳)』 と言っていたのが、私にも同感で…この陽気さ・笑いで解決する文化というか、血脈みたいなものこそが、 yapc が培ってきてここに受け継がれたものなんだろうな、などと思いました。

それにしてもよい会場でした… Microsoft さん、ありがとうございました。

トーク感想

どのトークも面白かったのですが、全部は書けそうにないので、3つだけ…発表順で。

pastak さん

speakerdeck.com

Browser 拡張は私もいつか再挑戦したくて…現在の状況がよく分かって、とても有益でした。

ytnobodyさん

http://blog.ytnobody.net/entry/2016-07-04-10-33-02.htmlblog.ytnobody.net

チーム作りと、経営層の巻き込み方の所が、凄いなぁと脱帽しました。 (野村再生工場を連想したり…)

どの項目もサラッと書いてましたけど、実行するのは大変だったはず…

しんぺい a.k.a. 猫型蓄音機さん

speakerdeck.com

MVC 的なものづくり、私も悩んできたし、悩みすぎて テンプレートエンジン+Webフレームワークを作っちゃう タイプなので、 非常に勉強になりました。 『Presentation とそれ以外を分けることこそが肝心なんだ!(意訳)』 って主張とか、 『Model の小分けの仕方はケースバイケース!(意訳)』 って話は、とても納得が行きました。 (とはいえ私の書いてるコードがしんぺいさんの考える適切さの範囲に該当するのかは、なんともですが…><)

最後に

楽しかった!

とある方のブログでみたスクリプトが、なぜ暴走したのかについて

perl

Twitter で流れてきたリンクで、Perl を勉強中の方のブログが目に止まりました。

note103.hateblo.jp

読んでいて気付いた点があったので、それについて書いてみます。 (最初はコメントで書いていたら文字数制限で途切れてしまったので… 途切れるなら字数制限の警告が欲しい…)

id:note103 様、はじめまして

id:note103 様、はじめまして、興味深く読ませて頂きました。

上記の現象のうち、 Perl 関連の部分について、多分こうではないか?と気付いた所があるので書かせて下さい。

なぜこのスクリプトは暴走したのか

第一に、 Sample.pm の中に間違って init(); が書かれた状態で何が起きるかです。 この場合、 単に use や require で他のスクリプトから読み込んだだけで Sample::init() の実行が始まる ことになります。 このことは以下のようにして確認できます。

perl -e 'require Sample'

第二に、状況からの推測ですが、 MacVim の syntastic (perl の構文間違いの自動検査) が呼び出した perl -wc main.pl の中で Sample.pm が読み込まれるので、そこで Sample::init() が呼ばれて入力待ちが発生。 syntastic が応答しない→ MacVim も固まる、となったのではないかと。

ここから先は、ご自身で書いておられる通り、 "具体的には最後のelse文の中で同じサブルーチンを呼び出しているのが悪手" となり、無限に再帰呼出しを行う状況だったのではないかと。

試しに

perl -we 'use Sample' </dev/null

と端末で打ってみると、無限ループが再現できるはずです。 (端末からなら ^C で停止させることが出来ます。)

あと、この Sample::init で使っている

chomp($bar = <STDIN>);

は、perl の普通の入力待ちループ

while ($bar = <STDIN>) {...}

と違って defined 検査が足りていないため、 eof が来ても停止しない、という点も、無限ループになった要因の一つですね。

ではこの辺りで失礼します〜

実験: SQLite の `in (...)` 句を *雑に* zsh の配列展開で生成してみる

zsh sqlite

はじめに

  • ツッコミ歓迎です。
  • 対象DB は SQLite です。
  • あくまで入力データの特性を完全に把握しコントロール出来る場合しかおすすめしません…

値に single quote (') が入らない保証が有る場合

% values=(
  foo
  bar
  baz
)
% print " in (${(j/,/)"${(@qq)values}"})"
 in ('foo','bar','baz')
% 

こっちは割と、仕事で使ってます。

解説

  • ここでは配列変数 values に値を格納
  • @qq は配列の要素ごとに qq (single quote で囲む) を適用
    • それを一旦 " " で囲むことで、 "$@" と同じ効果を出す
  • j/,/ は配列を , で join.

値に single quote も入る可能性が有る場合

qq フラグで自動 quote させる場合、文字列中の single quote は '\'' という文字列に置換される

% foo="Foo's Bar"
% print -r ${(qq)foo}
'Foo'\''s Bar'
%  

これを '' に置き換えれば行けるのではないか…

% values=(foo bar "Foo's Bar")
% print -r "in (${${(j/, /)${(@qq)values}}//'\''/''})"
in ('foo', 'bar', 'Foo''s Bar')
%

出来た気がする…

[linux]作業メモ:luks で暗号化した EFI ベースの notepc で HDD を換装した際の作業記録

fedora linux

単なる自分メモ。

  • ディストリは Fedora Linux
  • 起動モードは EFI
  • 作業環境は root の zsh

1. GParted で GPT で partitioning

  • EFI System Parition を作る。 (sdb1)
    • サイズは 384MB にしてみた
    • format 実行するまでは boot フラグが立てられなかった? 気のせい?
    • フラグを立てる画面に esp フラグもあるので、これを立てる
  • /boot も作る (sdb2)
  • 最後の一つを unformated で確保 (sdb3)

2. /boot/efi をベタコピー

mount /dev/sdb1 /mnt/tmp
cp -va /boot/efi/* /mnt/tmp
umount /mnt/tmp

3. /boot をベタコピー

mount /dev/sdb2 /mnt/tmp
rsync -av -x -A -X /boot/ /mnt/tmp
umount
  • pax -r -w -X -pe だと何かエラー出た。 selinux?

4. luks

cryptsetup luksFormat /dev/sdb3
cryptsetup luksOpen /dev/sdb3 newsecure
pvcreate /dev/mapper/newsecure
vgcreate vghk16 /dev/mapper/newsecure
vgchange -ay vghk16

5. (今回は lv の統合も行う)

mkdir /mnt/srcimg
lv=/dev/vghk15/root
snap=snap-$lv:t
lvcreate --snapshot --name $snap -L2G $lv
mount -r /dev/vghk15/$snap /mnt/srcimg
function lvsnap {lv=$1; snap=snap-$lv:t; lvcreate --snapshot --name $snap -L2G $lv}
lvsnap /dev/vghk15/var
mount -r /dev/vghk15/$snap /mnt/srcimg/var
lvsnap /dev/vghk15/libvirt_images
mount -r /dev/vghk15/$snap /mnt/srcimg/var/lib/libvirt/images
mkdir /mnt/destimg
mkfs.ext4 -m 1 /dev/vghk16/root
mount /dev/vghk16/root /mnt/destimg
lvcreate --name var.log --size 8G /dev/vghk16
mkfs.ext4 -m 1 /dev/vghk16/var.log
mkdir -p /mnt/destimg/var/log
mount /dev/vghk16/var.log /mnt/destimg/var/log
time rsync -a -A -X /mnt/srcimg/ /mnt/destimg
umount /dev/vghk15/snap-libvirt_images  /dev/vghk15/snap-var  /dev/vghk15/snap-root
lvremove -f /dev/vghk15/snap-libvirt_images  /dev/vghk15/snap-var  /dev/vghk15/snap-root
time hktools/sysadm/lvsnapbackup.zsh /dev/vghk16/ /dev/vghk15/home /dev/vghk15/*.hkoba

… snapshot 作成と mount は一つの関数にまとめるべきだった…読みづらい…

6. lvm vg name と luks の uuid 更新.

前回のメモ>

今回も grub2-mkconfig 使うべき所を変に気を回して失敗した。

以下は本来こうすべきだったはず!という事後の想像>

volume group 名の変化への対処

HDD 引越しで Fedora が起動しなくなるのは大抵 fstab の書き損じ。

  • 最後に chroot して mount -a 通るまで確認するべきだった。
    • lv を削ったので fstab 編集は必須
    • /boot/boot/efi については /dev/sda1 とかに書き換える方法だと chroot からの mount -a での確認が出来ない (この時点では sdb1 とかだから)。 だから真面目に uuid の置き換えをやるべきだった。
  • 一度やり直しが発生したせいで、fstab の確認が甘くなった。 (こういう再演をもっと確実にこなすには…?)
files=(/etc/fstab /etc/default/grub)
sed -i -e "s/vghk15/vghk16/g" /mnt/destimg$^files
vim /mnt/destimg/etc/fstab

luks uuid の変化への対処

files=(/etc/crypttab /etc/default/grub)
read vol olduuid rest < /etc/crypttab
newuuid=$(cryptsetup luksUUID /dev/sdb3) 
sed -i -e "s/$olduuid[6,-1]/$newuuid/g" /mnt/destimg$^files 

7. chroot して、 dracut

mount -t proc none /mnt/destimg/proc
mount -t sysfs none /mnt/destimg/sys
mount -t devtmpfs none /mnt/destimg/dev
chroot /mnt/destimg

chroot 内での作業

mount -a が上手く行くか確認し、大丈夫なら initramfs と grub2-efi.cfg を生成し直す。

mount -a

dracut --force
# 確認
lsinitrd -f etc/crypttab

grub2-mkconfig -o /etc/grub2-efi.cfg

exit
…余談…

この時作った chroot 空間の中で zsh の行編集の一部 (Ctrl キーと alphabet の組み合わせだけ)が 無効になってて…これは何が原因なのか、どなたかご存知でしたら是非ツッコんで下され〜

8. umount

umount /mnt/destimg/boot/efi /mnt/destimg/boot
umount /mnt/destimg/{proc,sys,dev}
umount /mnt/destimg

grep sdb /proc/mounts

で、後は物理交換して再起動するだけ(のはずだった…次こそは一発でやりとげたいなぁ…)

結末

追記. 更に後日談

/var/log を新たに lv として分けてあったのに、 fstab に書き忘れてて全然活かせてなかったと後で気付いた。 これも最初は正しいつもりの fstab を書き上げた後で、手戻りが発生したことで抜けになったケース。

そう言えば、増やした lv の fstab への書き忘れは mount -a でも検出できないなぁ…

折角 /etc を git で管理してあるのだから、一旦 git diff で変更を保存しておいて、 後で git apply すれば良かったと、今更ながら思う。

再帰globパターン **/ の元祖は zsh なのか、調べてみた

zsh

今日 @satoh_fumiyasu さんのこんなツイートを見かけました。

私もずっと気になっていたので、この機会に調べてみました。

最初は ..../ だった!

当時のソース公開は usenet 経由だったよな、というわけで groups.google.com で zsh を検索すると、Zsh-1.0 の公開時のアナウンス と、 ソース が出てきました。

アナウンスの中では zsh の機能の由来を From ksh, From tcsh, From bash と細かく挙げているのですが、 再帰globパターンは『その他』として別扱いされています。

Other stuff:

        - recursive directory search in filename generation (for example,
          "ls ..../*.c" lists all .c files in the hierarchy)

これによると Zsh-1.0 では、 **/ のような再帰 glob を ..../ と書いていたのですね。 この書き方は翌年リリースされた Zsh-2.00 でも変わらないようです。

Zsh-2.1 で ****/ に変わった

Zsh-2.1 の README の Modification history に、初めて ****/ の言及が出てきます。

Modification history:
0.03-1.0:
        - "..../" is now "****/".  I know this isn't backward compatible,
          but I had no choice; the string "..../" was unquotable.

0.03-1.0 とありますが、恐らく 2.03 から 2.1 のことを指しているのでしょう。

Zsh-2.2 で **/ になった

そしてついに Zsh-2.2 の Modification history に、 **/ が出現します。

Modification history:

2.2.0-2.1.0:

o ls **/file is now equivalent to ls ****/file

時に、1992/05/14。 ここまでの経緯を見るに、zsh が元祖ではないか?と私には見えました。

なぜ ..../ や ****/ だったのか?

ところでなぜ最初、再帰glob は ..../ という長い書き方だったのでしょう? それは、どうやら、 実装に理由があったようです。

そもそも zsh**/ は、0個以上へのマッチ文字 # (正規表現のクリーネスターに相当)を使った書き方 (*/)# の省略形です。 (マニュアルを参照)

で、なんと、Zsh-1.0 のソースを読むと、 ..../ を直接 (*/)# へと置き換える^^、という実装になっていたのです。

gist.github.com

要するに、5文字で、ファイル名として滅多に使わないものという制約の中で、打ちやすいものを選んだのでしょうね。

なぜ私は敢えて Zsh で Shell Script も書くのか、目的合理性はどこにあるのか

zsh

はじめに

UNIX, Linux のためのシェルスクリプトbash どころか敢えて zsh で書くことに、どんな目的合理性があるのか… 個人的な考えをまとめてみます。 #!/bin/zsh の勧めにしたかったけど、途中で力尽きました。

勿論、 万人向けではない話 なので、なるべく背景・仕事環境・与えられた状況についても言及していくつもりです。

ツッコミも歓迎です。

前提

  1. 人生は短い。 限られた時間で、目的を達成したい。 『成果出力 / 学習含めた開発時間』の比を、納得行くレベルに保ちたい。
  2. sh 族の各々には力の差が有る。 プログラミング言語としての表現力の優劣や、処理系の完成度の優劣が存在する。
  3. チームの root と教育を任されている。 自分が個人としてベストな仕事をするだけでなく、メンバーの学習まで含めて判断したい。
  4. 道具は使い分ける。 sh 族で書くメリットのある、向いたタスクだけを sh族で書き、複雑な部分は他の言語で解く。

1. Pure な sh 縛りで書くことのコストに耐えうるか

記憶では…20年以上前、私が研究室にいた頃に、SONY NEWS と Sun だったか…でどちらでも動く /bin/sh スクリプトを書こうとして、 test コマンドの、あるオプションの有無 で泣いたことが、有ったように思います。今ならベンダー間の互換性テストが十分進化して、 こんな事態は起こらないのかもしれませんが…私が Pure な sh 縛りの美辞麗句に疑問を抱くには十分すぎる出来事でした。 条件検査のような、動作の要すら外部コマンドに頼ると、ベンダー間の移植性を確保するために必要な努力は、生半可でなく膨らむのだと気付いたのです。

当時の zsh は既に ksh 流の組み込みテスト [[ を実装していました。(手元の 1993 頃の zsh2.3.1 のマニュアルで確認)。 これを使えば、移植性の問題は zsh 自体の移植の問題に帰着できます。Port zsh or die! (←いえ、移植に貢献できたことは無いですが…)

なお bashrelease note を見ると、 組み込みテスト [[ が入るのは bash-2.02 っぽいですね。 ftp サイトのタイムスタンプ を見ると、 1998-04-18 リリース?なのでしょうか。

(linux の各 distros vendor がいつ↑これを採用したのか、いつか調べてみたいものです)

2. bash/ksh/zsh… 各々の強みを殺して使うコストを払う余裕が有るか

bash/ksh/zsh は互いに切磋琢磨し、機能を向上させてきました。 複数の sh で同じ書き方で使える機能も有れば、そうでないものもあります。 従って、もし複数の sh で互換なコードを書くならば、絶対に使えない機能が出てきます。

一つの具体例を挙げます。

zshbash/ksh との違いの中で、私が最も zsh らしさを感じる点の一つが、 sh_wordsplit のオプション化です。

シェルスクリプトで初心者泣かせは沢山ありますが、最初に注意されることの一つは、 $変数"double-quote" で 囲むことでしょう。変数にスペースやタブが入ったら誤動作するからです。

$ foo="Program Files"
$ ls -dl $foo
ls: cannot access Program: No such file or directory
ls: cannot access Files: No such file or directory
$ ls -ld "$foo"
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 11:53 Program Files

こんな挙動になっている理由は、Pure な sh に配列変数が無かったことが原因でしょう。 配列がなかったから、複数の値を(一つの変数で)直接渡すことが出来ない。 だから代わりに、一つの文字列変数に空白などの区切り文字で繋げて渡し、受け手でそれを分解して使う… それしか実現手段が無かったからです。 この挙動は sh との互換性を優先する限り不可避です。 bash/ksh は今もそれに従っているはずです。

$ mkdir a b c d
$ foo="a b c d"
$ ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 a
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 b
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 c
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 d

対して zsh は、私が初めて触れた頃から既にこの挙動を オプション化して オフに していました。

% foo="Program Files"
% ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:30 Program Files

もし本当に複数要素を変数に入れるつもりなら、最初から配列変数にしておけば良いという考えでしょう。 (ここに限らず zsh の設計の節々には、 Pure な sh との互換性は程々にして利便性を優先する 、という、勇気ある選択が見て取れます。私はその勇気を賞賛したい)。

昔から、 zsh では代入の右辺を foo=() のように囲むことで、配列変数を作ることが出来ます。

% foo=(a b c d)
% ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 a
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 b
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 c
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 d

ちなみに配列の要素に空白が入っても大丈夫です。(唯一の残念な例外は、空文字列そのもの。私が知る限り…)

% foo=(
a b c d
""   ""
"Program Files"
)
% ls -ld $foo
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:30 Program Files
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 a
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 b
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 c
drwxrwxr-x. 2 hkoba hkoba 4096 Jan 31 12:39 d

重要なことは…このような 特定の sh 処理系の進化のメリットを受けたコードを書くためには、 他の処理系との互換性は捨てざるを得ないケースが有る 、ということです。

いつまで "$foo" って書けって叱り続けるの?

勿論、自分だけがシェルスクリプトを書くなら、 "$foo" と書けば良い、で済みます。 ですが、新人教育も自分の仕事だったら?

もっと言えば…教える相手が、自分と同じ方向での (root を期待されるような) unix プログラマーではなく、 他にもっと大事な仕事を抱えていて、片手間の自動化の道具を求めている人だったら?

明らかに、"$foo" の話は苦肉の策、回避策(work around) です。 そして解決策(配列変数を使え)も存在します。 それに新人さんを巻き込み続けることに、どの程度の合理性があるのか…

それよりも、自分が独裁者になって zsh と no_sh_wordsplit を押し付ける、 俺を信じて zsh で書け と言い切るほうが、お互いハッピーになれるのではないでしょうか…

Zsh で書くメリット

ここに記すには余白が狭すぎる(力尽きた)。

代わりに参考文献

英語が読めるならこちら

From Bash to Z Shell: Conquering the Command Line

日本語の本ならこちら

昔 fj に zsh のマニュアルの和訳を投稿していた方の本のはず。 変数展開フラグの解説など充実しているので、 zshスクリプトを書く人にも極めて有益な本です>

www.amazon.co.jp

Zsh で書くデメリット

  • 明示的にインストールする必要がある
  • 教育を請け負う責任が発生する
  • ユーザ向けの UnitTest の枠組みが不足(良いの有ったら知りたいです)

後は…?

.oO(孤独を味わう…とかはあるかもしれませんね…)