一部のBourneのようなシェルでは、read
ビルトインが/proc
のファイルから行全体を読み取ることができません(以下のコマンドはzsh
で実行する必要があります。$=Shell
を置き換えてください$Shell
と他のシェル):
$ for Shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
printf '[%s]\n' "$Shell"
$=Shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6
read
standardには、標準入力がテキストファイルである必要があります 、その要件によってさまざまな動作が発生しますか?
テキストファイル のPOSIX定義を読んで、いくつかの検証を行います。
$ od -t a </proc/sys/fs/file-max
0000000 6 0 2 1 6 0 nl
0000007
$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max
/proc/sys/fs/file-max
の内容にはNUL
文字が含まれていません。また、find
はそれを通常のファイルとして報告しました(これはfind
のバグですか?)。
シェルは内部でfile
のような何かをしたと思います:
$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
問題は、Linuxの_/proc
_ファイルがstat()/fstat()
に関する限りテキストファイルとして表示されますが、そのようには動作しないことです。
これは動的データであるため、それらに対してread()
システムコールを1つしか実行できません(少なくとも一部は)。 2つ以上行うと、2つの異なるコンテンツの2つのチャンクが得られる可能性があるため、代わりに、2番目のread()
は何も返さないようです(ファイルの終わりを意味します)(lseek()
を除いて)最初に戻る(そして最初のみ))。
read
ユーティリティは、改行文字を超えて読み取らないようにするために、一度に1バイトずつファイルの内容を読み取る必要があります。それがdash
が行うことです:
_ $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
read(0, "1", 1) = 1
read(0, "", 1) = 0
_
bash
のような一部のシェルは、read()
システムコールをそれほど多く行わなくても済むように最適化されています。彼らは最初にファイルがシーク可能かどうかをチェックし、可能であればチャンクを読み取ります。そのため、改行を超えて読んだ場合、改行の直後にカーソルを戻すことができます。
_$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR) = 0
read(0, "1628689\n", 128) = 8
_
bash
を使用しても、128バイトを超えるサイズのprocファイルで問題が発生し、1回の読み取りシステムコールでのみ読み取ることができます。
bash
は、_-d
_オプションが使用されている場合にも最適化を無効にするようです。
_ksh93
_は、さらに最適化を実行して、偽になるようにします。 ksh93のread
はシークバックしますが、次のread
のために読み取った余分なデータを記憶しているため、次のread
(またはcat
やhead
などのデータを読み取る他の組み込み関数)は、データをread
にしようとさえしません(たとえそのデータは、間にある他のコマンドによって変更されています):
_$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st
_
なぜであるかを知りたい場合は、カーネルソースでその答えを確認できます こちら :
_ if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
*lenp = 0;
return 0;
}
_
基本的に、シーク(_*ppos
_ not 0)は、数値であるsysctl値の読み取り(_!write
_)には実装されていません。 _/proc/sys/fs/file-max
_から読み取りが行われるたびに、問題のルーチン__do_proc_doulongvec_minmax()
が、同じファイルの構成 table の_file-max
_のエントリから呼び出されます。
_/proc/sys/kernel/poweroff_cmd
_などの他のエントリは、シークを許可するproc_dostring()
を介して実装されているため、_dd bs=1
_を実行してシェルから問題なく読み取ることができます。
カーネル2.6以降、ほとんどの_/proc
_読み取りは seq_file と呼ばれる新しいAPIを介して実装され、これによりシークがサポートされるため、たとえば_/proc/stat
_の読み取りで問題が発生することはありません。ご覧のように、_/proc/sys/
_実装はこのAPIを使用していません。
最初の試みでは、これは実際のBourneシェルまたはその派生物(sh、bosh、ksh、家宝)よりも少ないシェルのバグのように見えます。
元のBourne Shellはブロック(64バイト)を読み取ろうとしますが、新しいBourne Shellバリアントは128バイトを読み取りますが、改行文字がない場合は再度読み取りを開始します。
背景:/ procfsと同様の実装(たとえば、マウントされた_/etc/mtab
_仮想ファイル)には動的コンテンツがあり、stat()
呼び出しは動的コンテンツのre-creationを引き起こしません最初。このため、そのようなファイルのサイズ(読み取りからEOFまで)は、stat()
が返すものとは異なる場合があります。
POSIX標準では、ユーティリティは常に短い読み取りを期待する必要があるため、read()
がorderedバイト数はEOFの表示が壊れています。正しく実装されたユーティリティ呼び出しread()
予想よりも少ない値が返される場合は2回目-0が返されるまで。組み込みのread
の場合は、EOF
まで読み取るだけで十分ですまたはNL
が見つかるまで。
truss
またはトラスクローンを実行すると、実験で_6
_のみを返すシェルの不正な動作を確認できるはずです。
この特別なケースでは、Linuxカーネルのバグのようです。以下を参照してください:
_$ sdd -debug bs=1 if= /proc/sys/fs/file-max
Simple copy ...
readbuf (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf (3, 12AC000, 1) = 0
sdd: Read 1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).
_
Linuxカーネルはを2番目のread
とともに返しますが、これはもちろん正しくありません。
結論:最初に十分な量のデータを読み取ろうとするシェルは、このLinuxカーネルのバグを引き起こしません。
/ procの下のファイルは、ファイル内のフィールドを区切るためにNULL文字を使用する場合があります。 readはこれを処理できないようです。