sleepコマンドの最小分解能はもともと1秒である。
UNIX上のsleepコマンドは、秒単位のスリープ指定しかできない。
「いや、手元の(FreeBSD|Linux|OS X|…)では、0.5とか指定できるよ」と答える人もいるかもしれないが、それは独自拡張でできるようになっているだけだ。
それぞれのmanを見ればわかるように、「小数点以下も指定できるけどそれは非互換な独自拡張であり、他OSで使った場合の保証はできないよ」という旨が書いてある。
- FreeBSDのsleepコマンドオンラインマニュアル(10.3-RELEASE)
- Linux JMのsleepコマンドオンラインマニュアル(GNU coreutils版)
- Mac OS Xのsleepコマンドオンラインマニュアル(ちょっと古い)
(あれ、Linuxのにはその注意書きがないぞ!)
回りくどい説明になってしまったが、すべてのPOSIX準拠OSの規範になっているPOSIX文書上のsleepコマンドを見ればはっきりする。小数点以下を指定できるという記述はどこにも書いてない。
POSIXの範囲のC言語で作ればいい。
何か使えるコマンドは無いか?……何か他の処理をするついでに1秒未満のタイムアウト時間が指定できるとか、1秒未満で一定の時間がかかるコマンドがあればいいのだが、やはりPOSIXの範囲ではありそうにない。
POSIX原理主義ももはやこれまで
いや、見くびるのは早い。**無いものは作ればいい。**POSIXにはc99というC言語コンパイラーが存在するのだ。だからC言語で書けばよい。秒未満の分解能を持つsleepコマンドなど50行足らずで書ける。
# include <limits.h>
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
void usage (char* pszMypath);
void errmsg(char* pszMypath);
int main(int argc, char *argv[]) {
struct timespec tspcSleeping_time;
double dNum;
char szBuf[2];
int iRet;
if (argc != 2 ) {usage(argv[0]);}
if (sscanf(argv[1], "%lf%1s", &dNum, szBuf) != 1) {usage(argv[0]);}
if (dNum > INT_MAX ) {usage(argv[0]);}
if (dNum <= 0 ) {return(0); }
tspcSleeping_time.tv_sec = (time_t)dNum;
tspcSleeping_time.tv_nsec = (dNum - tspcSleeping_time.tv_sec) * 1000000000;
iRet = nanosleep(&tspcSleeping_time, NULL);
if (iRet != 0) {errmsg(argv[0]);}
return(iRet);
}
void usage(char* pszMypath) {
int i;
int iPos = 0;
for (i=0; *(pszMypath+i)!='\0'; i++) {
if (*(pszMypath+i)=='/') {iPos=i+1;}
}
fprintf(stderr, "Usage : %s <seconds>\n",pszMypath+iPos);
exit(1);
}
void errmsg(char* pszMypath) {
int i;
int iPos = 0;
for (i=0; *(pszMypath+i)!='\0'; i++) {
if (*(pszMypath+i)=='/') {iPos=i+1;}
}
fprintf(stderr, "%s: Error happend while nanosleeping\n",pszMypath+iPos);
exit(1);
}
「C言語で書けばいい」といったらなんでもアリで反則のように聞こえるかもしれないが、CプログラムであってもPOSIX原理主義はちゃんと守っている。
stdio.hやstdlib.hはもちろん、limits.hもtime.hも、そしてその中で定義されているnanosleep関数も、POSIX文書に載っているから利用している。
考えてみてもらいたい。K&R本の“Hello, world”プログラムがなぜ30年以上も動くのか?
- 「普及している言語のプログラムだから」
- 「短いから」
違う。後にPOSIXにも採用されるような「基本的な文法やライブラリー」しか用いていないからだ。POSIXという規格が存在する今、「基本的な文法やライブラリー」かどうかを判断するには、POSIXで規定されているかどうかで判断するのが有効なのではないだろうか。
だから私はPOSIX原理主義を提唱している。
さあ、実行してみよう。
AIXで試してみた。なぜかというと、AIXのsleepコマンドは秒未満の時間指定ができないからだ。
$ sleep 0.1
sleep: bad character in argument
$
ここで、ソースコードを書いてコンパイルをしてみる。(コマンド名がc99ではなくccな理由は後述)
$ vi sleep.c
:
(上記ソースコードを書いて保存する)
:
$ cc -o sleep sleep.c
$
無事コンパイルが通ったので、時間を計りながら実行してみると。
$ time ./sleep 0.1
real 0m0.10s
user 0m0.00s
sys 0m0.00s
$
うまくいった。
(補足1) C99が入ってないんだけど
確かに、そういうUNIX系OSが多いのが実情だ。でもほとんどの環境では心配要らない。
パターン1. “cc”というコマンド名になっている
多くのUNIX系OSは、慣例的に“cc”というコマンド名で使えるようになっている。じつは、先程例示したAIXもこのパターンだ。従って“c99”の代わりに“cc”という名前で試してみればよい。
パターン2. ベンダー独自のコンパイラーが用意されている
マーケティングやライセンス上の理由によって、独自のCコンパイラーが用意されている環境もある。
先程のAIXではxlcというIBM特製のコンパイラーが入っているし、多くのLinuxではgccが入っているし、FreeBSD 10以降ではclangが入ってる。
大抵はそれらが、ccという名前からリンクされているのだが、そうなってない場合もまれにある。
もし見つからないようであれば、とりあえず
$ whatis compiler
などと打ち込んで探してみるとよい。
パターン3. パッケージとして用意されている
OSによってはあえて外部パッケージにしているものもある。コンパイル環境を用意するとなると、単にコンパイラーだけでなく、OSシステムコールなどのライブラリー一式も必要になるので重荷なのだろう。
例えば、今テストリリースされているUbuntu on Windowsの場合、最初こんなメッセージが出る。
$ cc
The program 'cc' can be found in the following packages:
* gcc
* clang-3.3
* clang-3.4
* clang-3.5
* tcc
Try: sudo apt-get install <selected package>
$
そこで、指示通りにパッケージインストールしたら使えるようになった。(“cc”という名前でも)
POSIXを意識したOSであるなら、デフォルトで入っていなくてもパッケージとして簡単に入れられるようなっているはずなので、確認してもらいたい。
(補足2) そもそもなぜC言語を認めるの?
POSIX原理主義はC言語を使うなというものではない。
POSIX原理主義で使える言語は、POSIX文書に記載されているコマンドから扱えるという理由で次の2つに限定される。
- Bourneシェルスクリプト(shコマンドによる)
- C言語(c99コマンドによる)
つまりシェルスクリプトかC言語のどちらかということになる(AWKも言語として数えるならAWKもだが……)。従ってどちらを使ってもPOSIX原理主義に矛盾はしない。
しかし両者を比較すると、C言語は
- 低水準ゆえ、ハードウェア構造を意識しながら書かなければならない局面がある。(バイトオーダー等)そのようなプログラムは環境依存を引き起こす。
- シェルスクリプトよりも実装が大変である。(特に文字列処理)
- 確かに速く書けるが、シェルスクリプトもコマンドを上手に使えば、実用上それほど劣らない。
などの特徴がある。これが理由で、C言語よりシェルスクリプトの方を積極的に勧めているだけだ。
過去にjqやxmllintコマンド等を否定し、独自のJSONやXMLパーサーをシェルスクリプトで組んだこともあったが、それらをはじめ、過去シェルスクリプトで実装した各コマンドがPOSIXの範囲に閉じたC言語で書けるなら別にそれでもかまわない。
かまわないが、C言語で書かないとダメな局面が訪れないかぎり、すべきではない。それは、UNIX哲学の教えでもある。
でも紹介されているとおりだ。