web-dev-qa-db-ja.com

頭が余分な文字を食べる

次のシェルコマンドは、入力ストリームの奇数行のみを出力することが期待されていました。

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

ただし、代わりに最初の行aaaを出力します。

-c--bytes)オプションと一緒に使用した場合も同様です。

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

このコマンドは、期待どおり1234512345を出力します。ただし、これはheadユーティリティのcoreutils実装でのみ機能します。 busyboxの実装はまだ余分な文字を食うので、出力は12345だけです。

この特定の実装方法は、最適化の目的で行われていると思います。行の終わりがわからないので、読み取る必要がある文字数がわかりません。入力ストリームから余分な文字を消費しない唯一の方法は、ストリームをバイト単位で読み取ることです。ただし、一度に1バイトずつストリームから読み取ると、速度が遅くなる場合があります。ですから、headは入力ストリームを十分な大きさのバッファに読み込み、そのバッファ内の行を数えると思います。

--bytesオプションを使用した場合も同様です。この場合、読み取る必要のあるバイト数がわかります。そのため、このバイト数を正確に読み取ることができ、それを超えることはできません。 corelibs実装はこの機会を使用しますが、busyboxは使用しません。それでも、必要以上のバイトがバッファに読み込まれます。おそらく実装を簡素化するために行われます。

それで質問です。headユーティリティが入力ストリームから、要求されたよりも多くの文字を消費することは正しいですか? Unixユーティリティには何らかの標準がありますか?ある場合、それはこの動作を指定していますか?

[〜#〜] ps [〜#〜]

上記のコマンドを停止するには、Ctrl+Cを押す必要があります。 Unixユーティリティは、EOFを超えて読み取っても失敗しません。押したくない場合は、より複雑なコマンドを使用できます。

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

簡単にするために使用しませんでした。

15
anton_rh

Headユーティリティが入力ストリームから要求されたよりも多くの文字を消費することは正しいですか?

はい、許可されています(下記参照)。

Unixユーティリティには何らかの標準がありますか?

はい、 POSIXボリューム3、シェルとユーティリティ です。

ある場合、それはこの動作を指定していますか?

それは、その導入で行います:

標準のユーティリティがシーク可能な入力ファイルを読み取り、ファイルの終わりに達する前にエラーなしで終了した場合、ユーティリティは、開いているファイルの説明のファイルオフセットが、ユーティリティによって処理された最後のバイトのすぐ後に適切に配置されるようにします。シークできないファイルの場合、そのファイルの開いているファイルの説明におけるファイルオフセットの状態は指定されていません。

head標準ユーティリティ の1つなので、POSIX準拠の実装は上記の動作を実装する必要があります。

GNU headdoesファイル記述子を正しい位置に残そうとしますが、パイプをシークすることは不可能なので、テストで位置の復元に失敗しました。これはstraceを使用して確認できます。

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

readは17バイト(利用可能なすべての入力)を返し、headはそれらの4つを処理してから13バイト前に戻そうとしますが、それはできません。 (GNU headは8 KiBバッファーを使用することもここで確認できます。)

headにバイトをカウントするように指示すると(これは非標準です)、読み取るバイト数がわかっているため、(そのように実装されている場合)それに応じて読み取りを制限できます。これがhead -c 5テストは機能します:GNU headは5バイトしか読み取らないため、ファイル記述子の位置を復元するためにシークする必要はありません。

ドキュメントをファイルに書き込んで、それを代わりに使用すると、その後の動作が得られます。

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc
30
Stephen Kitt

POSIXから

headユーティリティは、その入力ファイルを標準出力にコピーし、指定されたポイントで各ファイルの出力を終了します。

入力からheadを読み取る必要があるかについては何も述べていません。ほとんどの場合非常に遅いため、バイト単位で読み取るように要求するのはばかげています。

ただし、これはreadビルトイン/ユーティリティで対処されています。パイプから一度に1バイトずつreadを見つけることができるすべてのシェル 標準テキスト を解釈できますこれを行う必要があることを意味し、その1行だけを読み取ることができるようにします。

readユーティリティは、単一の論理行を標準入力から1つ以上のシェル変数に読み取ります。

シェルスクリプトで使用されるreadの場合、一般的な使用例は次のようになります。

read someline
if something ; then 
    someprogram ...
fi

ここで、someprogramの標準入力はシェルの標準入力と同じですが、someprogramreadであり、readによるバッファリングされた読み取り後に残ったものはありません。一方、例のようにheadを使用することは、はるかに一般的ではありません。


1行おきに削除したい場合は、入力全体を一度に処理できるツールを使用することをお勧めします(高速化します)。

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | Perl -ne 'print if $. % 2'
6
ilkkachu
awk '{if (NR%2) == 1) print;}'
1
ijbalazs