UNIX の小技

2009/7/2 木曜日10:17:13

1. pv: パイプラインの速度表示
pv はパイプライン中を流れるデータの速度や予想終了時間を表示します。

pv - Pipe Viewer
http://www.ivarch.com/programs/pv.shtml

Fedora なら yum install pv でインストールできます。RHEL/CentOS なら上記から RPM を入手してインストールします。

(例 1) 圧縮速度を測定し、終了時刻を予想する

pv /dev/sda | gzip > sda.gz

(例 2) 圧縮比を予測する

pv -cN read /dev/sda | gzip | pv -cN write > /dev/null

-N : 名前をつける
-c : エスケープシーケンスを使って描画する

  参照: http://www.catonmat.net/blog/unix-utilities-pipe-viewer/

(例 3) データフローの速度を規制する

pv -L 1m /dev/sda | gzip > /dev/null

2. bash のプロセス置換

bash にはプロセス置換という構文があり、プロセスの入出力をファイルのように取り扱うことができます(zsh 由来の機能)。

(1)>(list)

プロセスへの書き込みをファイルとして取り扱うことができます。tee と組み合わせて、パイプラインを分岐させるのが典型的な用途です。

(例 1) md5sum を計算しながら gzip で圧縮する

cat file | tee >(md5sum > file.MD5SUM) | gzip > file.gz

(例 2) 同上、md5sum は標準エラーに表示

tee >(md5sum 1>&2) < file | gzip > file.gz

md5sum と gzip を別々に実行するのに比べて無駄がないため、入力が低速な場合、入力が巨大な場合、入力が一度しか読めない場合などに有用です。

(2)>(list)

プロセスの出力をファイルとして読み取ることができます。

(例 1) 一時ファイルを作らずに複数のパイプラインを組み合わせる

paste <(cut -d : -f 1 /etc/passwd) <(cut -d : -f 7 /etc/passwd)

参考ネタ: http://journal.mycom.co.jp/column/zsh/012/index.html

(例 2) 標準入力を受け付けないコマンドにデータを流し込む

diff <(echo foo) <(echo bar)

参考: http://d.hatena.ne.jp/oraccha/20080902/1220368128

(例 3) 通常のパイプ処理もプロセス置換で書き換えることができる

du -s /etc/* | sort -nr | head

  ↓

head <(sort -nr <(du -s *))

(3) 説明

プロセス置換は、名前付きパイプ(または名前付きオープンファイル)を使って実装されています。>(list) は内部的には以下のように処理されます。

  1. FIFO を作る
  2. FIFO の読み出し側を list の標準入力に接続する
  3. list の実行を開始する
  4. FIFO の名前(書き込み側)を置換して返す

自前で FIFO を作れば、bash/zsh 以外でも可能です。
例: md5sum を計算しながら gzip で圧縮する

mkfifo fifo
md5sum < fifo & tee fifo < file | gzip > file.gz
rm -f fifo

参考: http://www.codecomments.com/archive287-2006-1-778879.html

3. gzip

gzip は、ファイルを圧縮する場合、ファイル名やタイムスタンプ情報などの属性をヘッダに保持するため、同じ内容のファイルを圧縮しても異なる結果になる場合があります。しかし、-n (–no-name)オプションつきで実行すると、gzip は決定的(deterministic)です。つまり、同じ入力バイトストリームに対しては常に同じ出力バイトストリームを返します。gzip の出力を(md5sum などと同様な)暗号学的ハッシュとみなして利用することも可能です。例外として、Python の gzip モジュールは常に現在時刻をセットするため、非決定的です。http://bugs.python.org/issue4272マルチプロセッサ対応版の mgzip は、スレッド数(-t)によらず、つねに決定的です。-n オプションはなく、ファイル名・タイムスタンプ情報は記録されません。

4. dd

古典的プログラムでありながら今でも使われている dd ですが、パイプライン中に挿入する場合は bs= や count= が直感どおりに動作しないので注意が必要です。
これは、「短い複数の入力ブロックをまとめない」という dd の仕様によります。
dd の内部動作は以下の通り。まず、入力用/出力用の 2 つのバッファを用意します。ただし、bs が与えられ、ibs/obs が与えられず、かつ、コード変換をしない場合は、入出力共用のバッファを 1 つだけを使います。そして、入力をスキップし、出力をシークします。メインループでは、ibs バイトの読み込みを試みます。実際に読み込んだバイト数が ibs に達しない場合でも(特に 入力がパイプの場合、PIPE_BUF = 4KiB以下になります)、パーシャルブロックとして処理します。

http://www.opengroup.org/onlinepubs/009695399/utilities/dd.html

It shall read the input one block at a time, using the specified input block size; it shall then process the block of data actually returned, which could be smaller than the requested block size.

:

If the bs= exproperand is specified and no conversions other than sync, noerror, or notrunc are requested, the data returned from each input block shall be written as a separate output block; if the read returns less than a full block and the sync conversion is not specified, the resulting output block shall be the same size as the input block.

http://broadh2o.net/docs/unix/general/dd-cpio-etc.htm

Most people use dd incorrectly. This is because dd is a piece of junk that should be replaced. For example, one common misusage of dd is to try and get 64k blocks written to the tape with this command: tar -cf - args… | dd of=/dev/rmt8 bs=64k This won’t work because (as you will see below), the bs argument gives you only one buffer. The dd process will attempt to read 64k chunks from the pipe into this buffer, but will only receive a maximum of PIPE_BUF bytes (usually 4 or 8k). It will then write this buffer out to the tape as a single record (it will not pad this block to 64k, fortunately).

したがって、「先頭の 1GiB を取り出して」というつもりで

tar cf - /usr | dd bs=1M count=1024 of=foo

などとすると、出力は 1GiB よりも小さくなります。
この場合は dd ではなく head を使って

tar cf - /usr | head -c 1024m > foo

とするのがよいでしょう。
「2 GiB 目から 1GiB 分を取り出して」なら、

tar cf - /usr | tail -c +$((1024*1024*1024+1)) | head -c 1024m > foo

tail は通常「末尾から N 項目」ですが、+N 構文を使うと「先頭から N 番目以降」という意味になります。

mysql SQL シェルコマンドの TIPS

2008/7/12 土曜日15:29:48

pager, –safe-updates
mysql でクエリの結果を表示する pager には “less -S” がおすすめですが、すこし古い less だと、UTF-8 の処理がいまいちだったりするので、新しい less (Version 406 以降)を使うのがよいでしょう。
また、事故を防ぐために、つねに–safe-updatesオプションを使うのは良い考えです。
そうすると、コマンドラインが

% mysql -h localhost -u username –password=secret \
–pager=”/some/where/less-408 -S” \
–default-character-set=utf8 \
–safe-updates dbname

のように長くなるので、シェルの alias/function に登録するなどしておきます。
まあこのあたりはだれでもやることですね。

mysql のプロンプトをカスタマイズ

mysql のデフォルトのプロンプトは “mysql>” という素っ気無いものですが、環境変数 MYSQL_PS1 で変えることができます。
たとえば

% MYSQL_PS1=’MySQL \v \u@\h/\d [\R:\m:\s] \nmysql \c> ‘

とすると、

MySQL 5.0.45 root@localhost/tmpdb [09:08:14]
mysql 4>

のようになります。バックスラッシュシーケンスの意味は以下のとおり。

\v version
\u username
\h server host
\d default database
\R hour (00-24)
\m min
\s sec
\n newline
\c serial

ついでに、色をつけて楽しく(?)します。参照: ANSI エスケープシーケンス

% MYSQL_PS1=’MySQL \v ^[[1;33m\u^[[0m@^[[1;35m\h^[[0m/^[[1;31m\d^[[0m ^[[1;33m[\R:\m:\s]^[[0m\nmysql \c> ‘

注: “^[” は ESC(0×1c)のリテラルです。vi なら Ctrl-V ESC、Emacs なら Ctrl-Q ESC で入力します。

マニュアルに書いてあることなので面白くない?
ではすこしマニアックに…。

readline のマクロを使う

mysql> SHOW DATABASES;
mysql> SHOW TABLES;

といった頻繁に使うコマンドを毎回入力するのは、実に苦痛です。

ましてや、

mysql> GRANT ALL PRIVILEGES ON dbname.* TO username@localhost
IDENTIFIED BY “secret”
WITH GRANT OPTION;

などというのは、覚える気にもなりません(といいつつ実は覚えてますが)。

そこで、よく使うコマンドは readline のマクロに定義しておくと便利です。

$HOME/.inputrc の例

$if mysql

“\C-xd”: “SHOW DATABASES;\n”
“\C-xt”: “SHOW TABLES;\n”
“\C-xu”: “SELECT User,Host,Password,t.* FROM mysql.user AS t ORDER BY User,Host; SELECT Db,User,Host,t.* FROM mysql.db AS t ORDER BY Db,User,Host;\n”
“\C-xc”: “SHOW VARIABLES LIKE ‘char%’; SELECT * FROM information_schema.SCHEMATA; SHOW TABLE STATUS;\n”

“\C-xI”: “INSERT INTO tablename (column) VALUES (’value’);”
“\C-xU”: “UPDATE tablename col = ‘val’ WHERE id = ;”
“\C-xD”: “DELETE FROM tablename WHERE id = ;”

“\C-x\C-xd”: “CREATE DATABASE dbname DEFAULT CHARACTER SET utf8;”
“\C-x\C-xt”: “CREATE TABLE tablename (id SERIAL, created TIMESTAMP, modified TIMESTAMP, text TEXT);”
“\C-x\C-xg”: “GRANT ALL PRIVILEGES ON dbname.* TO username@localhost IDENTIFIED BY “secret”;”

“\C-x\C-xD”: “DROP DATABASE dbname;”
“\C-x\C-xT”: “DROP TABLE tablename;”
“\C-x\C-xU”: “DROP USER username@localhost;”

$endif

$if mysql~$endif で、mysql コマンドにのみ適用するマクロ群を定義しています。Ctrl-X d と 2 文字入力すると SHOW DATABASES; に置換されるというわけです。

このテクニックは、mysql 以外にも、readline ライブラリを組み込んだアプリケーションならば応用することができます。

もっとも、3 ストロークもあるショートカットは、すぐ忘れてしまうので、結局あまり使わなかったりします。
tcsh のように、コンテキストに応じて TAB で補完する候補をカスタマイズできれば、キーストロークを覚えなくてもすむのですが…。

発想を変えて、「よく使うコマンドを $HOME/.mysql_history に追加してから mysql を起動する」という wrapper スクリプトを書いて、C-p とか C-r で呼び出すようにするという手もありかもしれません。が、まあそこまでしないでもいいかなと。

JavaScript で sleep するには?

2007/6/25 月曜日13:57:38

JavaScript で時間待ちをしたい場合、

function busywait(sec) {
    alert(”ループを開始します”);
    var start = new Date;

    while (1) {
        var cur = new Date;
        if (sec * 1000 <= cur.getTime() - start.getTime()) {
            break;
        }

    }

    alert(”ループを終了します。”);
}

 のような単純なビジーウエイトをすると、ブラウザがセキュリティエラーを出す場合があります。ws000000.PNG

サーバに PHP や CGI のコードを設置することができる場合、XMLHttpRequest を使って応答を待つことにより、ウェイトを挿入することができます。

クライアント側

function sleep(sec) {
    alert(”サーバにリクエストを投げます”);
    var start = new Date;

    var dummy = new Ajax.Request(
                                 ’sleep.php’,
        {           method: ‘get’,
                    parameters: ’s=’ + sec + “&r=” + Math.random(),
                    asynchronous: false
        }
                                 );
    var end = new Date;
    alert(”サーバから応答がありました。経過時間 = ” +
          (end.getTime() - start.getTime()) / 1000 + “秒”);

サーバ (sleep.php) 

<?php
usleep($_REQUEST[’s’] * 1000000);
print(microtime());
?>

乱数でパラメータを変えているのは、キャッシュの影響を排除するためです。

 参考: JavaScriptsleepの実装を試みる

CGI PHP でファイルアップロード

11:30:10

PHP ではファイルアップロードの処理は非常に簡単です。
基本的には、マニュアルにあるとおり、

<form enctype=”multipart/form-data” action=”upload.php” method=”POST”>
  <input name=”userfile” type=”file” />
  <input type=”submit” value=”Send File” />
</form>

というフォームから、

if (move_uploaded_file($_FILES[‘userfile’][‘tmp_name’], $uploadfile)) {
    echo
“File is valid, and was successfully uploaded.\n”
;
} else {
    echo
“Possible file upload attack!\n”
;
}

というコードを呼び出すだけです。

たいていはこれで間に合いますが、

  • ファイルをアップロードするテンポラリディレクトリ1  を動的に変えたい
  • テンポラリディレクトリと最終アップロード先が別パーティションにあるが、無駄なコピーが許せない
  • テンポラリファイルのファイル名を自分で決めたい
  • アップロード中に何らかの処理を行いたい

などという場合は、自前で CGI スクリプトを書くことになります。性能が求められる場合、CGI.pm なども使わず、POST データを自前で処理することになります。CGI には POST データの中身が標準入力として与えられます。その中身は multipart/form-data というもので、このようになっています。

RFC1867 Form-based File Upload in HTML (元になっているのはRFC2049)

Content-type: multipart/form-data, boundary=AaB03x

–AaB03x
content-disposition: form-data; name=”field1″

Joe Blow
–AaB03x
content-disposition: form-data; name=”pics”; filename=”file1.txt”
Content-Type: text/plain

–AaB03x–

各パートの区切りは、

CR + LF + “– ” + (Content-Type の boundary で指定された文字列) です。

最後の区切りには “–” が追加されます。

 さて、これを自前で処理する場合、メモリに読み込んでから書き出すのでは意味がありませんので、ストリーム処理することになります。PHP で実装した例を添付します(あまりエレガントではありませんが)。

  1. php.ini の upload_tmp_dir で決まり、.htaccess や ini_set() では変更できない []

Windows (NTFS) でシンボリックリンクを作成するには?

2007/6/23 土曜日13:51:54

リンク/ジャンクション作成ツールを使うと簡単です。

異なるマシン間で、ハードリンクを保存しつつデータをコピーするには?

12:47:12
  1. NFS + cp -al
    少なくとも Linux システム間であれば、ハードリンクだけでなく、ブロックデバイス・キャラクタデバイス・名前つきパイプ(FIFO)・UID (数値マッピング)なども、ただしく保存してコピーできる。バックアップ対象ホストから disk を export して、バックアップサーバ側で mount するほうがよい。
  2. rsync の -H (–hard-links)オプションを使う
    ただし、このオプションは非常に遅い。また、通常、パーティション全体に対して rsync を実行することになるため、大量のメモリを必要とする。
    たとえば、CentOS 5 を標準インストールすると、約 100K のファイルが作成される。これを 10 世代分・10 台分バックアップすると、ファイル数は 10M となる(i-node の消費数はずっと少ない)。rsync は 1 ファイルあたり約 100 バイトのメモリを必要とするため、rsync を実行するホストでは 10M * 100 バイト = 1G バイトのメモリが必要である。

    • rsync は内部的に相対ディレクトリでパス名情報を保持しているらしく、ディレクトリ階層が深いのは、メモリ消費量には影響しないようです。
    • rsync のバッチモードを使っても、アルゴリズムは同じなので、ピークのメモリ必要量は、あまり変わらないと予想されます。
    • メモリアクセスが連続的になるアルゴリズムらしく、swap out しても、さほど性能は落ちないので、swap を大きく取っておけばなんとかなるようです。
  3. 補足: 上記では rsync のメモリ消費は?100byte/file と書きましたが、実際もほぼそんなところです。8.3M 個のファイルがあるディレクトリツリーを rsync すると、転送元・転送先のそれぞれで、最大 680MB のメモリを消費しました。すなわち 82byte/file です。

    しかし、一括 rsync しようとすると、5~6GB 以上の仮想メモリが必要ということになります。たとえ swap を増やしても、32bit CPU ではどうしようもありません。4G/4G パッチ適用カーネルでは x86(32bit) でも仮想メモリの合計は 64GB まで使えます。実際、swap を追加していったところ、仮想メモリを 5GB にできました。しかし、プロセス毎には 4GB までのようです。

  4. rsync の –link-dest=DIR オプションを使う
    rsync のバージョンが新しく、連番のディレクトリでハードリンクを張っている場合などは、この方法が使える。ハードリンクを保存するわけではなく、「同一内容ならハードリンクにする」という操作なので、故意にリンクを切っている場合には使えない。逆に、リンクを張りなおして、ディスク使用量を減らすのであれば最適である。
  5. dump
    ハードリンクを保存するが、ネットワーク越しのバックアップにはあまり適していない。
  6. tar, cpio
    tar や cpio は、もともとテープデバイスへの書き込みを前提にしたアーカイブフォーマットであるため、ハードリンク(i-node 情報)を保存しない。
  7. ディスクイメージでコピーする
    ○ ファイルシステムのすべての属性を保存する、もっとも確実な方法である。ext2 の特殊ビットもコピーできる。
    ○ 実行時間が容易に予測できる。
    △ 未使用領域もコピーするので効率が悪い(ディスクイメージを圧縮するツールを使えば改善できる)。
    × インクリメンタルなバックアップは困難。
    × 活性な(常時変化している)ファイルシステムには使えない。

ハードリンクがたくさんあると、ファイル操作が遅くなる?

12:43:01

削除に関しては、原理上、(少なくとも古典的な UNIX のファイルシステムでは)i-node のリンクカウントを -1 するだけなので、遅くなりません。むしろ、データ領域の bitmap に対する操作が不要なぶん、速くなります。
参照: システムプログラム概論 - ファイルシステム 1 2

実験: サンプルとして、

  • 総ディレクトリ数 2221
  • 階層数 3
  • ファイルサイズ 1000 バイト
  • 総ファイル数 28060
  • ディスク領域 237MB

というディレクトリツリーを作成する。これを、cp -a と cp -al でそれぞ れ 30 セット複製する。1 セットを削除するのに要する時間は、

  • cp -a (実体)            9.6 秒
  • cp -al (リンク)         3.6 秒

となり、ハードリンクを削除するほうがずっと速い。

大量のハードリンクがあると遅くなる操作の例としては、du があります。du はリンクを二重カウントしないように i-node を追跡するため、大量のハードリンクがあると動作が遅くなります。

URL の最大長は何文字?

12:29:47

ユーザーエージェントおよびサーバの実装に依存します。スキーム、ホスト名を含めて、255 バイト以下は安全です。メジャーなブラウザとサーバに限定すれば、2000 バイト程度までは使えるでしょう。

  1. SGML では 1024 文字
    HTML のスーパーセットである SGML では、LITLEN=1024 文字とされています。
    RFC2070 | URL の長さの制限って | HTML 4のSGML宣言
  2. HTML 4.01 では 65536 文字
    HTML 4.01 では LITLEN=65536 文字です。
  3. HTTP では未定義、255 バイト以下を推奨
    RFC2616 (HTTP/1.1) には、URL の長さに関する規定はありません。ただし、

    Note: Servers ought to be cautious about depending on URI lengths above 255 bytes, because some older client or プロキシ(proxy) implementations might not properly support these lengths.

    と、255 バイト以下が推奨されています。

  4. Internet Explorer は 2083 バイト
    URL の最大長が 2083 バイトです。
    「URL に使用可能な文字数は最大 2,083 文字」
    IE3.02 のころは 1KB 程度だったようです。
  5. Mozilla/Firefox は事実上無制限
    少なくとも 2MB (200 万文字)は送信可能なことを確認しました(後述)。ソースコードは未確認ですが、事実上無制限と考えてよいでしょう。
    ただし、あまりに長い URL は、アドレスバーが表示されなくなる、極端に動作が遅くなる、などの不具合があります。
  6. その他のブラウザ (Opera, NetFront など)
    未確認
  7. Apache は 8177 バイト
    Apache HTTP server では、HTTP リクエスト行の長さが LimitRequestLine を超えると、414 Request-URI Too Large エラーを返します。
    LimitRequestLine のデフォルト値は 8190 バイトです。Apache 2.0 では 0 からDEFAULT_LIMIT_REQUEST_LINE (=8190)の間で設定可能です。Apache 2.2 では任意の値にセットできます。
    通常のリクエスト行は
    GET <url> HTTP/1.1
    ですから、url 部分の最大長は 8177 バイトということになります。
  8. 実験

    <html>
    <form action=”show.php”>
    <textarea name=”t” ></textarea>
    <input type=”submit”>
    </form>
    </html>

    のようなフォームでデータを送信してみました。

    Internet Explorer 6 + Apache 2.0 の場合
    2083 バイトまでは送信可能です。それ以上は submit ボタンを押しても送信しなくなります。

    Firefox 1.5 + Apache 2.0 の場合
    8177 バイトまでは送信可能です。それ以上は 414 Request-URI Too Large になります。

    Firefox 1.5 + Apache 2.2 の場合
    LimitRequestLine 十分大きく設定すれば、十分長い URL を送信できます(2MB まで確認)。

  9. 備考
    サーバのファイルシステムの最大文字長や、ドメインの長さなど、他の要因で制限される場合もあります。
    ext2 のファイル名は 255 バイトまで
    ドメイン名は 255 オクテットまで (RFC1034)

proftpd chroot jail

12:08:36

Q: proftpd で、

  • グループ vm または virtual に属するメンバーは chroot jail する。
  • ただし、users グループに属するメンバーは chroot jail しない。

とするための設定方法は?

A:
(1) 間違った設定方法

DefaultRoot ~ virtual,vm,!users

これだと、virtual AND vm AND (NOT users) という意味になる。

(2) 正しい設定方法

DefaultRoot ~ vm, !users
DefaultRoot~ virtual, !users

つまり、
 (vm OR virtual) AND (NOT users)
という論理式を、
(vm AND (NOT users)) OR (virtual AND (NOT users))
と展開するわけである。

上記 2 つを実際にやってみると:

=======================================
users   vm      virtual (1)     (2)
---------------------------------------
-       -       -       -       -
-       -       yes     -       yes
-       yes     -       -       yes
-       yes     yes     yes     yes
yes     -       -       -       -
yes     -       yes     -       -
yes     yes     -       -       -
yes     yes     yes     -       -
---------------------------------------

参照:

WordPress 第一印象

12:04:40
  • WYSIWIG 風エディタは結構使いやすい。特に複数回の UNDO ができるのがよい。
  • でも font タグをたくさん埋め込むのはやめてほしい
  • 勝手に顔文字になってしまうのはやめてほしい
  • テーマによっては、箇条書き(番号つきリスト)は、preview と公開ページで番号が変わってしまう
  • パラグラフの前後の垂直スペースは、もう少し小さくしたい。(これは簡単に変更できそうだ)
  • もうちょっとさくさく動かんものか。
  • 結局、AreaEditor + Meadow で、HTML タグを手打ちしている
  • Footnote はないのか? と思ったらプラグインがあった。 2.0 用だが、2.2 でも使えた。1
  • バージョン管理機能はないのか?
  • やっぱり Pukiwiki の方が…。
  1. こういう風に使う []