2006-04-10 (Mon)

* 大量のテストメールを送信するシェルスクリプト

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト] [メール]

とあるサーバのテストで大量のダミーメールを一気に送信する必要が出た。大量と言ってもたかだか千通程度なので、簡単にシェルスクリプトを書いて送信。

$ max=1024; date=`date`; for i in `seq 1 $max`; do echo $date |mail -s "Mail Test $i/$max $date" landscape@example.jp; done;

表題には "Test Mail 1/1024 Mon Apr 10 12:58:19 JST 2006" などという文字列が、本文には日付だけがセットされる。Perl で書いても良かったけど、このマシンには SMTP 系のモジュール入れてたかどうかわからなかったのでシェルスクリプトにした。mail コマンドを使ってるので、ローカルで sendmail などの MTA が動いている必要がある。

・・・あれ? こういうローカルのプログラムからのメール配送要求を受け付けるプログラムも MTA (Mail Transfer Agent) でいいんだっけ? MDA (Mail Delivery Agent) または MUA (Mail User Agent) の方が適切? この場合の mail コマンドはメールをローカルで動いている MTA に渡すのが仕事なんだから、Mail User Agent が適切かな。

今気づいたけど、これはシェルスクリプトというかコマンドラインだね。

2006-02-27 (Mon)

* シェルのパラメータ展開でスクリプト自身のファイル名を取得する

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [bash] [シェルスクリプト]

2006-02-22 に書いた「シェルスクリプト自身のファイル名を取得できる特殊変数 $0」についてメールで指摘を頂いた。ありがとうございます。

私はスラッシュなどのパス指定文字列を除去するために basename コマンドを使っていたのだが、そうしなくても済む方法があるとのこと。

http://sonic64.com/2006-02-22.html
で言及さている 「basename コマンドを使わなくても済む特殊変数」ですが、
echo "This script name is ${0##*/}" でお望みの動作になるはずです。

今回の例に限っては ${0#*/} でも良いですが、Full Path を指定して実行した場合に意図しない結果になると思われます。
man bash 内を ${parameter##word}で検索してみてください。

試してみると、確かに ${0##*/} でスクリプト自身の名前を取得できた。

#!/bin/sh

echo "This script name is $0"
echo "This script name is `basename $0`"

echo "This script name is ${0##*/}"
echo "This script name is ${0#*/}"

上記を get_script_name.sh という名前で保存し、相対パス指定で実行した結果。

$ ./get_script_name.sh
This script name is ./get_script_name.sh
This script name is get_script_name.sh
This script name is get_script_name.sh
This script name is get_script_name.sh.

1行目の $0 のみを指定したもの以外は、見事にファイル名を取得できている。
次に、フルパスでスクリプトファイルを指定して実行してみる。

$ /home/sonic64/tmp/get_script_name.sh
This script name is /home/sonic64/tmp/get_script_name.sh
This script name is get_script_name.sh
This script name is get_script_name.sh
This script name is home/sonic64/tmp/get_script_name.sh

指摘の通り、3行目の ${0##*/} は見事にファイル名だけを取得できているが、4行目に書いた ${0#*/} だと余計な文字が入ってしまっている。

- ${parameter##word} の意味を調べる

さて、${parameter##word} ってどういう意味だろう? man bash して ${parameter##word} を検索してみる。あった。

${parameter##word}

word が展開され、パス名展開の場合と同じようなパターンを作ります。このパターンが parameter の値の先頭部分とマッチする場合、展開して得られる値は parameter を展開した値から最短一致パターン (``#''の場合) または最長一致パターン (``##'' の場合) を取り除いたものになります。 parameter が @ または * である場合、パターンを削除する操作は全ての位置パラメータに順番に適用され、展開結果はリストとして得られます。 parameter が @ または * が添字になっている配列変数である場合、パターンを削除する操作は配列の全ての要素に順番に適用され、展開結果はリストとして得られます。

上記を一度読んでもすぐには理解できなかった。順番に読んでいく。

・word に指定されたものについて、パス名展開と同じような展開がなされてパターンが作られる。
・パターンが parameter の先頭とマッチする場合、そこからパターンを削除する。
・word の前が ## の場合は最長一致、# の場合は最短一致。

わかってきた。あとはパス名展開の定義を調べておこう。

パス名展開

* 空文字列を含む、任意の文字列にマッチします。

ということは、*/ は「空文字を含む任意の文字列にスラッシュが続く」という文字列に展開されるということか。で、その文字列が最長一致で parameter の先頭とマッチしたときに parameter から取り除かれるんだな。なるほど、やっとわかった。こういう意味だったのか。勉強になったなあ。

2006-02-22 (Wed)

* シェルスクリプト自身のファイル名を取得できる特殊変数 $0

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [bash] [シェルスクリプト]

パラメータ $0 でシェルスクリプト自身の名前を取得できる。

#!/bin/sh

echo "This script name is $0"
echo "This script name is `basename $0`"

上記スクリプトを get_script_name.sh というファイル名で保存し、実行。

$ ./get_script_name.sh

This script name is ./get_script_name.sh
This script name is get_script_name.sh

スクリプトの呼び出し方によってはパス指定文字列が入ってしまうので、二行目では basename コマンドでファイル名部分だけを取り出している。basename コマンドを使わなくても済む特殊変数って無いのかな? man bash してみたけど見つけられなかった。

追記。
2006-02-27 に「シェルのパラメータ展開でスクリプト自身のファイル名を取得する」という記事を書いた。
basename コマンドを使わなくても、${0##*/} でスクリプト自身の名前を取得できる。

2005-12-07 (Wed)

* gzip/bzip2 は環境変数 GZIP/BZIP2 で圧縮レベルのデフォルト値を指定できる

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [unix] [Apache] [シェルスクリプト]

2005-12-04 に書いた「gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮」について、いくつか指摘を頂いた。ありがとうございます。本当に、当サイトにも早くコメント欄を付けるべきだなあ・・・。

まずは掲示板 (wiki でしたっけ?) の「イナモデ」に書かれていた指摘。
http://inamode6.tokuhirom.dnsalias.org/show/1827
わざわざ再圧縮するんであればbzip2使えばgzip -9より圧縮できるだろう。

今回の圧縮対象はテキストのログなので、確かに bzip2 の方が圧縮率は高くなるでしょうね。でも、以下の2つの理由で bzip2 は使いませんでした。

1. さくらインターネットが採用しているログ圧縮やローテーションの仕組みがわからないので、その仕組みによっては bzip2 で圧縮したファイルをさらに gzip 圧縮するとういう動きをするかもしれなかった。gzip や bzip2 コマンドならば、圧縮済みファイルだったら何もしないという動きをすると思いますが、さくらインターネットが採用している仕組みがわからなかったので現状維持で gzip としました。

2. さくらインターネットでは webalizer でアクセスログからレポートを生成しているが、webalizer が bzip2 で圧縮されたログを読めるかどうかわからないので不安だった。たぶん大丈夫なんでしょうけど、そこまで冒険することもないかなあと。

> ところで、この圧縮率の差が生まれる原因って、logrotate か rotatelogs あたりのオプションにあるのかな? それとも独自のシェルスクリプトか何かのオプションが原因なのかな。

man 1 gzipに「The default compression level is -6 (that is, biased towards high compression at expense of speed). 」と書いてあるって。

あ、疑問の趣旨は、logrotate や rotatelogs を使ってるとして、そこから呼びだされている gzip や zlib の圧縮レベルを指定してるのはどこなのかな、というものです。それで、logrotate や rotatelogs は圧縮レベルを指定できるオプションを持ってるのかな、というのが元の文の意味です。わかりにくかったですね。

gzip コマンド自体が圧縮レベルを指定できるオプションを持ってるのは知ってたので、とりあえずレベル最強で圧縮し直すという単純な解決法をとったわけですしね。

そういえば、その昔に linux の logrotate を使ってた頃は、何もしなくても gzip -9 と同等の圧縮レベルが指定されてたような気がします。気のせいかもしれないけど・・・。

- gzip/bzip2 コマンドは環境変数 GZIP/BZIP2 でオプションのデフォルト値を指定できる

freebsdグループ - TransFreeBSDの日記 - gzipの圧縮レベルを変えるには
http://freebsd.g.hatena.ne.jp/TransFreeBSD/20051205/p2
私も、newsyslogとかにオプションとかあるかもと思って、ちょっと探したけどなかった。で、gzipは環境変数「GZIP」にオプションを設定できるらしい。

On-line Manual of ”gzip”

というわけで、newsyslogとかlogrotateとかがこの環境変数を消していなければ、crontabに設定すればわたるはず。

あと、さくらインターネットの専用サーバはFreeBSDが選べますが、レンタルサーバのOSは何だろう。FreeBSDならbzip2の方がお得だと思います。

これは知りませんでした。確かに man ページに書いてありました。「よく使ってるコマンドでも man を読むと発見がある」という好例ですね。勉強になりました。

Manpage of GZIP
http://www.linux.or.jp/JM/html/GNU_gzip/man1/gzip.1.html
環境変数
環境変数 GZIP に gzip のデフォルトのオプションセットを入れることができる。これらのオプションは最初に解釈されるので、明示的なコマンドラインパラメータで上書きすることができる。例を示す。
      sh の場合:    GZIP="-8v --name"; export GZIP
      csh の場合:  setenv GZIP "-8v --name"
      MSDOS の場合: set GZIP=-8v --name

bzip2 コマンドも同じように環境変数 BZIP2 と BZIP でオプションのデフォルト値を指定できるようですね。

Manpage of bzip2
http://www.linux.or.jp/JM/html/bzip2/man1/bzip2.1.html
bzip2 は環境変数 BZIP2, BZIP からこの順番で引き数を読み込み、コマンドラインから読み込まれた引き数よりも先に処理する。これはデフォルトの引き数を与える便利な方法である。

ただ、やっぱりさくらインターネットのログ切り出しと圧縮の仕組みがわからないので、2005-12-04 の再圧縮スクリプトを cron で実行という方法を取ろうと思います。運営側で圧縮レベルを上げるという対処をしてくれるかどうかもわからないですしね。とりあえず後でサポートに要望を出しておこうとは思ってます。

そうそう、さくらインターネットのレンタルサーバは FreeBSD でした。uname -a したら以下のように出力されました。

$ uname -a
FreeBSD www****.sakura.ne.jp 4.10-RELEASE-p16 FreeBSD 4.10-RELEASE-p16 #0: Thu Aug  4 11:51:09 JST 2005    root@******.sakura.ne.jp:/usr/src/sys/compile/SAKURA8S  i386

2005-12-04 (Sun)

* gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [unix] [Apache] [シェルスクリプト]

当サイト http://sonic64.com/ は、さくらインターネットのレンタルサーバーサービスを利用している。レンタルなので、ディスク容量が足りなくなっても自宅のクライアント PC と違って簡単に増設することはできない。ディスク容量は貴重だ。ディスクの1バイトは血の一滴。

で、さくらインターネットでディスクの無駄遣いを見つけた。さくらインターネットでは各ユーザーに apache のアクセスログが提供されていて、最新のログ以外は gzip で自動的に圧縮される。そのときの gzip のレベルが -9 になっていないので、ディスクスペースが無駄になっている。

非常に細かいんだけど、gzip の圧縮レベルがデフォルトの -6 と -9 では、圧縮後のファイルサイズで一日あたり100KB くらいの違いが出てくる。ログは1年分保存されるので、100KB * 365 = 36.5MB の差がある。これはちょっと無視できない。

というわけで、gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮するスクリプトを書いた。

#!/bin/sh

find $HOME/log -type f -maxdepth 1 -mtime +1 -mtime -2 -regex .*.gz |xargs gunzip
find $HOME/log -type f -maxdepth 1 -mtime +1 -mtime -2 ! -regex .*.gz |xargs gzip -9

やってることはものすごく単純。単に2日前の圧縮ファイルを find で探し出して展開、その後最高の圧縮レベルを指定した gzip で再圧縮してるだけ。直近1日以内のファイルはまだ gzip されてないので除外。2日以上前のファイルは、このコマンドで処理済みなので除外。

これを cron で一日一回実行するために crontab に仕込んでおく。一応 nice コマンドでプロセスの優先度を最低にしておく。

# recompress with max compression level
5 6 * * * nice -19 $HOME/script/recompress_log.sh

ここまでやって思ったんだけど、これってさくらインターネットのサポートに要望として出した方がいいかな。そうすれば全ユーザーが幸せになれるな。よし、あとでサポートにメールしておこう。

ところで、この圧縮率の差が生まれる原因って、logrotate か rotatelogs あたりのオプションにあるのかな? それとも独自のシェルスクリプトか何かのオプションが原因なのかな。

追記。
2005-12-07 に『「gzip 圧縮されたファイルを最強の圧縮レベルで再圧縮」への指摘とその反応』を書いた。

2005-10-25 (Tue)

* 一定以上のサイズを持つファイルを gzip 圧縮するシェルスクリプト

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト]

一定の値以上のサイズを持つファイルを gzip 圧縮するシェルスクリプト。

どんなときに使うかというと、サイズの大きい html や xml をあらかじめ圧縮しておき、HTTP リクエストヘッダ Accept-Encoding: gzip を送ってきているクライアントには圧縮済みファイルを返す、ということをやりたいとき。以下の記事参照。

2005-09-14 「Content Negotiation でリクエストに応じて gzip 圧縮ファイルを返す」
2005-09-15 「mod_rewrite でリクエストに応じて gzip 圧縮ファイルを返す」

当サイト Landscape を置いているサーバ環境では mod_gzip が使えず、圧縮するには CGI でやるか、あらかじめ静的なgzip 圧縮ファイルを用意しておくことが必要。

- 一定の値以上のサイズを持つファイルを gzip 圧縮するシェルスクリプト

html を生成したあとに以下のスクリプトで圧縮している。

#!/bin/sh
compress_target=`find $HOME/public_html/log.sonic64 -maxdepth 1 -size +200k ! -name '*.gz'`
compress_target="$compress_target `find $HOME/public_html/log.sonic64 -maxdepth 1 -name 'index.html' -or -name 'cl.xml'`"

for file in $compress_target; do
  echo "compressing $file"
  gzip -9 -c $file >$file.gz
  touch -r $file $file.gz
  chmod 604 $file.gz
done

圧縮対象ファイルの条件は、ファイルサイズが 200 KB 以上で、かつファイル名の末尾が .gz でないもの。さらに、それに加えて index.html と cl.xml は無条件に圧縮対象としている。2回 find を実行している理由は、複数の条件を書いて一回の find にまとめようとしたけど意図したように動かなかったため。こういう仕事を楽にするためのスクリプトは、無駄なく書くことよりも速攻で書いて動かす方が重要だから妥協した。

で、条件に合致するファイルをリストにした後は一つずつ gzip -9 で圧縮し、touch でもとのファイルとタイムスタンプを一致させ、chmod でパーミッションを整えてる。umask でもいいんだけど、明示的にやりたかったので chmod してる。

2005-07-06 (Wed)

* find コマンドの -mtime は +1 でも2日前のファイルが対象

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [unix] [シェルスクリプト]

find コマンドの -mtime は +1 でも2日前のファイルが対象となる。つまり、(n + 1) 日前のファイルが対象。n は +0 を指定しても、一日以上前のファイルが対象になる。

- find が古いファイルを検索してくれない

とあるテスト用 DB があり、定期的にバックアップを取っている。バックアップ先はディスク容量に余裕がないので、以下のような古くなったバックアップファイルを消す cron を設定しておいたのだが、どうもうまく動いていない模様。今日は7月6日なのに、7月4日のファイルがまだ残っているのだ。

6 0 * * * /usr/bin/find /cygdrive/c/DB_BACKUP -maxdepth 1 -type f -mtime +1 |/usr/bin/xargs /bin/rm -f

毎日午前0時6分に起動。一日以上経過したバックアップファイルを find で探しだし、xargs から起動する rm に渡して削除するというシンプルなコマンド。2003-04-16 の「n 日以前のタイムスタンプを持つファイルを消す」にも書いた。cron と find があるマシンでは重宝している。なんで今になってうまく動かないことが発覚したんだろう? 実行環境が Windows2000 + cygwin だから? まさか。

まずは原因を切り分けよう。xargs に渡す前の find だけを実行してみると、古くなったバックアップファイルが find の対象になっていない。なんでだろう? mtime の指定が間違っているのだろうか? find の man を見てみる。

Manpage of FIND
http://www.linux.or.jp/JM/html/GNU_findutils/man1/find.1.htm ...
-mtime n
ファイルのデータが最後に修正されたのが n*24 時間前なら真。

うーん、間違ってないよね。試しに -mtime +1 を -mtime 1 にしたところ、古いバックアップファイルが find の検索対象となった。ここが間違ってるのかな?

判別式

数値の引き数を

+n
と表わすと n より大きいことを示し、

-n
と表わすと n より小さいことを示す。

n
はちょうど n と等しいことを示す。

別におかしくないよなあ。-mtime +1 なら、24時間以上前なら真ってことでしょ? それとも何か勘違いしてる? ちょっとテストしてみるか。

- テスト

find の削除対象にならないタイムスタンプを持つファイルを作成してみる。

その前に、該当マシンのマシンデイトの確認。
$ date
Wed Jul  6 10:25:14    2005
よし、合ってるね。NTP で合わせてるんだから当たり前だけど。

Windows エクスプローラで確認すると、該当のファイルのタイムスタンプ情報は以下の通り。
作成日時: 2005年7月4日、14:00:16
更新日時: 2005年7月4日、14:00:16
アクセス日時: 2005年7月6日、10:06:52

-mtime は更新日時のことなので、-mtime +1 の「一日以上前のファイル」という 条件には合致するはずだ。

まず touch で当該タイムスタンプを持ったファイルを生成し、ls -l でタイムスタンプを確認。

$ touch -m -t 200507041400.16 200507041400.16

$ ls -l
-rw-r--r--    1 Administ なし            0 Jul  4 14:00 200507041400.16

問題なく生成された。ファイル名も 200507041400.16 にした。

では find 実行。

$ find -mtime 1
./200507041400.16

$ find -mtime +1

あれ? なんで? 明らかに一日以上経過しているファイルなのに、+1 だと find の対象にならず、1 だと find の対象になる。この Windows2000 + cygwin 環境の find がおかしいのか? 念のため TurboLinux Server 6.5 でも同じことをやってみる。

[hiroaki@pro ~/test/find_test]$ find -mtime 1
./200507041400.16

[hiroaki@pro ~/test/find_test]$ find -mtime +1

同じだ・・・。なんでだろう?
+1 の条件を変えて +0 でやってみると検索対象になった。そういうことか。つまり、+0 を指定したら、0 * 24 = 0 なので、全てのファイルが対象になると思うところだが、そうはならないんだろう。

試してみる。touch now で現在時刻の mtime を持つファイルを作成し、mtime +0 で find の検索対象になるか試す。

$ touch now

$ find -mtime +0
./200507041400.16

find の検索対象にならない。そういうことか。

- find の -mtime +n のまとめ

find の -mtime +n の検索対象となるファイルは以下の通り。

mtime +0 今から24時間以上前のファイル
mtime +1 今から48時間以上前のファイル
mtime +2 今から72時間以上前のファイル

(n + 1) * 24 ってことだね。man にそう書いてあれば良かったのに。あとでスクリプトを書いて試してみよう。

- 一時間ずつずらしたタイムスタンプを持つファイル群を生成する Perl One Liner を使ってテスト

以下を実行すると、200個のファイルが生成される。ファイルのタイムスタンプはそれぞれ、一時間前、二時間前、三時間前・・・と200時間前までさかのぼったものを utime 関数を使ってセットする。ファイル名は epoc time そのもの。で、これを find に検索させて、どこが検索対象の境界になるかを調べようと言うわけだ。

$ perl -le 'for (1..200) { $fname = (time - $_ * 3600); open(FH, ">$fname"); print FH ''; close(FH); utime($fname, $fname, $fname); }'

いきなり200個のファイルを作っても結果の検証がしにくくなるだけなので、まずは 35個だけ作ってみる。

$ perl -le 'for (1..35) { $fname = (time - $_ * 3600); open(FH, ">$fname"); print FH ''; close(FH); utime($fname, $fname, $fname); }'

$ ls -ltr
total 35
-rw-r--r--  1 aqua なし 3 Jul  5 12:39 1120534797
-rw-r--r--  1 aqua なし 3 Jul  5 13:39 1120538397
-rw-r--r--  1 aqua なし 3 Jul  5 14:39 1120541997
-rw-r--r--  1 aqua なし 3 Jul  5 15:39 1120545597
-rw-r--r--  1 aqua なし 3 Jul  5 16:39 1120549197
-rw-r--r--  1 aqua なし 3 Jul  5 17:39 1120552797
-rw-r--r--  1 aqua なし 3 Jul  5 18:39 1120556397
-rw-r--r--  1 aqua なし 3 Jul  5 19:39 1120559997
-rw-r--r--  1 aqua なし 3 Jul  5 20:39 1120563597
-rw-r--r--  1 aqua なし 3 Jul  5 21:39 1120567197
-rw-r--r--  1 aqua なし 3 Jul  5 22:39 1120570797
-rw-r--r--  1 aqua なし 3 Jul  5 23:39 1120574397
-rw-r--r--  1 aqua なし 3 Jul  6 00:39 1120577997
-rw-r--r--  1 aqua なし 3 Jul  6 01:39 1120581597
-rw-r--r--  1 aqua なし 3 Jul  6 02:39 1120585197
-rw-r--r--  1 aqua なし 3 Jul  6 03:39 1120588797
-rw-r--r--  1 aqua なし 3 Jul  6 04:39 1120592397
-rw-r--r--  1 aqua なし 3 Jul  6 05:39 1120595997
-rw-r--r--  1 aqua なし 3 Jul  6 06:39 1120599597
-rw-r--r--  1 aqua なし 3 Jul  6 07:39 1120603197
-rw-r--r--  1 aqua なし 3 Jul  6 08:39 1120606797
-rw-r--r--  1 aqua なし 3 Jul  6 09:39 1120610397
-rw-r--r--  1 aqua なし 3 Jul  6 10:39 1120613997
-rw-r--r--  1 aqua なし 3 Jul  6 11:39 1120617597
-rw-r--r--  1 aqua なし 3 Jul  6 12:39 1120621197
-rw-r--r--  1 aqua なし 3 Jul  6 13:39 1120624797
-rw-r--r--  1 aqua なし 2 Jul  6 14:39 1120628397
-rw-r--r--  1 aqua なし 2 Jul  6 15:39 1120631997
-rw-r--r--  1 aqua なし 2 Jul  6 16:39 1120635597
-rw-r--r--  1 aqua なし 2 Jul  6 17:39 1120639197
-rw-r--r--  1 aqua なし 2 Jul  6 18:39 1120642797
-rw-r--r--  1 aqua なし 2 Jul  6 19:39 1120646397
-rw-r--r--  1 aqua なし 2 Jul  6 20:39 1120649997
-rw-r--r--  1 aqua なし 2 Jul  6 21:39 1120653597
-rw-r--r--  1 aqua なし 2 Jul  6 22:39 1120657197

よしできた。ls のオプションに指定した -ltr の意味は、-l がおなじみのパーミッションとタイムスタンプを表示の有効化、-t は「アルファベット順ではなく修正時刻 (inode の `mtime') でのソートの有効化、-r が逆順にするオプション。

ちなみに現在時刻と、実行環境と find のバージョンは以下の通り。
$ date
Wed Jul  6 23:40:41    2005

$ uname -a
CYGWIN_NT-5.1 a64 1.5.17(0.129/4/2) 2005-05-25 19:38 i686 unknown unknown Cygwin

$ find --version
GNU find version 4.2.11-CVS

で、find . -mtime +0 してみる。私の予想通りなら、24時間以上経過しているファイルのみが対象になるはずだ。

$ find . -mtime +0
./1120534797
./1120538397
./1120541997
./1120545597
./1120549197
./1120552797
./1120556397
./1120559997
./1120563597
./1120567197
./1120570797
./1120574397

うーん、これじゃ24時間以上経過したファイルかどうなのかがよくわからないよね。find の出力結果をファイル名じゃなくてタイムスタンプにしてみよう。

$ find . -mtime +0 -printf "%t\n"
Tue Jul  5 12:39:57 2005
Tue Jul  5 13:39:57 2005
Tue Jul  5 14:39:57 2005
Tue Jul  5 15:39:57 2005
Tue Jul  5 16:39:57 2005
Tue Jul  5 17:39:57 2005
Tue Jul  5 18:39:57 2005
Tue Jul  5 19:39:57 2005
Tue Jul  5 20:39:57 2005
Tue Jul  5 21:39:57 2005
Tue Jul  5 22:39:57 2005
Tue Jul  5 23:39:57 2005

うん、"Tue Jul 5 23:39:57 2005" のタイムスタンプを持つファイルは対象になっておらず、確かに現在時刻から24時間以上経過したものだけ対象になってるね。なんでこういう仕様なんだろう? 仕様さえわかればまあいいか。

テストを終えてふと思ったこと。ここまでテストするんだったら find のソースを読んだ方が早い気がする。周りから攻めようとするのはエンジニアらしくないよね。次からはソースを読むようにしよう。

2005-04-17 (Sun)

* Bloglines の登録データをバックアップする シェルスクリプト

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [バックアップ] [シェルスクリプト] [RSS]

bloglines に登録したサイト一覧を定期的に自動で OPML にエクスポートしてバックアップする方法。

- Bloglines の登録データをバックアップするシェルスクリプトの概要

http://www.bloglines.com/export に Bloglines の Cookie 付きで HTTP GET し、返ってきたレスポンスを保存する。あとはこれを cron などで定期的に自動実行すれば OK。

やっぱり自分のデータは手元に置いておきたい。他の RSS リーダに乗り換えるときに手元に OPML があれば楽だろうから、定期的なバックアップは取っておきたいし。

- Bloglines の登録データは OPML でエクスポートできる

Bloglines のアカウントを持っている人なら、http://www.bloglines.com/export にアクセスすると Bloglines で購読しているサイトの一覧を OPML でエクスポートすることができる。http://www.bloglines.com/export には特にユーザを識別するようなクエリが付いていない。おそらく Cookie にユーザーデータが入っていて、それでユーザを識別しているんだろう。Web アプリケーションの定石だね。

2005-04-18 追記。「Bloglines から エクスポートした OPML が文字化けする現象への対処」という記事を書いた。

- Live Http headers で HTTP トランザクションの中身を調べる

どんな Cookie が Bloglines に送信されているかを見てみる。

Firefox を起動し、2005-04-16 の「Live Http headers - HTTP ヘッダ表示ツール」で書いた Live Http headers で HTTP ヘッダを表示させながら Bloglines にアクセスする。Bloglines の登録に使ったメールアドレスとか、色々それらしき情報を見ることができた。私のアカウントを保護するため、一部伏せ字や文字の削除をした。

http://www.bloglines.com/export

GET /export HTTP/1.1
Host: www.bloglines.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; ja-JP; rv:1.7.6) Gecko/20050318 Firefox/1.0.2
Accept: text/xml,application/xml,application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: BloglinesLang=0; BloglinesTracker=MXk4zPhcOWV3tjEJV14gNY; BloglinesEmail=blog****@**********.com; clickedFoldersubtree=; clickedFoldermanagetree=

HTTP/1.x 200 OK
Date: Sun, 17 Apr 2005 12:47:22 GMT
Server: Apache/1.3.31 (Unix)
Cache-Control: no-cache, must-revalidate, no-cache="Set-Cookie", private
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Set-Cookie: BloglinesLang=0; path=/; expires=Monday, 17-Apr-2006 23:59:59 GMT
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/xml; charset=utf-8

ちなみに Cookie を無効にして http://www.bloglines.com/export にアクセスすると、ログイン画面に飛ばされた。つまり、http://www.bloglines.com/export に Cookie 付きでアクセスすればいいということ。

Cookie の有効期限は expires=Monday, 17-Apr-2006 23:59:59 GMT なので、一年間だ。

- Bloglines の登録データをバックアップするシェルスクリプト

Perl などで書いても良いけど、これくらいだったらシェルスクリプトで十分。wget で HTTP リクエストを投げ、date コマンドで7世代分残す。

wget で送信する Cookie は --header='' に記述する。さっきの Live Http headers で表示させた Cookie をそのまま使えばいい。

#!/bin/sh

wget --header='Cookie: BloglinesLang=0; BloglinesTracker=MXk4zPhcOWV3tjEJV14gNY; BloglinesEmail=blog****@**********.com; clickedFoldersubtree=; clickedFoldermanagetree=' http://www.bloglines.com/export -O $HOME/etc/bloglines`date +%u`.opml

ちなみに、date コマンドの引数の +%u は 月曜日を1とし、日曜日を7とする数字で、毎日出力が変わる。7世代分残したいからこれを使った。
date --help の結果から抜粋。
%u  day of week (1..7);  1 represents Monday"

上記スクリプトを export_bloglines_opml.sh というファイル名で保存する。
crontab に登録し、毎日 01:01 に実行する。
1 1 * * * nice -19 /home/aqua/script/export_bloglines_opml.sh

ホームディレクトリは 2004-11-09 の「GMail をバックアップストレージとして使う」で書いたスクリプトで毎日バックアップしている。これでとりあえず大丈夫でしょう。Cookie の有効期限までは自動でバックアップがとれる。

- public 設定の人は Cookie 不要

2005-04-19 追記。Public 設定にしておけば Cookie 不要で OPML をエクスポートできる。

void GraphicWizardsLair( void ); // http://www.otsune.com/diary/ の otsune さんから以下のメールを頂いた。ありがとうございます。

public設定になっているIDの人は
http://www.bloglines.com/export?id=hoge
というURLでもエクスポートできるようです。

なるほど。public ってどこで設定するんだろう? ちょっと調べてみよう。

わかった。Bloglines にログインして、右上の「アカウント」の「ブログ設定」から Public 属性を設定できる。パンくずリストで言うと、「ホーム > アカウント > ブログ設定」。ここでユーザ名を登録して、「ブログの公開設定」を「はい」にすれば良い。

ブログの公開設定:
○ はい、ブログとブログロールを公開します
● いいえ、ブログを公開しません

あなたのアカウントを非公開にすると、あなだだけ見ることができます。
公開すると、あなたのブログにアクセスできます http://www.bloglines.com/blog/************

やり方も分かったし、Public 設定を試してみたい。テスト用のアカウントを登録して Public にしてみよう。アカウント名は最近鉄拳5のおまけにも付いた StarBlade がいいかな。

http://www.bloglines.com/export?id=StarBlade
<?xml version="1.0" encoding="utf-8"?>
<opml version="1.0">
<head>
    <title>Bloglines Subscriptions</title>
    <dateCreated>Mon, 18 Apr 2005 15:03:26 GMT</dateCreated>
    <ownerName>StarBlade</ownerName>
</head>
<body>
  <outline title="Subscriptions">
    <outline title="Bloglines | News" htmlUrl="http://www.bloglines.com" type="rss" xmlUrl="http://www.bloglines.com/rss/about/news" />
    <outline title="Landscape - エンジニアのメモ" htmlUrl="http://sonic64.com/" type="rss" xmlUrl="http://sonic64.com/cl.xml" />
    <outline title="Landscape - エンジニアのメモ" htmlUrl="http://sonic64.com/" type="rss" xmlUrl="http://sonic64.com/cl-full.xml" />
    <outline title="@IT" htmlUrl="http://www.atmarkit.co.jp" type="rss" xmlUrl="http://www.atmarkit.co.jp/rss/rss.xml" />

</outline>
</body>
</opml>

なるほど、そのまま OPML でエクスポートできた。確かにこれならいちいち Cookie をセットしなくていいので簡単かつ確実に OPML をエクスポートできる。公開してる人にはこっちの方が楽で良いね。秘密主義の私にはできないけど。、

ちなみに、RSS 登録時に「非公開」扱いにしたサイトは表示されない。
フィードを登録していることを:
  公開
  非公開

非公開の登録はブログロールには表示されません

もうひとつ分かったこと。「ホーム > アカウント > ブログ設定」でユーザ名だけ登録しておいて「いいえ、ブログを公開しません」を選んで http://www.bloglines.com/export?id=************ にアクセスすると Internal Server Error になる。Bloglines 開発チームの考慮漏れかな?

いろいろ調べて好奇心を満たした後、ふと Bloglines を見ると「共有」という怪しげなタブがあった。見てみると、試したことがちゃんと書いてあった。最初に見ておけば良かったね。

Bloglines | あなたの登録をシェア
http://www.bloglines.com/help/share
ホーム > ヘルプ > ブログロールウィザード

Bloglinesではあなたが登録しているフィードを2通りの方法でほかの人と共有することができます。1つ目の方法はあなたのプロファイルで共有を許可する方法です。共有設定をオンにすると、あなたが登録しているフィードがすべて公開されます。登録フィードの一覧を表示した状態で編集リンクをクリックすれば個別のフォルダや登録フィードを非公開にすることができます。非公開フォルダや非公開登録フィードはブログロールや/blogビューには表示されません。

/public

ブログロールに加えて、公開登録フィードはBloglinesのウェブサイトで公開されます。あなたの公開フィードは次のURLから参照できます:

http://www.bloglines.com/public/StarBlade
あなたの公開フィードはOPMLファイルとしてエクスポートされます。

Public 設定にしていれば http://www.bloglines.com/export?id=StarBlade だけじゃなく、 http://www.bloglines.com/public/StarBlade という URL でも OPML をエクスポートできるんだね。

- http://www.bloglines.com/public/StarBlade が OPML エクスポートじゃなくなった?

2005-04-19 追記。
なんかいろいろいじってたら http://www.bloglines.com/public/StarBlade が OPML エクスポートじゃなくて単なるフィード紹介ページみたいになっちゃった。OPML をバックアップするという使い方なら、http://www.bloglines.com/export?id=StarBlade の記法が一番確実な模様。

- w3m を使って Bloglines の OPML をエクスポート

public 設定にできない人向けの楽なエクスポート方法を、読書記録ChangeLog http://dkiroku.com/ のうぞよとさんが書いてくださった。

Bloglines の登録データをバックアップする シェルスクリプト
http://dkiroku.com/2005-04-19-2.html
w3mを使用すればもっと簡単です。
(1)w3mでhttp://www.bloglines.com/exportなどにアクセス。
(2)手作業でログインする。
(3)いったんw3mを終了
(4)以下のコマンドを実行
w3m \
-config /home/usename/.w3m/config \
-cookie \
http://www.bloglines.com/export > export.xml

w3mを使用すれば簡単にクッキーを食わせることが可能なのです。
Yahooファイナンスのポートフォリオ機能などでも使用できます。

おお、なるほど。HTTP リクエストヘッダを表示させるといった、面倒なことが必要ないというところが素晴らしい。w3m はテキストベースのウェブブラウザだし、コマンドラインとの相性も良いはず。さっそくやってみよう・・・と思ったけど、手元の FreeBSD マシンには w3m 入ってないや。あとで試すことにする。

2004-11-20 (Sat)

* ssh + tar で安全なファイルコピー

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト] [ssh] [バックアップ]

2004-11-18 の「scp + tar + gpg で暗号化ネットワークバックアップ」について指摘を頂いた。

Misc Change Log 安全なファイルコピー
http://quasiquote.org/log/Memo/ssh_filecopy.html
http://sonic64.hp.infoseek.co.jp/2004-11-18.html#2004-11-18-1

ssh + tar じゃいかんのかいな。 rsh の時代からの基本だと思うのだが。

(cd path && tar cf - .) | ssh user@hostname "(cd path && tar xvfp -)"
暗号化したければ、送信側は tar の後、受信側は tar の前にいれてやればよい。何のために ssh 使ってるかわからないけど。

メンドイが、scp や sftp でもできなくはない。ヒントは mkfifo。でも本末転倒。だって scp も sftp も ssh のラッパーだから(少なくとも OpenSSH は。他の処理系はどうなのかな?)。

なんか変なこと書いてる?

ありがとうございます。恥ずかしながら、「rsh の時代からの基本」を知りませんでした。そもそも rcp や rsh は使ったことなかったりします。ssh + tar なら、出力を直接標準入力に渡したいという要件は満たしています。

2004-11-18 の「scp + tar + gpg で暗号化ネットワークバックアップ」を書いた後、FreeBSD QandA 2255 http://www.jp.freebsd.org/QandA/HTML/2255.html を読んでいて、ssh の標準入出力を使う方法があることを知って試してみてはいました。ただ、使い勝手とセキュリティの兼ね合いがまだできてなかったので公開してませんでした。

ローカルマシンで以下を実行し、tar + gpg したデータを ssh に渡してアップロードする。
echo $PASS_PHRASE | { tar --bzip2 -cf - $TAR_TARGET |gpg --batch -c --cipher-algo AES256 --force-mdc --passphrase-fd 3;} 3>&0 |ssh -i $HOME/.ssh/auto $REMOTE_SERVER -l $REMOTE_USER

上記コマンドで使っている公開鍵。
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="umask 077; f=backup`date +%F-%H%M%S`.encoded; echo $f; cat >/home/hiroaki/backup/$f;" ssh-dss AAAAB3N (略)

command="" と no-pty を使って安全性を高められるのは良いんですが、出力先ファイル名がリモート側で生成されることが不便。環境変数 SSH_ORIGINAL_COMMAND を使うか、それとも他にもっと良い方法ないかなーと探している状態でした。でもこれで ssh + tar + gpg + SSH_ORIGINAL_COMMAND で問題なさそうとわかったのでこの方法を使うことにします。ありがとうございました。

mkfifo は思いつきませんでした。なるほど、これなら中間ファイルを作らないというのは実現できますね。名前付きパイプって使ったことありませんでしたが、いろいろ面白いことができそう。

- ssh を使ってるのに、なぜわざわざ gpg でファイルを暗号化するのか

何のために ssh 使ってるかわからないけど。

ssh は経路の安全性と認証の安全性を確保するため、gpg はファイルの安全性を確保するために使っています。2004-11-18 において背景を書かずに、「このご時世に外部にデータを持ち出すなら、通信の暗号化とファイルの暗号化は必須だと思う。」で済ませてしまったのは言葉が足りませんでした。

今回の件では、遠隔地のファイルバックアップ用サーバにデータを送ることを想定していました。

共用のサーバであれば、パーミッションが甘いと他の一般ユーザにファイルを見られてしまいます。そんな事態は論外ですが。私だけが使える専用サーバだとしても、遠隔地にあるので管理者権限を持った人間が現地にいます。悪意を持った管理者ならファイルを見ることができてしまいます。

また、万が一マシンが盗難などにあうと、盗んだ人間にもデータの中身を見られてしまいます。もちろん、盗難などに備えて、設備管理者は防犯対策を、システム管理者はファイルシステムの暗号化などの対策はやっておくべきです。同じように、一般ユーザである私にできることがあるならやっておこう、という意味で GPG を使ったファイルの暗号化を施しています。

セキュリティは各担当者の積み重ねの結果確保できるものであって、各担当者が手を抜いた場合、手を抜かれた箇所に依存する部分の安全性が揺らいでしまいますしね。

- 訂正

追記。やっぱり変なこと書いてた。リモートマシンで pgp したいのか。全然読んでなかったorz

なら上のスクリプトを実行後、

ssh user@hostname "(cd path && find . -type f -exec gpg options {} \;)"
でどうよ? gpg は使ってないので知らない。ので、オプションは適当に。 tar ファイルで送ったものを単に gpg したいなら(多分これがお望みの方法だろう)、

(cd path && tar cf - .) | \
ssh user@hostname "(cd path && gpg options > filename.tar.gpg)"
さらに圧縮したいなら、

(cd path && tar cf - . | bzip2 -9) | \
ssh user@hostname "(cd path && gpg options > filename.tar.bz2.gpg)"
でいい罠。

いえ、ローカルで暗号化しないとデータの安全性を確保しにくいので、暗号化はローカルでやって暗号化したデータのみを外部に送信したいところです。なぜ「リモートマシンで pgp したいのか」と解釈なさったんだろう? あ、なるほど。2004-11-18 の以下の部分ですね。

もちろんこれに gpg を組み合わせれば、転送後のリモートマシン上でのファイルの暗号化も実現できる。

確かにこれだと「リモートマシンで gpg を実行したいと」解釈できますね。表現が良くなかったので以下のように改めます。

「ssh/scp/sftp のみを使った場合はデータが暗号化されるのは通信経路上だけだが、gpg を組み合わせればリモートマシンに送ったあともデータを暗号化された状態に保つことができる。」

2004-11-18 (Thu)

* scp + tar + gpg で暗号化ネットワークバックアップ

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト] [バックアップ] [ssh] [gpg]

scp + tar + gpg で経路とファイルを暗号化したネットワーク・バックアップをおこなう。

- 要するに 2004-03-19 の「ftp + tar + gpgで暗号化ネットワーク・バックアップ」の置き換え

2004-03-19 に「ftp + tar + gpgで暗号化ネットワーク・バックアップ」という記事を書いた。この方法だとファイル自体は gpg によって暗号化しているので、中身を解読される可能性は低い。しかし、ftp を使っているため 経路上で ID と パスワードを盗聴されるおそれがある。ファイルの安全とアカウントの安全は別の問題だ。

無料ホームページサービスのように ftp のみのサービスしているホストの場合は盗聴に対して有効な対策をとるのが難しい。しかし、ssh をサービスしているホストなら scp や sftp を使うことで通信の暗号化を実現できる。

ssh/scp/sftp のみを使った場合はデータが暗号化されるのは通信経路上だけだが、gpg を組み合わせればリモートマシンに送ったあともデータを暗号化された状態に保つことができる。このご時世に外部にデータを持ち出すなら、通信の暗号化とファイルの暗号化は必須だと思う。

- scp 用の鍵作成

2004-11-17 の「ssh scp sftp の正しい自動実行方法」のやり方で scp 専用かつ権限を限定した鍵を作成。これを使う。

2004-11-22 追記。
2004-11-17 に書いた、command="" の指定がない公開鍵を使った場合は権限の限定が不十分。command="" を使わずに scp 専用の鍵としたいならば、ファイル転送に特化したシェルを使うなどの対策を講じないと結果的にシェルへのアクセスを許してしまうおそれがある。詳しくは 2004-11-22 「authorized_keys に書く no-pty と command="" の意味」を参照。

- scp は標準入力からデータを読んでくれない

2004-03-19 の「ftp + tar + gpgで暗号化ネットワーク・バックアップ」の利点の一つに、gpg が出力したデータを直接 ftp の標準入力に渡してアップロードする、というものがある。これのおかげで余計な中間ファイルを作らずに済む。

scp でも同じ事をしようと思ったのだが、なんと scp には標準入力からデータを読んで直接アップロードという機能がない。scp の man page を読んでみたが、代替の機能は見つけられなかった。仕方がないので一度ファイルに書き出すことにした。

scp man scp.0
http://www.unixuser.org/~euske/doc/openssh/jman/scp.html

- scp + tar + gpg で暗号化ネットワークバックアップするシェルスクリプト

というわけでできたのが以下のスクリプト。これを cron で自動実行する。

#!/bin/sh

# setup
PASS_PHRASE="MY SECRET PASS PHRASE STRING"
TAR_TARGET="/home/hiroaki/log.txt /home/hiroaki/etc /cygdrive/s/cvsroot"
OUTPUT_FILE="backup`date +%F-%H%M%S`.tar.bz2.encoded"
OUTPUT_PATH="/home/hiroaki/tmp/$OUTPUT_FILE"
REMOTE_SERVER=backup.example.com
REMOTE_USER=hiroaki
REMOTE_UPLOAD_DIR=/home/hiroaki/backup

date
echo $TAR_TARGET
echo $PASS_PHRASE | { tar --bzip2 -cf - $TAR_TARGET |gpg --batch -c --cipher-algo AES256 --force-mdc --passphrase-fd 3;} 3>&0 >$OUTPUT_PATH

echo output to $OUTPUT_PATH
scp -oIdentityFile=$HOME/.ssh/auto $OUTPUT_PATH $REMOTE_USER@$REMOTE_SERVER:$REMOTE_UPLOAD_DIR
if [ $? -ne 0 ]; then
  echo "`date` scp error" >&2
  exit 1
fi
rm -f $OUTPUT_PATH

echo backup complete
date

- scp では今回の目的には力不足

やっぱり標準入力から渡したデータをアップロードしたい。gpg の生成したファイルのハッシュをチェックしたり、scp以外のプログラムでも gpg の出力を利用したり、ローカルにもファイルをため込んだりしたい場合は一度ファイルに出力する方がいいのだが、今回はそんなことはしないし。

また、ファイルをアップロードした後にパーミッションを落としておきたいんだけど、scp では chmod などを発行できないようなのでそれもできない。umask を 077 にしておくだけじゃなくて、アップロードしたファイルに 600 を明示的に指定したいんだけどなあ。

scp コマンドがバージョンアップして機能が強化される見込みもなさそうだし。

OpenSSH FAQ
http://www.ie.openbsd.org/openssh/ja/faq.html#2.10
2.10 - scp に (なにかの機能) を追加してくれない?
短い答え: だめです。

長めの答え: scp は標準化されていないのです。それにもっとも近いものは「rcp が何をやるか」という仕様だけです。これは接続の両側で同じコマンドが使われるため、機能やオプションを追加すると異なる実装の間で相互運用できなくなる危険性があります。

新しい機能は sftp のほうが追加されやすいでしょう。なぜならこのプロトコルは標準化されていますし (まあ、 ドラフト標準ですが)、クライアントとサーバが別々になっているからです。

今回の目的には scp はあまり適していないようだ。明日は sftp を試してみよう。

2004-11-09 (Tue)

* GMail をバックアップストレージとして使う

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [Google] [メール] [シェルスクリプト] [バックアップ] [gpg]

GMail の 1GB という容量を生かして、バックアップストレージとして使う方法。

ストレージというと大げさな感じがするが、やってることは暗号化したアーカイブファイルを GMail にメール送信するスクリプトを書き、これを cron で一時間ごとに実行するだけ。GmailFS などを使ってファイルシステムとしてマウントするのも一つの方法だとは思うが、バックアップなんだから安定性重視でいきたいし、こういうのはシンプルな方がいい。

今日の戯言 ChangeLog 主にテキストファイルを gmail にバックアップ
http://apollo.u-gakugei.ac.jp/~sunaoka/clog/2004-11.php#2004 ...
主にテキストファイルを gmail にバックアップするつもりででっち上げてみたが,車輪の再開発のような気がしてならない.

1GB の容量を与えられたらそれを活用しようとする人はたくさんいるし、実装もたくさんあるようなので確かに車輪の再発明かもしれないですね。でも、自分好みの機能やインターフェイスにしたいなら、スクリプトを書いちゃう方が良いと思います。File::MMagic と Compress::Zlib を使って、テキストだったら圧縮するという機能は面白いと思いましたし。

- GMail をバックアップストレージとして使う - 私の場合

私の場合、シェルスクリプトで tar と gpg を呼び出して圧縮と暗号化をおこない、2004-10-092004-11-01 で書いた「添付ファイル付メールを送信する Ruby スクリプト」である samail で SMTP サーバに投げている。

送信先は GMail をはじめとするメールボックス容量に余裕のあるアドレス。メールなので数十メガバイトのファイルのバックアップという用途には適さない。ChangeLog メモのファイルや自分用 CVS リポジトリ、etc ディレクトリなど、小さなものだけをバックアップしている。Gmail のアカウントを取得する前は一日一回だったが、Gmail ができてからは一時間に一回に実行頻度を上げた。

内容にもよるが、外部にデータを持ち出すなら暗号化は必須だと感じている。GnuPG を使えばフリーで強力な暗号を利用可能。具体的には 2004-01-08 で書いた「gpg で標準入力からパスワードを渡してバッチ処理で暗号化」を利用し、GPG の共通鍵暗号で暗号化している。

暗号化アルゴリズムには AES256 (256bit Advanced Encryption Standard) を指定。gpg のデフォルトの CAST5 でも良いと思うけど、AES は NIST (National Institute of Standards and Technology 米国商務省技術標準局) 御用達の暗号化アルゴリズムとのことなので、これを使うことにした。

Perl だったら Crypt::CAST5 や Crypt::Rijndael とか使うのが一般的なのかな?

#!/bin/sh

# gmail_backup.sh
# Archiving, Crypting, and mail sending script
# Copyright (C) 2004 Saito Hiroaki <sonic64@infoseek.jp>
# http://sonic64.com/

# setup
PASS_PHRASE="MY SECRET PASS PHRASE STRING"
TAR_TARGET="/home/hiroaki/log.txt /home/hiroaki/etc /cygdrive/s/cvsroot"
OUTPUT_PATH="/home/hiroaki/tmp/backup.tar.bz2.encoded"
to_address="to@example.com, to@gmail.example.com"
optional_adderss="to@daily.example.com"
from_address="from@example.com"
smtp_server="smtp.example.com"


date
echo $TAR_TARGET
echo $PASS_PHRASE | { tar --bzip2 -cf - $TAR_TARGET |gpg --batch -c --cipher-algo AES256 --force-mdc --passphrase-fd 3;} 3>&0 >$OUTPUT_PATH

echo output to $OUTPUT_PATH
echo archiving complete

if [ `date +%H` -eq 23 ]; then
  to_address="$to_address $optional_adderss"
fi

/usr/local/bin/ruby /home/hiroaki/script/samail -v --to "$to_address" --from $from_address --smtp $smtp_server --subject "[GMail Backup] `date +%c`" --attachment $OUTPUT_PATH

date
echo mail send complete

以前は暗号化した圧縮ファイルの md5 ハッシュを取っておいて、前回と異なったときのみメール送信という動きにしていたが、その機能は外してしまった。そういう意味では一度ファイル出力せずに直接 samail の標準入力に渡した方がシンプルだな。よし、samail の次のバージョンでは標準入力から読み込んだデータを添付ファイルとして送信できるようにしよう。

- gmail_backup.sh の設定方法

# setup のところに設定を書く。
PASS_PHRASE に gpg に渡すパスフレーズ、TAR_TARGET にバックアップしたいファイルやディレクトリのパス、OUTPUT_PATH に暗号化した圧縮ファイルの出力先を記述。あとは To と From と SMTP サーバ名を書いて完了。Ruby のパスを /usr/local/bin/ruby と samail のパスを /home/hiroaki/script/samail とハードコーディングしてるのはあまり良くないかも。

私の場合 cygwin ネイティブのファイルシステムと /cygdrive の両方にバックアップしたいファイルがあるため、/ からフルパスで TAR_TARGET を記述している。このせいで tar に以下のようなメッセージを表示されるけど、実害がないので問題ないだろう。
tar: Removing leading `/' from member names

- GMail 側の設定

Gmail といえども 1GB しか容量がないので、古いバックアップファイルは削除する必要がある。Gmail の Trash ディレクトリは 30日過ぎると削除されるという性質があるので、これを利用する。この性質は Trash を開いたときに出るメッセージに書かれていた。
Note: Trashed messages more than 30 days old will be automatically deleted.

メール送信時に Subject に特徴的な文字列を入れておき、その文字列でフィルタリング。マッチしたメールは自動的に Trash 行きになるように設定する。こうすることで「30日を過ぎたバックアップファイルは自動的に削除」を実現できる。

- メール送信の頻度はどれくらいがよいか

これはもうお好みで良いと思う。一日一回くらいでも充分なんじゃないかと思うが、せっかく容量があるんならいっぱい送っちゃえ、ということで一時間に一回にしている。

一回 1MB のファイルを送るとして、1MB * 24時間 * 30日 = 720MB 、Base64 すると 3分の4倍くらいになるので、それを含めても 1MB * 24時間 * 30日 * 4 / 3 = 960MB。24時間稼働のマシンでなければ頻度はもっと減るし、一日一回にしておけばさらに余裕。ところで、GMail って添付ファイルは Base64 デコードした状態でファイルサイズを計算している気がするんだけど気のせいかな。

- 一日一回だけ別のアドレスを追加して送信

このスクリプトは cron で一時間ごとに起動しているが、23時台に起動したときだけ optional_adderss にセットされた送信先を追加している。Gmail ほどメールボックス容量に余裕がないアドレスなので、一日一回だけの送信にしたいからだ。Gmail の予備として使っている。

if [ `date +%H` -eq 23 ]; then
  to_address="$to_address $optional_adderss"
fi

- 分割すれば巨大ファイルでも OK?

分割すれば数十メガバイト単位の巨大ファイルでもバックアップできるが、いざリストアしようと思ったときに分割したメールを連結するという手間をかけたくない。標準で POP で受信できない場合はなおさら。そういう巨大ファイルは他の方法を使うべきでだ。どうしてもネットワーク経由でのバックアップがしたいなら、2004-03-19 で書いた「Linux: gpg: ftp: ftp + tar + gpgで暗号化ネットワーク・バックアップ」などが使えるかもしれない。

- 正常に復号して圧縮ファイルを展開できるかどうかのテストを忘れずに

バックアップしただけで安心してはいけない。いざという時にに迅速にリストアできなければ意味がない。私は以下のようにして復号と圧縮ファイルの展開をしている。

$ echo "MY SECRET PASS PHRASE STRING" |gpg --passphrase-fd 0 -o - backup.tar.bz2.encoded |tar -x --bzip2
Reading passphrase from file descriptor 0
gpg: AES256 encrypted data
gpg: encrypted with 1 passphrase

bzip2: (stdin): trailing garbage after EOF ignored

--force-mdc オプションなしで暗号化したファイルの場合、以下のメッセージが表示されるかも知れない。これについては私も詳しくないので言及しない。とりあえず --force-mdc を付ければ警告は出なくなる。
gpg: WARNING: message was not integrity protected

bzip2 の警告は、末尾になにか余計なデータがあるので無視するよ、というもの。理由は不明。tar の出力を直接 gpg の標準入力に渡しているから?

- 「フッフッフッフッフッフッ まぬけめ! Gmail」「や…やろう まさか!」「きさまのおかげで ストレージ利用がしやすくなったぞッ!」

追記。Gmail が POP3 をサポートするとのこと。

ITmediaニュース:GmailがPOP3サポート、ウイルス対策も提供へ
http://www.itmedia.co.jp/news/articles/0411/11/news010.html

これでさらにストレージとして利用するのが簡単になった。そのうち 読み書き用の API を書く人も出てくるだろうな。POP/SMTP ベースだと遅延があるのでリアルタイム性を要求するアプリケーションには使えないだろうけど、用途を選べば充分使える。

2004-08-12 (Thu)

* n 分の1の確率で何かをするスクリプト

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [bash] [シェルスクリプト]

cron で毎分スクリプトを回したいが、一定の確率で何もしないでそのまま終了するようにしたい。

とあるスクリプトに、ランダムに仕事をしないで怠けるような機能を追加したい。乱数を使えばいいのだが、乱数を得るコマンドってあったかな? Perl のワンライナーで rand() を呼べばできるけど、それは最後の手段だ。

- シェル変数 RANDOM

bash の man page を読んでみる。シェル変数 RANDOM が使えそうだ。

Manpage of BASH
http://www.linux.or.jp/JM/html/GNU_bash/man1/bash.1.html
RANDOM
このパラメータが参照される度に、 0 から 32767 までのランダムな整数が生成されます。 RANDOM に値を代入すると、乱数の列を初期化できます。 RANDOM を unset すると、この変数の特殊な性質は無くなります。後で再び set しても元には戻りません。

- 乱数を n で割った余りが0だったら何もしない

乱数を n で割った余りを求め、0だったら何もせずに終了する。これで「n 分の1の確率で何もしない」という処理ができる。今回の用途にはこれで十分だ。

# 確率3分の1で何もせずに終了する
probability=3
if [ `expr $RANDOM % $probability` -eq 0 ]; then
  echo unlucky!
  exit
fi

2004-05-28 (Fri)

* 秒間隔でコマンドを繰り返し実行し続ける

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト] [unix]

ドロップパケットの数値を監視するため、netstat -i を1秒間隔で自動実行したい。

- while と sleep を使ってシェルスクリプトを書く

$ while true; do date; netstat -i; sleep 1; clear; done;

while で無限ループを作って sleep で間隔を入れている。日付を表示するために date を、毎回画面クリアするために clear を追加した。
単純だけどこれで十分。もちろん、sleep している間は CPU を消費しないので安心。

- watch コマンドを使う

$ watch --interval 1 netstat -i
これで1秒間隔で netstat -i を実行し、出力を画面に表示してくれる。ちなみに cygwin でも試してみようと思ったら watch コマンドがインストールされてなかった。

- 参考

2004-05-26 (Wed)

* ディレクトリ中のファイルサイズ合計値を バイト表示

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト] [unix]

Windows では、ディレクトリのアイコンを右クリックしてプロパティを表示させると、そのディレクトリ以下のファイルのサイズの合計値がバイト単位で表示される。併せて、ファイル数やクラスタギャップを含めたディスク上のサイズも表示される。ちなみに、プロパティは ALT + ディレクトリアイコンのダブルクリックでも表示される。

Linux や Unix のコマンドラインで操作しているときも、同じ情報を表示させたい。

- du じゃブロックサイズの表示なのでダメ

すぐに思いついたのは du コマンドだが、du はファイルサイズではなくファイルが使っているブロックサイズを表示するのでダメだ。-b オプションをつけてバイト単位で表示させても、ブロックサイズをバイト単位で表示するだけなので今回の目的には使えない。以下、実行結果。ls では 29バイトなのに du ではブロック1個分の 4096バイトになっているのがわかる。

$ date >date.txt
$ ls -l date.txt
-rw-r--r--    1 hiroaki  hiroaki        29 May 25 12:50 date.txt

$ du -b date.txt
4096    date.txt

ls や du の man を読んでみたが、一発で解決してくれるようなオプションは無かった。

Manpage of LS
http://www.linux.or.jp/JM/html/GNU_fileutils/man1/ls.1.html

Manpage of DU
http://www.linux.or.jp/JM/html/GNU_fileutils/man1/du.1.html

- コマンドを組み合わせて合計値を表示させる

こうなったらコマンドを組み合わせて望みの値を得るスクリプトを書くしかないでしょう。One Liner でも書けるかな。

特定ディレクトリ下を再帰的に検索するなら find の出番だ。man を見ると出力書式もかなり自由だし、いろいろできそう。find にファイルサイズを出力をさせて、それを合計してやればいいわけだ。合計くらいなら sh や bash などのシェルでもできそうだけど、パフォーマンス気にするわけでもないし、perl でいいか。文法調べるの大変だし。

Manpage of FIND
http://www.linux.or.jp/JM/html/GNU_findutils/man1/find.1.htm ...

- ディレクトリを再帰的にたどって、ファイルの合計サイズを出力する。

$ find -type f -printf "%s\n" |perl -ne '$sum += $_; print "\r$sum"'
一気にカウントが増えていくとこがかっこいい。ガソリンスタンドで給油量のカウンタについ見入ってしまうみたいな感覚。

- ディレクトリを再帰的にたどって、ファイルの合計サイズを出力する awk 版。

でも 必要ないのに重い perl 使う理由は無いよね。awk でいいか。--help したらサンプル載ってたし。awk だと END っていうのがあるので、\r を使わずに書けるんだね。
$ find -type f -printf "%s\n" |awk '{sum += $1; printf "\r" sum };'
$ find -type f -printf "%s\n" |awk '{ sum += $1; }; END { print sum }'

- ファイルの個数とファイルサイズを表示。ファイルサイズは3桁ごとにカンマで区切る。

$ find -type f -printf "%s\n" |perl -ne '$i++; $byte += $_; $str = "\r$i files, $byte byte"; $str =~ s/(\d{1,3})(?=(?:\d\d\d)+(?!\d))/$1,/g; print $str'
カンマが入っただけでもかなり雰囲気が変わる。なんだか「日本の借金カウンタ」みたいな感じがする。

- 他に欲しい機能は・・・

あとは再帰的にディレクトリをたどって、ディレクトリ毎にそのディレクトリが持つ容量を表示する機能が欲しいかなあ。要するに、du みたいに表示してほしいってこと。上記スクリプトはカレントディレクトリの容量しか表示しないけど、それを一気にやる機能。必要になったら作ろう。

- このコマンドの名前をどうしよう?

du - disk usage に対抗して bu - byte usage ってのはどうだろう? いかん、私ってセンス無い。

2004-04-19 (Mon)

* smbclientでファイル転送、-b buffersizeで高速化

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [samba] [シェルスクリプト]

一日一回、Linux マシンから Windows マシンに接続してファイルをもらってくるという作業がある。使用できるプロトコルは netbios のみなので、smbclient を使ってシェルスクリプトを書いた。スクリプトといっても実質はたった一行だけど。Windows2000 サーバに接続して再帰的にディレクトリの中身をコピーする。あとはこれを cron で自動実行するだけ。

#!/bin/sh

LOG='/home/hiroaki/log/get_profitlog.log'
echo `date` get start >>$LOG

cd /home/hiroaki/import
/usr/bin/smbclient //windows2000server/public PASSWORD -W DOM-HIROAKI -U saitou_hiroaki -D /common -c "prompt; recurse; mget BbsLog" >>$LOG

echo `date` get complete >>$LOG

- なんだか遅い

なんだか転送速度が遅い。1.2Mbps くらいしか出ていない。相手先サーバは 100Mbps ではなく10Mbps でネットワークに接続しているようだが、それを差し引いても遅すぎる。
getting file ABC00001.PIX of size 2030122 as ABC00001.PIX (138.533 kb/s) (average 164.479 kb/s)

smbclient は TurboLinux Server 6.5 付属の Version 2.0.7-ja-1.2 だ。swat の管理画面からマニュアルを読んでみたところ、以下のようなオプションがあると書かれていた。
-b buffersize
このオプションはサーバとファイルの送受信を行なうときの送受信バッファのサイズを変更する。規定値は 65520 バイトである。この値を(最小 1200 バイトまで)小さくすることで、 Windows 9x サーバとのファイルの転送がスピードアップすることが確認されている。

Domain=[DOM-HIROAKI] OS=[Windows 5.0] Server=[Windows 2000 LAN Manager]
接続時に上記メッセージが出ていることからもわかるように、今回の相手先サーバは Windows2000 なのだがとりあえず試してみた。

- -b で smbclient が速くなった

-b 4096 を付けると10倍くらい速くなった。何でだろう? Widows9x 系以外でも有効なのかな?

getting file ABC00001.PIX of size 2030122 as ABC00001.PIX (1293.24 kb/s) (average 1282.02 kb/s)

今回は理論よりも実を取り、以下のようにスクリプトを修正。
/usr/bin/smbclient //windows2000server/public PASSWORD -W DOM-HIROAKI -U saitou_hiroaki -D /common -b 4096 -c "prompt; recurse; mget BbsLog" >>$LOG

2003-09-08 (Mon)

* ping してエラーだったらメールするシェルスクリプト

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト]

土曜日、日曜日と、スイッチの障害が発生した。しばらくスイッチを監視しなければならない。監視を楽にするために、サーバが IP reachable かどうかをチェックして、エラーだったらメールするスクリプトを書いた。これを cron で一分毎に実行する。
#!/bin/sh

echo `date`

TARGET_IP='10.122.23.254 10.122.23.249 10.122.23.1';
for ip in $TARGET_IP; do
        ping $ip -c 4
        if [ $? -ne 0 ]; then
                echo 'ping error'
        mail -s "Ping Error at ${ip}" example@example.com, example@example.com << EOF
        Ping Error at ${ip} `date`
EOF
        fi;
done

2003-09-06 (Sat)

* シェルスクリプトで直前のコマンドの終了コードをチェック

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト]

直前のコマンドの終了コードは $? という特殊変数に格納される。
こんな感じでテストできる。超便利だね。
if [ $? -ne 0 ]; then
  echo "Error";
else
  echo "OK";
fi

2003-06-06 (Fri)

* ファイルの内容が変わっていなかったらスクリプトを実行しない

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [シェルスクリプト]

ChangeLog の md5 をチェックし、変わっていなかったら exit するようにした。
2003-05-26 に書いた、/home/hiroaki/backup/changelog が変化のない同じファイルで溢れるという事態もこれで解決するだろう。

# compare previous log with current log
LOG_HASH=`/usr/bin/md5sum $LOG_PATH |perl -na -e 'print @F[0]'`
PREVIOUS_LOG_HASH=`/usr/bin/md5sum $PREVIOUS_LOG_PATH |perl -na -e 'print @F[0]'`
if [ $LOG_HASH == $PREVIOUS_LOG_HASH ]; then
  exit 1
fi


すべての記事の見出し (全1029件)
全カテゴリの一覧と記事の数
カテゴリごとに記事をまとめ読みできます。記事の表題だけを見たい場合は、すべての記事の見出し (カテゴリ別表示) へ。

直近30日分の記事
2007-04-23 (Mon)
2007-03-07 (Wed)
2007-02-27 (Tue)
2007-01-17 (Wed)
2007-01-15 (Mon)
2007-01-14 (Sun)
2007-01-08 (Mon)
2006-12-01 (Fri)
2006-11-22 (Wed)
2006-11-20 (Mon)
2006-11-19 (Sun)
2006-09-30 (Sat)
2006-08-29 (Tue)
2006-08-04 (Fri)
2006-07-27 (Thu)
2006-07-23 (Sun)
2006-07-17 (Mon)
2006-07-10 (Mon)
2006-07-06 (Thu)
2006-07-03 (Mon)
2006-06-29 (Thu)
2006-06-28 (Wed)
2006-06-27 (Tue)
2006-06-25 (Sun)
2006-06-19 (Mon)
2006-06-18 (Sun)
2006-06-15 (Thu)
2006-06-11 (Sun)
2006-06-01 (Thu)
2006-05-30 (Tue)
プロファイル
斎藤 宏明。エンジニアです。宇都宮市に住んでいます。
リンク
RSS
スポンサードリンク
Powered by
さくらインターネット

© 斎藤 宏明 Saito Hiroaki Gmail Address
Landscape - エンジニアのメモ http://sonic64.com/
Landscape はランドスケープと読みます。
ひらがなだと らんどすけーぷ です。