web-dev-qa-db-ja.com

一部のシェル `read`ビルトインが` / proc`のファイルから行全体を読み取れないのはなぜですか?

一部の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
19
cuonglm

問題は、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(またはcatheadなどのデータを読み取る他の組み込み関数)は、データを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
_
31

なぜであるかを知りたい場合は、カーネルソースでその答えを確認できます こちら

_    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を使用していません。

9
meuh

最初の試みでは、これは実際の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カーネルのバグを引き起こしません。

3
schily

/ procの下のファイルは、ファイル内のフィールドを区切るためにNULL文字を使用する場合があります。 readはこれを処理できないようです。

0
Tony George