頭が余分なキャラクターを食べる


15

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

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は期待どおりに出力します。ただし、これはユーティリティのcoreutils実装でのみ機能しheadます。busyboxのの出力がちょうどあるので、実装はまだ、余分な文字を食べます12345

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

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

質問です。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)

単純化のために使用しませんでした。


2
Neardupe unix.stackexchange.com/questions/48777/...unix.stackexchange.com/questions/84011/...。また、このタイトルがmovies.SXにあった場合、私の答えはZardozです :)
dave_thompson_085

回答:


30

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

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

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

はい、POSIX volume 3、Shell&Utilities

そして、もしあれば、この動作を指定しますか?

はじめに:

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

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

GNU head ファイル記述子を正しい位置に残そうとしますが、パイプを探すことは不可能であるため、テストでは位置を復元できません。これを使用して見ることができます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のバイトを移動しようとするが、それができません。(ここで、GNUheadが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

2
問題を回避するために、一度に1バイトずつ読み取る代わりに、line(POSIX / XPGから削除されたが、多くのシステムでまだ使用可能な)またはreadIFS= read -r line)ユーティリティを使用できます。
ステファンシャゼル

3
head -c 55バイトを読み込むか、バッファ全体を読み込むかは実装に依存することに注意してください(またhead -c、標準ではないことに注意してください)、これに頼ることはできません。dd bs=1 count=55バイトを超えないことが保証される必要があります。
ステファンシャゼル

@Stéphaneに感謝し-c 5ます。説明を更新しました。
スティーブンキット

head組み込みは、入力がシーク可能でないときにksh93一度に1バイトを読み取りますhead -n 1
ステファンシャゼラス

1
@anton_rh、ddだけでパイプで正しく動作bs=1しますが、使用している場合count、パイプの上に読み込むよう(EOFに到達しない限り、少なくとも1バイト)、要求より少ない戻してもよいです。GNU ddiflag=fullblockそれを軽減することができます。
ステファンシャゼル

6

POSIXから

ヘッドユーティリティは、指定された時点で、各ファイルの出力を終了し、標準出力にその入力ファイルをコピーしなければなりません。

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

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

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

以下の場合はreadシェルスクリプトで使用され、一般的な使用例は、このようなものになるだろう。

read someline
if something ; then 
    someprogram ...
fi

ここで、の標準入力はsomeprogramシェルの標準入力と同じですが、によってバッファリングされた読み取りの後に残ったものではなくsomeprogram、によって消費される最初の入力行の後に来るすべてを読み取ることが期待できます。一方、例のようにasを使用することは、はるかに一般的ではありません。readreadhead


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

$ 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'

しかし、第3巻のPOSIX入門の「入力ファイル」セクションを参照してください。
Stephen Kitt

1
POSIXは次のように述べています。ユーティリティ。シークできないファイルについては、そのファイルのオープンファイル記述でファイルオフセットの状態が指定されていない。
AlexP

2
を使用しない限り、複数の行を読み取ることができることに注意してください(これを使用しないと-r、先頭および末尾のスペースとタブも削除されます(デフォルト値は))。readIFS=$IFS
ステファンシャゼル

@AlexP、はい、スティーブンはちょうどその部分をリンクしました。
イルカチュウ

head組み込みは、入力がシーク可能でないときにksh93一度に1バイトを読み取りますhead -n 1
ステファンシャゼル

1
awk '{if (NR%2) == 1) print;}'

Hellóka:-)そしてサイトへようこそ!注意してください、私たちはより詳細な答えを好みます。これらは、将来のグーグルに役立つはずです。
peterh -復活モニカ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.