#■ 問: シェルスクリプトで 0.01 秒間などのごく短い時間だけ sleep する方法は? それもできるだけ精確に。
この問いは一見して簡単な問いに思われるが、考えて見ると意外と難しい。
後で詳しく説明するが、POSIX (Unix の規格) では sleep
に小数は指定できない。また、外部コマンド sleep
の起動自体に余分な時間が掛かることも問題になる。
- 記事更新 (2020-04-11): 記事 "POSIX準拠シェルスクリプトだけで1秒未満sleepを実現する - Qiita" 言及
-
記事更新 (2018-08-18): Bash 5.0 以降の
EPOCHREALTIME
について追記 -
記事更新 (2018-08-18): 最近の Cygwin の
/dev/tcp/*/*
について追記・修正 - 記事更新 (2018-07-26): Bash 4.4 以降の Bash loadable builtins について追記
おまけ: sleep 0.01
を使ったシェルアニメーション (スクリプトはこの記事の最後)
## 先に答え? (現状の案 for bash-4.0+/zsh)
上記の問いがどう難しいのかだとかそういう説明を全部飛ばして先に結果を書いてしまう。色々考えたり測定してみたりした結果、今のところの最良案 (シェル関数版 sleep
) は以下である。組み込みコマンド read
のタイムアウト (小数の指定は bash-4.0 以上) を使っている。
sleep.sh
# function sleep for bash-4.0+/zsh
##
## 関数 sleep time
## スリープする。
##
## @param[in] time 待ち時間。単位は秒。小数も可能。
##
## 例: $ sleep 0.1
##
## 注意: ファイルディスクリプタ 9 を別の用途に使うと駄目
##
function sleep { local REPLY=; ! read -u 9 -t "$1"; }
if [[ $OSTYPE == cygwin* ]]; then
# Cygwin work around
exec 9< <(
[[ $- == *i* ]] && trap -- '' INT QUIT
while :; do command sleep 2147483647; done
)
if [[ $BASH_VERSION ]]; then
function sleep {
local s="${1%%.*}"
if ((s>0)); then
! read -u 9 -t "$1" s
else
# 修正 (2018-08-18): 最近の Cygwin (もしくは最近の Windows?)
# では /dev/tcp/... を使うと失敗するので、/dev/udp/... に変更。
! read -t "$1" s < /dev/udp/0.0.0.0/80
fi
}
fi
else
rm -f ~/.sleep.tmp
mkfifo ~/.sleep.tmp
exec 9<> ~/.sleep.tmp
rm -f ~/.sleep.tmp
fi
```
シェル関数で `sleep` を上書きする実装にしているので、`sleep 小数` を使っている既存のスクリプトがあれば、`source sleep.sh` するだけで POSIX 環境に対応できる。
もっと良い `sleep 小数` の方法・綺麗な解があれば教えてほしい。
改善の余地?
- 予め `sleep` 自体にかかる余分な時間を計測して補正を行う?
- `/proc/uptime` が利用可能なシステムでは、それを見てずれを補正可能かも。
- bash-4.1+/zsh なら `{fd}<> ...` で未使用のファイルディスクリプタを自動で割り当てられる。
※本当はアニメーションにするには、単なる `sleep` では余りよくない。フレームの描画に掛かる時間を差し引いて `sleep` しなければならない。そのためには精確で高速な時刻計測が必要になるが、話がややこしくなるのでここでは考えない。
----
#■ 前文
まあ、実際にこういう `sleep 小数` の需要がそうそうあるのかどうかは分からないけれど…うーん…例えば *シェルスクリプトで端末にアニメーションを表示したい* とか? 正直なところ、**まともに考えればそんなことでシェルスクリプトに拘る理由はない。もっと便利な別の言語を使えば良いのである。**
しかし、**稀にシェルスクリプトで書かなければならないときがある**。例えば、`bash` の拡張 (`.bashrc` から読み込むようなの) をシェル関数で書くときである。特に、それを他の人に配布する場合には、できるだけ他のツールに依存せず `bash` (+ POSIX 環境) だけ存在していれば動くようにしたい。実際に [`bash` 用の拡張の ble.sh](http://qiita.com/akinomyoga/items/22bbf8029e6459ed57ba) を書いていてちょっと困ったのである。
以降ではもう少し詳しくこの `sleep` の問題について調べる。構成は以下の通り。1. で問題点を説明し、2. で複数の解決法を挙げる。3. で実測して比較し、4. で応用例としてアニメーションを表示してみる。ちなみに、この記事では `bash` での実現が主目的であり `zsh` は序でである。
#■ 1. シェルスクリプトの sleep 問題
二種類の問題がある。
##:arrow_forward: 1.1 POSIX では `sleep 小数` は使えない
ひとつ目の問題。POSIX では `sleep` コマンドは整数 (単位は秒) を指定した時のことしか規定されていない ([sleep - The Open Group](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sleep.html))。つまり、sleep コマンドに小数を指定しても動かない UNIX 環境がありうるということである。少なくとも GNU/Linux だとか Cygwin では `sleep (GNU coreutils)` が入っていて 0.01 などの小数を使うことができる。一方で AIX では小数は指定できないようだ ([AIXで1秒未満のsleepを実施 - Qiita](http://qiita.com/regeek/items/fc2fbac83ac33d1de0b5))。Mac OS X, FreeBSD, Solaris, HP-UX, etc. ではどうかしら。
ところで、それぞれの環境で `sleep 小数` が使えるかどうか調べるために Qiita を徘徊していたら `sleep 小数` を使っているスクリプトは結構たくさんある。やはり需要は大きいようだし、また、`sleep 小数` は事実上の標準になっている気配がする。
> **Qiita内の調査結果**
>
> 以下は `sleep 小数` を使っている記事。
>
- 2011/10/25 [markdown をコマンドラインでプレビュー](http://qiita.com/k_ui/items/2d45b0cd33b2acc3836e)
- 2014/05/23 [シェルスクリプトでコマンド多重コントロール](http://qiita.com/TanukiTam/items/b64e0669e479c26d4790)
- 2014/05/26 [シェルスクリプトで日付と時間を大小判定](http://qiita.com/TanukiTam/items/6572ea18c2de80ebf458)
- 2014/06/16 [同じ行に出力する - Qiita](http://qiita.com/mattintosh4/items/3ef2334631763986e724)
- 2014/09/24 [Mplayerで再生中の曲をtmuxのステータスラインに表示する方法](http://qiita.com/syui/items/384b57ba081659c9f082)
- 2014/12/21 [Zshで長い処理をしている間に読込中を表示する](http://qiita.com/petitviolet/items/5cc1916eb3fdb8d54823)
- 2015/03/29 [ターミナルで簡単にグラフを描くツール termeter](http://qiita.com/atsaki/items/e7d2e53bac8ba6fdbce0)
- 2015/05/09 [bcコマンドの限界は68桁?(回避方法あり](http://qiita.com/178inaba/items/5836a09e28e628f61de9)
- 2015/06/16 [CI用にヘッドレスなAndroidエミュレータを複数台立ち上げるシェルスクリプト](http://qiita.com/muran001/items/ffd56f8a7dd76e3968fb)
- 2015/06/19 [Self-extracting Shell Script](http://qiita.com/dharry/items/f1f9b9e003e78e87c849)
- 2015/10/22 [ShellScriptで使えるメソッドまとめ](http://qiita.com/KENJU/items/a318686d82ce6ea65223)
>
以下は環境を指定した記事なので `sleep 小数` でも大丈夫。
>
- 2013/09/21 [MacVim をコマンドラインから起動する](http://qiita.com/b4b4r07/items/9013e19ba47fd07e87b5)
- 2014/06/03 [Linuxサーバのパフォーマンス測定スクリプト](http://qiita.com/TanukiTam/items/0b0e07b248e85c31303b)
- 2015/08/04 [Linux でミリ秒まで表示するワンライナー時計](http://qiita.com/ikesato/items/4e1d2ff5251f6805b2a0)
- 2016/04/28 [MSYS2で快適なターミナル生活](http://qiita.com/Ted-HM/items/4f2feb9fdacb6c72083c)
>
以下は分かりやすくするためにわざわざ `sleep 1s` だとか `sleep 5m` だとかとしているが、この指定方法は勿論 POSIX にはない。
>
- 2016/01/03 [シェルスクリプトでだいたい1時間の間隔であれをやる](http://qiita.com/piroor/items/8682faddd4b0c7d397a1)
- 2016/03/15 [bashコマンドの出力を延々とログとるスクリプト](http://qiita.com/moriyant/items/6e88398ac0b61748b574)
>
下の記事では "おまけ" で無限スリープ `sleep inf` についての考察がある。
>
- 2015/09/10 [Process Substitutionとexec redirectで画面出力を加工するときの問題点と解決、そして無限に寝る話](http://qiita.com/takei-yuya@github/items/7afcb92cfe7e678b7f6d)
>
**追記**: また `usleep` や `sleepenh` などの "秒以下を扱える強化版コマンド" を使っている記事もある。もちろんこれらのコマンドがあるかどうかは `sleep 小数` にもまして環境に依存する。2番目の記事のように `sleep` を fallback とするのが賢いやり方である。
>
- 2013/03/29 [ディレクトリ配下のファイルを再帰的に並列でrsyncする](http://qiita.com/nekogeruge_987/items/ed1bd8704b25b1f509ec)
- 2015/03/18 [ターミナルが256色使えるかどうか確認するbashスクリプトを作った](http://qiita.com/844196/items/294bf1960b55cb574661)
- 2016/01/14 [シェル芸力向上のため moreutils を一通り試してみた](http://qiita.com/ngyuki/items/ad7d52186a84cc973438)
##:arrow_forward: 1.2 `sleep` 自体の起動に時間がかかる
もうひとつの問題は `sleep` コマンドの起動にかかる時間である。シェルから外部コマンドの `sleep` を立ち上げるためには、新しくプロセスを生成することになるので多少時間がかかる。`sleep` は自分が起動される原因となったイベントがいつ起こったかを知らないから、当然自分が起動完了した瞬間から測ってスリープを行う。つまり、`sleep` 呼び出し元から見た停止時間は `起動時間 + 指定した時間` になる。
ではこの起動時間はいかほどだろうか。プロセスの起動時間はシステムによる。環境によっては大体ミリ秒程度で済むが、十ミリ秒ぐらいかかることも普通にある。Cygwin などの環境にいたっては数十ミリ秒かかるのが普通である。(Cygwin なんか使うのが悪いという声が聞こえてきそうだが、Cygwin でもシェルくらいはまともに動作してほしい。) 最近 "Bash が Windows 10 上で動く" という話で賑わしている Windows Subsystem for Linux ではもっと遅いという噂 ([Windows Subsystem for Linux の fork は速いのか - Qiita](http://qiita.com/temmings/items/0c704ddbd0dd491eb4e2)) も。つまり、整数単位の `sleep` では誤差にしかならなかった起動時間が、ミリ秒単位でのスリープでは深刻なずれを引き起こす。
また、プロセスの起動に時間が掛かるということは、それなりのコストがあるということである。例えば、アニメーションなどに使うとなると一秒間に何十回も `sleep` を実行することになるがこれは結構な負荷である。たかが sleep ごときでホストに負荷をかけたくない。したがって `bash`/`zsh` の組み込みコマンドの範囲内でどうにかしたい。
#■ 2. 色々の方法
ここでは `sleep` を実現するための様々な方法を挙げている。特に 2.3.2/2.3.4 の方法を用いれば POSIX すら必要なくて、bash の組み込み機能だけで実現可能であることが興味深い。
##:arrow_forward: 2.1 ビジーウェイト
さて、シェルの組み込み機能だけで `sleep` を実現したい。しかし bash/zsh で `sleep` が組み込み機能として提供されていない以上は、普通に考えて不可能な気がする。そこで "最終手段" ビジーウェイトに手を出す誘惑に駆られる。つまり、何もしないループを回して時間を潰す。
```bash
while 指定の時刻になるまで; do :; done
```
この方法を忌避する第一の理由は、CPU に負荷をかけることである。`sleep` するだけのために、ファンをいつもより多く回すという虚しさ。
しかし、CPU の負荷を気にしないとしてもこの方法はすぐ頓挫する。"`指定の時間になるまで`" をミリ秒単位で実現する可搬な方法が<font color="green">Bash 5.0 未満では<sup>**[追記 2018-08-18]**</sup></font>ないからである。
1. **date**: 例えば `date (GNU coreutils)` を使えば `%N` で秒以下の単位を取得できるが、それならそもそも `sleep (GNU coreutils)` を使っておけば良い話である。そして POSIX の `date` には `%N` はないので秒単位の計測はできない。
2. **printf '%()T'**: ミリ秒単位の精度を保証するためにはプロセスを起動せずにシェルの組み込み機能だけで実現したい。組み込み機能で時刻を計測するといえば Bash-4.2 で追加された `printf '%()T'` が使えるが、これも残念ながら `%N` など秒以下の単位に対応していない。
3. **/proc/uptime**: もしシステムが `/proc/uptime` に対応していれば、ひたすら `read time < /proc/uptime` で値を読みだすという手も使える。プロセスを起動するわけではないので高速である。しかし `/proc/uptime` が使えるシステムは `sleep` が小数に対応しているシステムよりも少なそうだ。
4. <font color="green">**EPOCHREALTIME**: Bash 5.0 以降では特殊シェル変数 `EPOCHREALTIME` が追加され、この変数を読み取ればその瞬間の Unix 時間を秒単位の小数で取得できるようになる。例えば、`1534564384.985884` などの文字列が取得できる。<sup>**[追記 2018-08-18]**</sup></font>
5. <font color="green">**ループ速度の計測**: @ko1nksm さんの記事 "[POSIX準拠シェルスクリプトだけで1秒未満sleepを実現する - Qiita](https://qiita.com/ko1nksm/items/725a1ac53fab7991b0ee)" ではループ速度を計測してループ回数を事前に計算してビジーウェイトしている。さらにCPU負荷を下げるために、複数の `sleep 1` ループをバックグラウンドでずらして起動して、本体のシェルにシグナルを送っている。POSIX の範囲内で実現しているが、記事中にもあるように精度に難あり。`sleep 0.01` 相当を実現するにはバックグラウンドで `sleep 1` を1秒に100回実行する負荷があるし、長時間の実行でバックグランド間のタイミングがずれる。ミリ秒精度の用途には難しそう。<sup>**[追記 2020-04-11]**</sup></font>
##:arrow_forward: 2.2 Bash Loadable Builtins (`enable -f ./sleep.so sleep`) を使う
Bash には強力な機能がある。動的に組み込みコマンドを追加できる機能だ。それ専用にビルドされた共有ライブラリを用意して、それを `enable -f` で読み込む。後付なのに "組み込みコマンド" という名前は妙だが、シェルコマンドの区分としては確かに "組み込みコマンド" として取り扱われる。つまり、外部コマンドとしてプロセスを起動するのではなく、`bash` のプロセス内で実行される。C/C++ などで書いた自分の好きなプログラムを `bash` 内で実行させることができるのだ。ちなみに `zsh` にも `enable -f` はあるが意味はまったく異なり、この目的では使えない。
そして実際に **Bash の開発者もスリープ問題に気づいていたのか**、なんと bash のソースコードに**附録**として `sleep.c` がついてくる。`sleep.c` をコンパイルしてできる `sleep.so` を使えば以下のようにして新しい組み込みコマンド `sleep` を動的に読み込める。
```bash
enable -f ./sleep.so sleep
```
しかし、この `sleep.so` は<font color="green">Bash 4.4 未満では<sup>**[追記 2018-07-26]**</sup></font>デフォルトでビルドされないし、Linux の distribution でも配布されているのを見たことは<font color="green">なかった<sup>**[修正 2018-07-26]**</sup></font>。 <font color="green">Bash 4.4 以降ではデフォルトで附録のビルトインコマンドがビルドされる様になり、それに伴って一部の Linux distribution では `bash-builtins` などのパッケージ名で配布されるようになった。<sup>**[追記 2018-07-26]**</sup></font>
<font color="green">Bash 4.3 以前において<sup>**[追記 2018-07-26]**</sup></font> `sleep` をビルドするためには Bash 本体のソースコードが必要である。そして、様々なシステムや Bash のバージョンがあることを考えるとバイナリを直接配布するわけには行かない。つまり、この機能を利用するためには、シェルスクリプトの**ユーザの側**でおのおの以下のようなことをしてもらう必要がある。
1. 自分の使っているのと同じ version の bash のソースコードを手に入れる
2. `./configure` & `make` する
3. `sleep.c` は `bash-4.3/example/loadables` 的な場所にある。そこに入って `make` する。
4. 完成した `sleep` をシェルスクリプトによって予め定められた場所に配置する
可搬ではないどころかとてつもなく面倒くさい。また、Cygwin では Makefile やら何やらを色々書き換えてようやく `sleep.c` をビルドできた。更に、共有ライブラリ (DLL) の仕組みが特殊なシステムの場合、そもそもこのような仕組みを使えないという可能性もある (MinGW などではそもそもビルドできるのかどうかすら怪しいんじゃないかこれ…)。
**追記**: Bash Loadable Builtins の使い方に関しては以下が詳しい。
- [bashの組込みコマンド自作によるスクリプトの高速化](https://satoru-takeuchi.hatenablog.com/entry/2020/03/26/010711) ([Qiita](http://qiita.com/satoru_takeuchi/items/7d424fa5ef1e33ace4df)より移動)<br/>コンパイル方法・利用方法・利点・欠点などについてまとめられている。
- [本を読む bashの内蔵コマンドを自作してみた](http://emasaka.blog65.fc2.com/blog-entry-499.html)<br/>特殊変数の定義方法についても述べられている。
##:arrow_forward: 2.3 読み込みのタイムアウトを使う: `read -t 0.01`
さて、bash/zsh には組み込みのコマンドとして `sleep` はないものの、考える範囲を拡げれば実行をブロックする組み込みコマンドが存在する。`read` だ。そして、なんとタイムアウト (単位は秒) を引数に指定することができる。特に bash-4.0 以上ではタイムアウトとして小数を指定することができる。
```bash
read -t 0.01
```
しかしこのままの形で使ってしまうと、なにかが標準入力にあるとタイムアウトする前に読み取りが完了して `read` がすぐに終了してしまう。そこで、何か読み取りに無限に時間のかかる (つまり何も読み取れない) ストリームに標準入力をつなぐ。
###:bulb: 2.3.1 /dev/null
```bash
read -t 0.01 < /dev/null
```
は駄目だ。"何も文字が読み取れない (ストリームの終端)" ということがすぐにばれてしまうので、`read` はタイムアウトする前に読み取りを諦めてしまう。
つまり、必要なのは "内容を送る送るといいつつも、いつまで経っても送ってくれないストリーム" である。
###:bulb: 2.3.2 /dev/tcp/0.0.0.0/80
次に試したのは bash の特別ファイル名 `/dev/tcp/ip/port` である。このファイル名にリダイレクトを行うと、Bash が勝手に `ip:port` に対して TCP 接続 (socket) を開いてくれる。ここで、存在しないサーバに対して接続を試みればブロックされるのではないか、ということである。0.0.0.0 という IP アドレスは使われないはずなのでこれで試してみた。
```bash
read -t 0.01 < /dev/tcp/0.0.0.0/80
```
TCP 接続のタイムアウト (少なくとも 1 秒よりは長かろう) よりも短い時間の sleep ならばこの方法で動く。
しかし、この方法の問題点は "実際に TCP 接続を試みる" ということである。一秒間に何十回も sleep を実行するとネットワークに負荷をかけるおそれがあるし、そもそも socket の数の上限とかそういうのにひっかかるかもしれない。お手軽ではあるが実用にはならないだろう。
###:bulb: 2.3.3 exec 9<> 名前付きパイプ
あるいは名前付きパイプを開いて、書き込み側が何も書き込まずに待てば良い。読み書き両方を自分の適当なファイルディスクリプタに割り当ててしまい、書き込みはせずに読み込みだけすることにすれば良い。
```bash
# 前準備
mkfifo sleep.tmp
exec 9<> sleep.tmp
rm -f sleep.tmp
read -t 0.01 <&9
```
なかなかいい感じに動く。
名前付きパイプの扱いに慣れない人のために、前準備の部分について細かい説明を追加しておく:
- まず `mkfifo` は名前付きパイプをファイルシステムの何処かに作る。
- `exec` でその名前付きパイプを読み書き両用 (`<>`) で開く。`exec リダイレクト` を用いて自プロセス(シェル)の
ファイルディスクリプタを弄れることを使う。上記の例では 9 番にパイプを紐付ける。
- 設置した名前付きパイプを後で削除するのは面倒なので、すぐに `rm` で削除する。ファイルシステム上ディレクトリから unlink されても、ファイルディスクリプタが生きている限りは、その名前付きパイプはシステム上に残ることを利用したやり方。
###:bulb: 2.3.4 exec 9< プロセス置換
上の名前付きパイプの方法はいい感じに動く。しかし **Cygwin では名前付きパイプが完全には実装されていない** ([Eric Blake - bi-directional named pipe](https://cygwin.com/ml/cygwin/2009-07/msg00081.html)) **ので、この方法をやろうとすると失敗する**。`read` で `Communication error on send` とかいうエラーが出る。
一方で、プロセス置換は (内部的に名前付きパイプを使っているものの) Cygwin 上でも動く。というわけでプロセス置換にしてみる。
```bash
# 前準備
exec 9< <(
[[ $- == *i* ]] && trap -- '' INT QUIT
while :; do command sleep 2147483647; done
)
read -t 0.01 <&9
```
書き込みを行う側の子プロセスでは何も書き込みをせずにひたすら sleep する。
ただし、子プロセスが本体のシェルより先に終了してしまわない様に注意する。特に、対話シェル上 (`[[ $- == *i* ]]`) では `C-c` (`SIGINT`) や `C-\` (`SIGQUIT`) を押しても本体のシェルは終了しない。しかし、子プロセスはこれで見事に終了してしまう。これを防ぐために `trap` している。しかし、この様に対策したとしても子プロセスが勝手に死なないかどうか不安が残る。
#:black_large_square: 3. 実測してみよう
2.2 `enable -f` と 2.3 の `read -t` のアイディアを利用して様々な `sleep` を実装する。それぞれで `sleep 0.001` の時間を計測してみる。Bash-4.3 を使う。結果が 1.00 ms に近ければ嬉しい。
:hourglass: **外部コマンド** (比較用)
先ずは比較のためにコマンド `date (GNU coreutils)`、`usleep`、`sleepenh` を計測する。ソースコードは、適当に検索して [function-src-8.53/usleep.c](https://github.com/yars068/slackware-stuff/tree/8d4d3642145b041351f3ad5415c9a86bc1911338/sysvinit-functions/function-src-8.53) と [nsc-deb/sleepenh-debian](https://github.com/nsc-deb/sleepenh-debian) から持ってきた。
| コマンド | 環境1 Cygwin | 環境2 GNU/Linux |
|-----------------------------------------|----------|---------|
| `command sleep 0.001` (GNU coreutils) | 61 ms | 4.17 ms |
| `command usleep 1000` | 62 ms | 4.03 ms |
| `command sleepenh 0.001` | 62 ms | 3.76 ms |
:hourglass: **Bash Loadable Builtin `sleep`**
```bash:準備
enable -f ./sleep.so sleep
```
| コマンド | 環境1 Cygwin | 環境2 GNU/Linux |
|-----------------------------------------|----------|---------|
| `builtin sleep 0.001` (enable -f) | 1.8 ms | 1.21 ms |
:hourglass: **Bash ネットワーク用特殊ファイルを `read`**
| コマンド | 環境1 Cygwin | 環境2 GNU/Linux |
|-----------------------------------------|----------|---------|
| `read -t 0.001 < /dev/tcp/0.0.0.0/80` | 2.0 ms | 1.41 ms |
| `read -t 0.001 < /dev/tcp/127.0.0.1/80` | 2.0 ms | 1.41 ms |
| `read -t 0.001 < /dev/tcp/##.##.##.2/80`| 23 ms | 14.1 ms |
| `read -t 0.001 < /dev/udp/0.0.0.0/80` | 3.1 ms | 1.31 ms |
> **追記** (2018-08-18): 最近の Windows では `read < /dev/tcp/0.0.0.0/80` は失敗するようである。`read < /dev/udp/0.0.0.0/80` は動く。
>
> ```bash:
> $ time read -t 0.1 < /dev/tcp/0.0.0.0/80
> bash: connect: Connection refused
> bash: /dev/tcp/0.0.0.0/80: Connection refused
>
> real 0m1.001s # ← 1秒間 (指定したよりも長い
> user 0m0.000s # 時間) 待って失敗している。
> sys 0m0.000s #
> $ time read -t 0.1 < /dev/udp/0.0.0.0/80
>
> real 0m0.101s
> user 0m0.000s
> sys 0m0.000s
> ```
:hourglass: **名前付きパイプを `read`**
```bash:ファイルディスクリプタを準備
mkfifo tmp1
exec 7<> tmp1
mkfifo tmp2
(exec 3> tmp2; sleep 2147483647) & exec 8< tmp2
exec 9< <(
[[ $- == *i* ]] && trap -- '' INT QUIT
while :; do command sleep 2147483647; done
)
```
| コマンド | 環境1 Cygwin | 環境2 GNU/Linux |
|-----------------------------------------|----------|---------|
| `read -t 0.001 <&9` | 12 ms | 1.21 ms |
| `read -t 0.001 -u 9` | 12 ms | 1.22 ms |
| `read -t 0.001 -u 8` | 12 ms | 1.21 ms |
| `read -t 0.001 -u 7` | エラー | 1.20 ms |
##3.1 グラフ
正しい待ち時間 1.00 ms からのずれ (遅延) をグラフにしてみる。縦軸は**対数スケール**であることに注意。短い方がよい。(**※編集2016-06-27**: コマンド `sleep` たちを追加。縦軸を 1 ms からのずれ(対数スケール)に変更。)
![benchmark2.png](https://qiita-image-store.s3.amazonaws.com/0/63439/2c3d8333-e0b0-6f38-4aa1-d95a66d4cc73.png)
:pushpin: **GNU/Linux**
- 基本的に **`read -t` & 名前付きパイプが速い(ずれが少ない)**ようだ。
- 驚くべきことに、`enable -f` を通して読み込んだ**組み込みコマンド `sleep` は別に `read -t` と較べて速い訳ではない**ようだ。名前付きパイプと変わらない。
:pushpin: **Cygwin**
- 一番遅いのはもちろん外部コマンド起動だが、**名前付きパイプも遅い**。
- 代わりに、**`/dev/tcp/0.0.0.0/80` が速い**。ネットワークに負荷をかけるのではないかと懸念したが、実際にやってみると通信をしている気配はない。少なくとも Cygwin では `/dev/tcp/0.0.0.0/80` にアクセスしまくっても問題はなさそうだ。
- 序でに、実際に存在する IP アドレス `##.##.##.2` について同様に `read` してみたらネットワーク I/O が生じるようだし遅延時間も大きい。つまり、**有効な範囲の IP アドレスで `/dev/tcp` による `sleep` をするのは良くない**。
#■ 4. おまけスクリプト (bash-4.0+/zsh)
*この記事で何か楽しいことをしているようにに見せかける* ために `sleep` を使って端末の中で動くアニメーションを作ってみる。スクリーンセーバー的な何か。ダウンロードは [Shell Animation using sleep - GitHub Gist](https://gist.github.com/akinomyoga/fb8fa72b8016b624227e66fb9b39fdc9) の [Download Zip](https://gist.github.com/akinomyoga/fb8fa72b8016b624227e66fb9b39fdc9/archive/40ef563fa4b2a428ac181357cad0414e963e2f32.zip) から。xterm 系の端末制御シーケンス(256色対応含む)を吐くので、端末によっては動かないことに注意。
```bash:snake.sh
#!/bin/bash
source sleep.sh
[[ $ZSH_VERSION ]] && setopt KSH_ARRAYS
: ${COLUMNS:=$(tput cols)} ${LINES:=$(tput lines)}
((xN=${COLUMNS:-80}-1,yN=${LINES:-24}-1,mfp=((xN+yN)/15+3)*2))
printf '\e[?47h\e7\e[m'
trap -- "printf '\e[m\e8\e[?47l'" 0
[[ $ZSH_VERSION ]] && trap -- exit INT QUIT
let color=196 'colors[cN++]=color+='{6,-36,1,-6,36,-1}{,,,,}
x_iswall='x==0 &&d==2||x==xN-1&&d==0||y==0 &&d==3||y==yN-1&&d==1'
movechar=CBDA
x_move=(x++ y++ x-- y--)
while :; do
((x=RANDOM%xN,y=RANDOM%yN,d=0,c=0))
printf '\e[H\e[2J\e[%d;%dH' $((y+1)) $((x+1))
for ((i=0;i<xN*yN;i++)); do
((((a=RANDOM%mfp)<2||x_iswall)&&(
d=(d+1+(a&1)*2)%4,
x_iswall&&(d=(d+2)%4)),
x_move[d]))
printf "\e[${movechar:$d:1}\e[48;5;${colors[c++/5%cN]}m \e[D"
sleep 0.01
done
printf "\e[H\e[${yN}B\r\e[m"
done
```
**使い方**
動作には `sleep.sh` と上記 `snake.sh` が必要。終了は `C-c` (`SIGINT`) で。
```bash:使い方
$ ls
sleep.sh snake.sh
$ ./snake.sh
```
![snake2.png](https://qiita-image-store.s3.amazonaws.com/0/63439/2face020-68f9-5431-546b-d2059988fc2b.png)
※ファイルサイズが大きくなるので静止画
幅と高さは既定では端末の画面の大きさになっている。これらを明示的に指定するときは環境変数 `COLUMNS` と `LINES` でどうぞ。
```bash:幅と高さの指定
$ COLUMNS=15 LINES=10 ./snake.sh
```
![snake.gif](https://qiita-image-store.s3.amazonaws.com/0/63439/3295afa4-ea6b-b278-d89b-531674b73bda.gif)