とある方のブログでみたスクリプトが、なぜ暴走したのかについて
Twitter で流れてきたリンクで、Perl を勉強中の方のブログが目に止まりました。
読んでいて気付いた点があったので、それについて書いてみます。 (最初はコメントで書いていたら文字数制限で途切れてしまったので… 途切れるなら字数制限の警告が欲しい…)
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 の配列展開で生成してみる
はじめに
値に 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') %
出来た気がする…
追記2018-05-08+:配列じゃなくてスカラーの場合の書き方、コピペ用
実務でよく使うようになったので、これも。
% foo="Foo's Bar" % print -r "${${(qq)foo}//'\''/''}" 'Foo''s Bar' %
関数にまとめるとこうかしら?
function zqsql { local val=$1 print -nr "${${(qq)val}//'\''/''}" }
[linux]作業メモ:luks で暗号化した EFI ベースの notepc で HDD を換装した際の作業記録
単なる自分メモ。
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 更新.
前回のメモ>
fedora のディスク暗号化をやり直す場合のメモ。
— hkoba (@hkoba) August 25, 2014
lv を restore したら /etc/crypttab と /etc/default/grub の luks 行を書き換えて、 grub2-mkconfig で grub.cfg を作り直す
今回も 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
で、後は物理交換して再起動するだけ(のはずだった…次こそは一発でやりとげたいなぁ…)
結末
よし、新 HDD で desktop までたどり着いた!
— hkoba (@hkoba) April 10, 2016
% sudo vgs
— hkoba (@hkoba) April 10, 2016
VG #PV #LV #SN Attr VSize VFree
vghk16 1 10 0 wz--n- 930.13g 624.13g
%
ディスクにも余裕出来たし、めでたし。
.oO(…いっそ systemd が top 的な物まで取り込んでくれたら、心穏やかに boot を見守れるようになるのかもしれん…)
— hkoba (@hkoba) April 10, 2016
@hkoba はじめての systemctl enable debug-shell .
— hkoba (@hkoba) April 10, 2016
起動時に top 取れて幸せ。
なんだ setfiles(8) くんが時間掛かってたのか~(白目
追記. 更に後日談
/var/log
を新たに lv として分けてあったのに、 fstab に書き忘れてて全然活かせてなかったと後で気付いた。
これも最初は正しいつもりの fstab を書き上げた後で、手戻りが発生したことで抜けになったケース。
そう言えば、増やした lv の fstab への書き忘れは mount -a でも検出できないなぁ…
折角 /etc を git で管理してあるのだから、一旦 git diff
で変更を保存しておいて、
後で git apply
すれば良かったと、今更ながら思う。
再帰globパターン **/ の元祖は zsh なのか、調べてみた
今日 @satoh_fumiyasu さんのこんなツイートを見かけました。
zsh や rsync にある拡張 glob pattern のひとつ、** の元祖ってどこ?
— ふみやす@シェルまおう(自称ではない) (@satoh_fumiyasu) 2016, 2月 10
私もずっと気になっていたので、この機会に調べてみました。
最初は ..../ だった!
当時のソース公開は 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 のソースを読むと、 ..../
を直接 (*/)#
へと置き換える^^、という実装になっていたのです。
要するに、5文字で、ファイル名として滅多に使わないものという制約の中で、打ちやすいものを選んだのでしょうね。
なぜ私は敢えて Zsh で Shell Script も書くのか、目的合理性はどこにあるのか
はじめに
UNIX, Linux のためのシェルスクリプトを bash どころか敢えて zsh で書くことに、どんな目的合理性があるのか…
個人的な考えをまとめてみます。 #!/bin/zsh
の勧めにしたかったけど、途中で力尽きました。
勿論、 万人向けではない話 なので、なるべく背景・仕事環境・与えられた状況についても言及していくつもりです。
ツッコミも歓迎です。
前提
- 人生は短い。 限られた時間で、目的を達成したい。 『成果出力 / 学習含めた開発時間』の比を、納得行くレベルに保ちたい。
- sh 族の各々には力の差が有る。 プログラミング言語としての表現力の優劣や、処理系の完成度の優劣が存在する。
- チームの root と教育を任されている。 自分が個人としてベストな仕事をするだけでなく、メンバーの学習まで含めて判断したい。
- 道具は使い分ける。 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! (←いえ、移植に貢献できたことは無いですが…)
なお bash も release note を見ると、
組み込みテスト [[
が入るのは bash-2.02 っぽいですね。
ftp サイトのタイムスタンプ を見ると、
1998-04-18 リリース?なのでしょうか。
(linux の各 distros vendor がいつ↑これを採用したのか、いつか調べてみたいものです)
2. bash/ksh/zsh… 各々の強みを殺して使うコストを払う余裕が有るか
bash/ksh/zsh は互いに切磋琢磨し、機能を向上させてきました。 複数の sh で同じ書き方で使える機能も有れば、そうでないものもあります。 従って、もし複数の sh で互換なコードを書くならば、絶対に使えない機能が出てきます。
一つの具体例を挙げます。
zsh と bash/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 でスクリプトを書く人にも極めて有益な本です>
Zsh で書くデメリット
- 明示的にインストールする必要がある
- 教育を請け負う責任が発生する
- ユーザ向けの UnitTest の枠組みが不足(良いの有ったら知りたいです)
後は…?
.oO(孤独を味わう…とかはあるかもしれませんね…)
謹賀新年/2016目標
あけましておめでとうございます
昨年交流させていただいた皆様、良くして下さり有難うございました。
また今年も、仲良く本音で楽しんで生きましょう。
昨年の反省点
- 引越し、ならず!荷物減らしも、イマイチ進まなかった。
- ライフワーク(オレオレ言語作り)にも、望んだほどは体力を充てられなかった。
- Twitter 読むのに時間を使い過ぎ?得られる情報も多いとは言え、なかなか難しい…
昨年の良かった点
- 生活
- 食生活改善(肉を積極的にとるようにした)のお陰か、体が疲れにくくなった気がする。(2kg 太ったけれど、痩せ過ぎだったから良いよね?)。以前は一日働いた後は『燃え尽きた』ような脱力感があった。
- 仕事と OSS 活動
- YAPCAsia2015 のお手伝いが出来たこと
- 職場に Slack を有料版で導入してもらえたこと。細かいやり取りを記録に残せるようになったことで、業務のタコツボ化回避に極めて有効と分かった。(独り分報も始めてみた。こっちは todo の意識化程度)。正直、もう出社しなくても良いのでは…?
- 2015年の新人さん3人が、何とか職場に定着してくれたこと。本人も教育担当も、大変頑張って下さいました。ありがたや…
- お客様の社内で少しずつ YATT::Lite の導入事例を増やせていること。非プログラマのために作った YATT なので、それが目論見通りの成果を出すのを見るのは嬉しい。
- Perl で10年?以上かけて探求してきた
use fields
とimport()
を活かした、静的 typo 検出とコンパイル時コード生成を両立させたコーディングスタイルの、知見の集大成である MOP4Import::Declare を CPAN にリリース出来たこと。これで自作モジュール CPAN 化の最大の障壁が無くなった。 - (Fedora) Linux の Surface Pro3 の日本語キーボードカバー対応に、(ショボいパッチとは言え)貢献できたこと。
- 長年構想をあたためてきた、TclTk + sshcomm + tkhtml3 ベースの、業務特化ウェブサイトのための半自動設定 GUI、そのプロトタイプを一週間ででっち上げて実戦投入できたこと。大きな手応えを得られた。
- 英語
- 未来塾で、自分の英語の音作りに関して、ほんの少し、進むべき道が見え始めたこと
今年の目標
- 追記 いいかげん棚卸しの習慣をつける。本のあふれ、食材の賞味期限切れに対策を。
- 引越し(移住?)を、現実の計画にする。Slack ベースで働ける体制が出来た以上、職場から遠くても良いので、家賃の低い所に移りたい。
- YATT::Lite (や perlminlint) の静的な typo 検出のメリットを、普通の人が簡単に受けられるように、 Web IDE とか作れないか…模索したい。
- TclTk + tkhtml3 という thin なプラットフォームの可能性を、もっと世の中の人に使いやすい形にまとめる。
- FSharp か OCaml の勉強をもう少し頑張って、実戦投入の道を探る。
- 自分の余暇時間を増やせるようなツール作りを模索する。(Twitter 関連かなぁ…)
- 彼女(嫁)探しを、諦めない…諦めない…(一体どこに行けば、オイラと共存可能なひとが見つかるのか…?)
SQLite でも列名を生成する時の quote には backtick (`) を使ったほうが良いぽい気がしてきた
軽くハマったのでメモ。
まずは実験用のテーブル tab1
を作ります。(中身は空のままにします)
sqlite> create table tab1(foo, bar); sqlite> select * from tab1; sqlite> select * from tab1 where foo = 3; sqlite>
次に、意図的に列名を打ち間違って入れてみましょう。 例えば fooo
みたいに。
sqlite> select * from tab1 where fooo = 3; Error: no such column: fooo sqlite>
ちゃんとエラーになります。では、今度は(間違った列名を) double-quote で囲んで
"fooo"
のように変えてみましょう。これは SQL 標準に従った書き方です。ところが…
sqlite> select * from tab1 where "fooo" = 3; sqlite>
なんと、 エラーにならない!
なぜこうなるかは SQLite の公式マニュアル SQLite Query Language: SQLite Keywords にあるように、
If a keyword in double quotes (ex: "key" or "glob") is used in a context where it cannot be resolved to an identifier but where a string literal is allowed, then the token is understood to be a string literal instead of an identifier.
"..."
の指す列が存在しないなら、代わりに 文字列として解釈し直して しまうから。…超、迷惑…
このように、SQLite において、列名を double-quote で囲むと 列名の誤りがエラーにならないため、問題に気づくのが遅くなるのでよろしくないのでは?という話です。
むしろ mysql 風の backtick (`) で囲ったほうが (SQL標準から外れても) エラーになるだけマシではないか?と。
sqlite> select * from tab1 where `fooo` = 3; Error: no such column: fooo sqlite> select * from tab1 where `foo` = 3; sqlite>
SQL::Maker で引用符を切り替えるには
例えば Perl の SQL::Maker - Yet another SQL builder - metacpan.org は SQLite に対してはデフォルトでは double-quote (") を使います。 これを backtick に切り替えるには、明示的にオプション "quote_char => q{`}" を渡して
use strict; use SQL::Maker; my $mk = SQL::Maker->new(driver => "SQLite", quote_char => q{`}); print join "\n", $mk->select(tab1 => ["*"], +{fooo => 3}); # SELECT * # FROM `tab1` # WHERE (`fooo` = ?) # 3
とする必要が有ります。