バッチの find | xargs では -r オプションを付けること > 未来の自分

今回は短いです。ライトニングな感じです。

xargs なんですが、結構 find と組み合わせて使いますね?
…使いますよね?
…ここは空気読んでウソでも「使う」って言う所ですよ?

で、パイプした find の出力が空だった場合でも xargs の部分が実行されてしまうんですよ。
衝撃の事実ですね。ええ、インパクトありますよ。rm -rf とか食わせてると特に…
まあ、今回はテストで echo 付けてあったので死なずに済みましたが。

というわけで、crontab とかのバッチ処理の時は xargs -r の使用を考慮しましょう。
こうすると、入力が空の時には xargs に食わせたコマンドが実行されません。
バッチ利用(大抵は cron)の時は付けた方がいいと思います。

PostgreSQL の UPDATE トリガで時刻を自動設定できなかった話

今回はいつになく簡易版でお送りします。

ようは下のようなトリガを書いても期待通りに動かなかった話です。

該当カラムの定義は updatedAt TIMESTAMP DEFAULT current_timestamp NOT NULL です。
MySQL の時は updatedAt DATETIME DEFAULT current_timestamp ON UPDATE current_timestamp NOT NULL と書けばトリガ不要で自動更新してくれますね。

-- UPDATE 文の実行時に updatedAt カラムを
-- 現在時刻に設定してくれるトリガ(PostgreSQL 用)
-- 動作不良版
create or replace function set_updated_at() returns trigger as $function$
begin
if new.updatedAt is null then
new.updatedAt := current_timestamp;
end if;
return new;
end;
$function$ language plpgsql;

create trigger auto_set_updated_at before update on SomeTable for each row
execute procedure set_updated_at();

これで SomeTable の UPDATE 文実行時に updatedAt カラムをその時の時刻で更新してくれるはずでした。
ところが、どうやら SET 句に updatedAt カラムを指定しない場合でも new.updatedAt が NULL になってくれないようです。(例えば UPDATE SomeTable SET OtherCol = 1)

で、色々試して、とりあえず動いたのが次のバージョンです。
ちなみに、明示的に SET 句で値を指定した時(updatedAt = ‘2016-06-06 12:00’ 等)は、そちらを使います。

-- UPDATE 文の実行時に updatedAt カラムを
-- 現在時刻に設定してくれるトリガ(PostgreSQL 用)
-- いちおう動作版
create or replace function set_updated_at() returns trigger as $function$
begin
-- 次の行の条件を変更
if new.updatedAt is null OR new.updatedAt = old.updatedAt then
new.updatedAt := current_timestamp;
end if;
return new;
end;
$function$ language plpgsql;

動いたのはいいんですが、なぜ new.updatedAt が NULL じゃないのか気になって仕方ありません。
どなたか理由をご存知でしたら、ぜひともご教示ください。

common lisp で何か書いてみる(その2)

どうも。
最近の投稿ペースが速くて自分でも驚きながら書いています。

さて、引き続き caveman2 で何か書いてみようという話です。
が、今回もまた自前のコードを入力する機会は訪れませんでした…

とりあえず目次は以下の通りです。

  • Road to qlot:quickload
  • 穴居人が立ち上がる時

■Road to qlot:quickload

結論:行ってみた限りでは全部行き止まりでした。

や、色々試したんですよ、ホントに。

qlfile の中身を次のようにして、それぞれコメントアウトしながら全部試しました。

# qlfile

github caveman2 fukamachi/caveman
# git caveman2 https://github.com/fukamachi/caveman.git
# ql caveman2 :latest

インストールコマンドも色々やってみました。

  • プロジェクトディレクトリで
    • (qlot:install :myproj1)
    • (qlot:install)
    • (qlot:install :caveman2)
  • プロジェクトディレクトリの一つ上で
    • (qlot:install :myproj1)
    • (qlot:install)
    • (qlot:install :caveman2)

見付けた変数を変えてみたりもしました。

(setq ql-setup:*quicklisp-home* #P"~/myproj1/")
(qlot:quickload :caveman2)

上の色々な組み合わせも試しました。

最終的に挫折しました…(泣)

■穴居人が立ち上がる時

で、qlot を使わずに caveman2 だけでも動かしてみようと試行錯誤して、こちらは何とかサーバを立ち上げる事ができました。

前回で (ql:quickload :caveman2) は通るようになったので、github の README を読みながら進めていきます。

最初のプロジェクト作成は caveman2:make-project で問題無く終了です。

続いて、とりあえずテスト稼動が目的なので、ルーティングとかデータベース操作とかはどんどん飛ばして、サーバの立ち上げに入るわけです。

書いてある通り、おもむろに (myproj1:start :port 8080) を実行… 当然パッケージ myproj1 が無いと怒られます。
そして毎度の如く、苦闘の日々が始まるのです。

まあ、状況としては windows ユーザに linux の bash プロンプト使わせるようなものですね。
やりたい事は分かってるのに、どうやればいいのか分からないというジレンマに陥るわけです。

最終的に動くようになるまで

  • ファイルパスの指定方法
  • 読み込ませるファイル選択

で悩む事になりました。

パスの指定方法は make-project の引数にあったので、それでいけるはずです。
が、カレントディレクトリが何処なのかとか、うっかり #P を付けずに文字列で書いてしまったりとかで、なかなかうまく行きませんでした。

さらに、読み込むファイルが分からないという点が問題の複雑さに拍車をかけることになります。

コマンドの方は load か require あたりだろうと推測しましたが、ファイルに関しては見当もつかなかったので、考えていても仕方無いと割り切って総当たりして解答に辿り着きました。

誰かの役に立つかどうか怪しいところですが、いちおう最終形を載せておきます。

(ql:quickload :caveman2)
(caveman2:make-project #P"~/myproj1/" :author "myname") ; '#P' を付けないとエラー、ディレクトリは未作成でもよい

(load #P"~/myproj1/myproj1.asd") ; これと次は '#P' 無しでも動いたが、一応付けておく
(load #P"~/myproj1/app.lisp")
(myproj1:start :port 8080)

以上です。

VirtualBox のホストオンリーネットワークアダプタで困った件

ここ数日、いろいろ躓いてばかりな気がしますが、今度は VirtualBox のお話です。

毎回だらだらと長い文章になってしまうので、簡潔に書いてみます。

問題:ホストオンリーアダプタのネットワークでホストマシンだけ接続できない
結論:以前の設定が悪さをしてる場合があるので、未使用のサブネットアドレスを割り当ててみると改善される事がある

以上… では説明不足ですよね、はい。

まず VirtualBox の仮想マシンでホストオンリーネットワークアダプタを使っていました。
それで、ホストマシン(VirtualBox 自体を実行してる方ですね)をスリープしてから復帰すると、ホストだけが該当のネットワークで通信できない状態になっていました。
その間もゲストマシン(仮想マシンの方です)同士では問題無く通信できていました。

このような状況でも、最近までは仮想マシンを利用しての開発が無かったので特に問題ありませんでした。
ところが、ここ数日で やれDockerだ common lisp だと仮想マシンを使い倒すようになったため、この接続不良による再起動の嵐が非常に面倒になりました。

同じ問題で悩んでいる人がいるかと思い google 先生に検索してもらって幾つか事例を見て回ったのですが、現象が微妙に違っていたり、書いてある通りに試してみても効き目が無かったりと、うまくいきませんでした。
しかも、途中から「コントロールパネルのネットワークアダプタを無効にするとアイコンが消える」という新たな問題も発生したのです。

その後紆余曲折があり、最終的にはクリーンな状態になるよう

  1. 今あるホストオンリーアダプタを全部削除
  2. 再起動
  3. 今のバージョン(Ver 5.0.x)をアンインストール
  4. 再起動
  5. 以前のバージョン(ver 4.3.x)をインストール
  6. 再起動
  7. ホストオンリーアダプタを新規追加
  8. 再起動
  9. 仮想マシンのネットワークアダプタを新しいのに変更

という作業をして、接続を試しました。
ところがやっぱり駄目で、途方に暮れかけた時にゲスト側に不自然な点を発見したのです。
この時は VirtualBox の設定で DHCP サーバを OFF にしてあったのですが、何故かゲストが DHCP で IP アドレスを取得していました。
不思議に思って別のサブネットに変更してみたところ、それが大当りだったらしく嘘のように素直に仮想マシンと通信できるようになったのです。

それまでに散々いじったので、最後のサブネット変更が決定打だと言い切ることはできませんが、これが最も有力だと思います。
思い返してみると、確かに以前ホストオンリーアダプタを作成したまま virtualbox をアンインストールしたことがありました。
その後に新バージョンを入れ直した時にも、何故かインストール直後にアダプタが有り、気になった覚えもあります。

このような点から、最初に書いた結論が導きだされた次第です。
この記事が解決事例の一つとして、どなたかの参考になればいいなと思います。

common lisp で何か書いてみる(その1)

いつもの如く、唐突に common lisp の勉強をしようと思い立ち、色々見ながら環境設定をしたのでメモ。

思い付きで行動したので、あちこちで袋小路に突き当たりまくっているのは相変らずである。

■alpine linux で環境設定してみた

始まる前から後のことを考え、docker コンテナで動かすと面白そうということで virtualbox で新しく VM を作って alpine linux をインストールした。
debian とか ubuntu と違って、ISO 起動でインストーラが走ってくれるわけではなくて、Wiki を見ながら何とか導入。
パッケージは sbcl と clisp があったから「apk add」で簡単に入った。
ちなみに何度も「apt install」と入力してしまったのはご愛嬌。

問題はここからで、common lisp のパッケージ管理ライブラリ quicklisp を入れてみたが動かない。
ライブラリ自体の読み込みはできるけれど、そこから他のライブラリの自動ロードをするとデバッグモードになってしまう。
しばらく格闘してみたが、このまま環境設定で時間を食ってもアレなので、alpine は放置することにした。

■debian GNU/Linux で環境設定してみた

というわけで、愛用の debian GNU/Linux の VM を立ち上げて、こっちで環境設定してみた。
慣れ親しんだ「apt-get install」で、あっと言う間に sbcl のインストールが完了。
cl-quicklisp というパッケージもあったので、それも入れた。

で、プロジェクトごとにパッケージ管理をしてくれる(ruby の bundler みたいな) qlot というのを入れてみた。
これもすんなりと入ったので「よーし、これでコードが書けるぜー」とか思いながら、web フレームワークの caveman2 を入れ「ようとし」てみた。
…入ったよ、うん、結果的には。それだけで 1時間かかったのを気にしなければ。

その経緯

  1. qlot が初期化できない
    qlot が入った。ql:quickload でロードした。qlfile 書いた。
    さあ (qlot:install :myproj) …あれ?
    エラーした。
    qlfile を空にしてみてもエラーした。
    ライブラリ取得先を ql から git に変えてみてもエラーした。
    …よし、一旦放置。

  2. ql:quickload で caveman2 をロードできない
    さすがに直接 ql:quickload すれば動くだろう… あれ?
    エラーした。パッケージ名の書き方間違ったっけ?
    文字列で “caveman2” … エラー。
    キーワード形式で :caveman2 … エラー。
    クォートして ‘caveman2 … エラー。

    …落ち着け、キーボードは投げるもんじゃない。
    近場には HHK2Lite(英語) は売ってないんだ。
    それに買ったばかりのディスプレイに当たったらどうする。
    とりあえずお茶でも飲んで落ち着くんだ。

    (30分経過)…ふぅ、今日の茶菓子は美味かった。
    お、よく見たらエラーファイル名あるじゃん。cffi?
    とりあえず「apt-cache search」して… cl-cffi …って、そのまんまかい!
    駄目元でインストールっと。これで直ったら微妙だなぁ…
    (ql:quickload :caveman2) Enter!
    え、通るの?原因これだけ?私の30分を返せ!(←責任転嫁:嫁居ないけど)

  3. 改めて qlot
    んー、これなら qlot も動きそうな気がする。
    (qlot:install :myproj) っと。ん、エラー?
    …落ち着け、深呼吸だ。吸ってー、吐いてー、吐いてー、吐…けるかボケェ!吸わんと死ぬわ!
    ……やめよう、一人ボケツッコミは寂しすぎる…

    そうだ、さっきと同じくダイイングメッセージにヒントがあるに違いない。エラーでプロセス死んでるしな。
    死体は語る、犯人は現場に戻る(違)
    えーと…

    error while parsing arguments to DESTRUCTURING-BIND:
    invalid number of elements in
    ("caveman2:latest")」

    …待て、google 翻訳に頼るんじゃない。たぶん翻訳しても、そんなに変わらんし。
    一つずつ順番に行こう。

    • エラー
    • ↓している間
    • 解釈
    • 引数
    • ↓のため
    • DESTRUCTURING-BIND
    • (区切り)
    • 不正な
    • ↓の
    • 要素
    • ↓の中の
    • “caveman2:latest”

    まとめると
    「DESTRUCTURING-BIND のため(に)引数(を)解釈している間(に)エラー」
    「”caveman2:latest” の中の要素の数(が)不正」
    …ん?「caveman2:latest」って qlfile の書き間違い?
    あー、うん。たしかに違うわ。スペース必要なのね。

    よーし、これで動くはず。今日のおやつを賭けてもいい。
    (qlot:install :myproj) …エラー?え、おやつ抜き?えぇっ?
    くそぅ、この恨み晴らさでおくべきか。(←自業自得)
    二度ある事は三度あるのであるからしてヒントはエラーメッセージにあるのである。
    というわけでチェック!
    Component "myproj" not found
    うん、myproj は見付からないよね、今から作ろうと思ってるんだから。
    ……… もしかして :myproj の部分いらない?
    (qlot:install) っと… あー、うん。動いたね。動いちゃったね。
    私のおやつ返せ!(←再度の責任転嫁:もちろん嫁は居ない)

補足:没収されたおやつはスタッフ(= 私)がおいしくいただきました。

VirtualBox に CentOS 6.7 を入れた後で NAT にした時の設定

例によって忘れた時用のメモです。

タイトル通りなんですが、VirtualBox で CentOS 6.7 を使った時に困った話です。
状況としては下のようになります。

  1. ネットワーク設定で最初に NAT以外(ホストオンリー等)のネットワークアダプタを選んだ
  2. そのアダプタを後から NAT にした

この場合、VM を起動しても NAT からインターネットへのアクセスができません。
とりあえず下のように設定したら NAT を通って外へ出られました。

  • /etc/sysconfig/network ファイル
    • GATEWAY の行をコメントアウト(先頭に「#」を追加)する
  • /etc/sysconfig/network-scripts/ifcfg-eth0 を下の内容にする(NIC の数によっては eth0 の「0」を別の数字に読み替えます)
    DEVICE=eth0
    ONBOOT=yes
    BOOTPROTO=dhcp

ちなみに、NAT が動いた時の IP が「10.0.5.15」で、デフォルトルートは「10.0.5.2」でした。
このパラメータを利用して手動設定ができるかもしれません。どなたか試したら教えてください。

今回は短い(当社比)ですが、以上です。

MySQL のテーブルコピーでやらかしてしまった件

はい、タイトル通り、今回はやらかしてしまった件です。
幸いにも被害は自分だけに留まったので大目玉をくらう前にコッソリ修正できましたが…

で、何をやったかというと、データごとテーブルのコピーを取るのに「CREATE TABLE test_t SELECT * FROM current_t」と叩いて test_t テーブルを作成したわけですよ。
それで、test_t テーブルをいろいろいじってから「RENAME TABLE current_t TO old_t, test_t TO current_t」として現行のテーブルと入れ替えました。
念の為にちょこっと動かしてみて、問題が無いようなのでそのまま利用することにしました。

悲劇はその後に起こったのです!

機能の追加を行った後で動作確認をしようと、テーブルに新規レコードを追加してからそのレコードを検索したところ… 見付からない…?
慌てて同じ条件で SQL コマンドを叩くと、ちゃんと居る…?
まさか幽霊かと思っても、この相手には見て確かめるための足はありません。
首を傾げながらレコードを消しては作り、消しては作り… 何度目かにふと思い立って SELECT * で全カラムを表示させてみました。
するとそこには… ああ、今思い出しても恐しい… プライマリキー値 0 (ゼロ)が居たのです!!

この時の動作は、レコードを探してそのプライマリキーを得るというものでした。その関数はエラー時に FALSE を返します。
ここで親切な PHP さんは、boolean の文脈では空文字列や数値の 0 (ゼロ)も FALSE だと判断してくださいます。
賢明な読者様にはもうお分かりでしょう。上の新規レコードが返すプライマリキー値 0 はエラー処理ルーチンの方へ入ってしまうのです。

まあ、ここまでは比較的よくある話とも言えます。
しかし、今回の真の敵はコイツであるはずが無いのです。何故ならそのカラムには AUTO_INCREMENT 属性が付けてあったのです。
つまり、新規レコードには自動的に新しい ID値が付与されるため、値 0 が設定される事実とは矛盾します。
この矛盾が今回の密室殺人の謎を解くカギになるはずだっ!(違)

と、ここまでが前置きです。
本題は「SHOW CREATE TABLE current_t」を実行した時に発見した驚愕の事実にあります。
コピー元(リネーム後の old_t)では「NOT NULL PRIMARY KEY AUTO_INCREMENT」となっていた部分が「NOT NULL DEFAULT ‘0’」に変わっているではありませんか。
確かにこれならプライマリキー値を指定しない新規レコードで、その値がデフォルトの 0 になるのは納得です。

実はテーブルのコピーを行う SQL 命令はもう一つあります。
こちらはテーブルの形だけコピーし、レコードは拾って来ない「CREATE TABLE new_t LIKE current_t」というものです。
この命令では、上で問題になった部分も元のまま「PRIMARY KEY AUTO_INCREMENT」で引っ張ってくれます。

以上から導き出される結論が「テーブルの全コピーを取るには CREATE TABLE LIKE で定義をコピーしてから INSERT INTO SELECT でレコードをコピーするという二段階で行うべし」というものです。
まあ、それ以前に作業した後にはちゃんと確認しろ(この場合は SHOW CREATE TABLE)という事なんですが…

ということで、今回の(イタい)レポートはこれで終わります。

phpMyAdminの環境保管領域が完全に設定されていない… エラー

おひさしぶりです。
不定期開催の今回のネタは phpMyAdmin on Ubuntu 14.04 LTS です。

とりあえずパッケージ入れてみたんですよ。
そしたら、ログインすると「phpMyAdmin 環境保管領域が完全に設定されていないため、いくつかの拡張機能が無効になっています。理由についてはこちらをご覧ください。」と表示されたわけです。
コマンドラインから見てみると、環境保管領域に使われる phpmyadmin データベースが無かったので CREATE TABLE して…ダメ。
中身を入れればいいのかと /usr/share/doc/phpmyadmin/examples/create_tables.sql.gz を流し込んでみても…ダメ。
権限の問題かと CREATE USER と GRANT を駆使しても…ダメ。

…結局、/etc/phpmyadmin/config.inc.php の中身で環境保管領域用のテーブル名を定義している部分で「_」(下線、アンダースコア)が一つ不足していたのが原因でした。
直接そのファイルを直すかわりに /etc/phpmyadmin/conf.d/env-storage.php を以下の内容で作りました。
ほとんどは /usr/share/doc/phpmyadmin/example/config.example.inc.php からコピーしたものです。


<?php // ここから

$i = 1; // インデックスを 1 に戻して(1-ベースなので最初が 1)
/* Storage database and tables */
$cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
$cfg['Servers'][$i]['relation'] = 'pma__relation';
$cfg['Servers'][$i]['table_info'] = 'pma__table_info';
$cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
$cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
$cfg['Servers'][$i]['column_info'] = 'pma__column_info';
$cfg['Servers'][$i]['history'] = 'pma__history';
$cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
$cfg['Servers'][$i]['tracking'] = 'pma__tracking';
$cfg['Servers'][$i]['designer_coords'] = 'pma__designer_coords';
$cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
$cfg['Servers'][$i]['recent'] = 'pma__recent';
/* Contrib / Swekey authentication */
// $cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf';
?> // ここまで

それから、試行錯誤している時に /etc/phpmyadmin/config.inc.php を見ていたところ、設定ファイルは次のような順序で読み込まれるようでした。(後ろにある内容が優先)

  1. /var/lib/phpmyadmin/config.inc.php (phpMyAdmin/setup で変更されるファイル)
  2. /etc/phpmyadmin/config-db.php
  3. /etc/phpmyadmin/config.inc.php (今回の悪の元凶)
  4. /etc/phpmyadmin/conf.d/*.php (設定を追加した場所)

…以上で今回のレポートを終わります。

ufw によるファイアウォール

今回は ufw です。
以前からパッケージにあるのは知っていたのですが、VPS の Ubuntu 14.04 LTS に標準で入ってるのを触るまでスルーしてました。

ファイアウォールと言えば設定した途端に SSH が使えなくなって、泣きながら実マシンのコンソールまで駆け付けるハメになる、というのはよく有る話です。
今回はクラウドの VPS を使っており、何故か Web の管理画面からコンソールが見られないという、危険が危ない状態だったので細心の注意を払いました。

ドキュメントと man を読み漁り、iptables の一番上に作業マシンの IP 許可設定を突っ込み、ufw にも IP の許可と SSH の許可を設定した上でドキドキしながら「ufw enable」を実行しました。

結果的には ufw の設定だけでも十分でしたが、iptables に書いておいた設定が上に残っているのを見て、転ばぬ先の杖は大事だと実感しました。

ちなみに ufw の使い方は、設定を ufw コマンドで行い、準備ができたら「ufw enable」で有効化するという手順になります。とりあえず、次のコマンドを設定しておけば SSH が切れる心配は少ないと思います。


ufw allow 22/tcp
ufw allow from 作業マシンのIPアドレス

指定内容は「ufw status」で見られます。
詳細な形式は(私の所では) /lib/ufw/user.rules に記録されていました。

以上。いつものように備忘メモでした。

Emacs キーバインド その3 矩形リージョン・レジスタ

さて、第3回です。
今回は、使う場面が限定されるけど、使いこなすとスゴいシリーズです。

いろいろ
「消去」は「キル」と違って、内容を覚えずに消します。

  • M-SPC :カーソル前後の空白文字をまとめて消去
  • C-x o :前後の空行を 1行だけ残して消去
  • C-h w COMMAND :コマンド COMMAND を実行するキーバインドを表示

矩形リージョン
通常のリージョン(カーソルとマークの間の文字全て)ではなく、カーソルとマークが左上と右下に来るような矩形(四角形)の中の文字だけを対象とします。
よって、カーソル・マーク間の行に含まれていても、矩形の外側の文字は対象になりません。

  • C-x r k :矩形をキルして右側の文字列を左へ(詰めるように)移動
  • C-x r y :最後にキルした矩形を、カーソル位置を左上としてヤンク(ヤンク矩形範囲内の文字列は右へ(逃がすように)移動)
  • C-x r d :矩形内を消去して右側の文字列を左へ(詰めるように)移動
  • C-x r c :矩形内を空白で上書き(範囲外への影響は無し)
  • C-x r o :矩形サイズの空白を挿入して、矩形内の文字列を右へ(逃がすように)移動

レジスタ
レジスタとは 1文字の名前(a、b、x、y など)が付いた保管場所です。
新しい物を入れると、前に入っていた物は消えてしまいます。

以下の REG_CHAR はレジスタを表す 1文字の名前とします。

  • C-x r s REG_CHAR :リージョン内容をレジスタにコピー
  • C-x r i REG_CHAR :レジスタ内容をカーソル位置に挿入
  • C-x r C-SPC/C-@ REG_CHAR :カーソル位置をレジスタに記録
  • C-x r j REG_CHAR :レジスタに記録されている位置へカーソルを移動
  • M-x list-register :空でないレジスタの内容を一覧表示