kepala makan karakter tambahan

15

Perintah shell berikut diharapkan untuk mencetak hanya garis ganjil dari aliran input:

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

Tapi bukannya itu hanya mencetak baris pertama: aaa.

Hal yang sama tidak terjadi ketika digunakan dengan opsi -c( --bytes):

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

Perintah ini menghasilkan 1234512345seperti yang diharapkan. Tetapi ini hanya berfungsi dalam implementasi coreutils dari headutilitas. The busybox pelaksanaan masih makan karakter tambahan, sehingga output hanya 12345.

Saya kira cara implementasi khusus ini dilakukan untuk tujuan optimasi. Anda tidak bisa tahu di mana garis itu berakhir, jadi Anda tidak tahu berapa banyak karakter yang perlu Anda baca. Satu-satunya cara untuk tidak mengkonsumsi karakter tambahan dari aliran input adalah dengan membaca stream byte demi byte. Tetapi membaca dari aliran satu byte pada suatu waktu mungkin lambat. Jadi saya kira headmembaca input stream ke buffer yang cukup besar dan kemudian menghitung baris di buffer itu.

Hal yang sama tidak bisa dikatakan untuk kasus ketika --bytesopsi digunakan. Dalam hal ini Anda tahu berapa byte yang perlu Anda baca. Jadi, Anda dapat membaca persis jumlah byte ini dan tidak lebih dari itu. The corelibs implementasi menggunakan kesempatan ini, tapi busybox satu tidak, masih membaca lebih byte dari yang dibutuhkan ke dalam buffer. Mungkin dilakukan untuk menyederhanakan implementasi.

Jadi pertanyaannya. Benarkah headutilitas mengkonsumsi lebih banyak karakter dari aliran input daripada yang diminta? Apakah ada semacam standar untuk utilitas Unix? Dan jika ada, apakah ini menentukan perilaku ini?

PS

Anda harus menekan Ctrl+Cuntuk menghentikan perintah di atas. Utilitas Unix tidak gagal membaca di luar EOF. Jika Anda tidak ingin menekan, Anda dapat menggunakan perintah yang lebih kompleks:

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

yang saya tidak gunakan untuk kesederhanaan.

anton_rh
sumber
2
Neardupe unix.stackexchange.com/questions/48777/… dan unix.stackexchange.com/questions/84011/… . Juga, jika judul ini ada di film. X Jawaban saya adalah Zardoz :)
dave_thompson_085

Jawaban:

30

Apakah benar utilitas head menggunakan lebih banyak karakter dari input stream daripada yang diminta?

Ya, diizinkan (lihat di bawah).

Apakah ada semacam standar untuk utilitas Unix?

Ya, POSIX volume 3, Shell & Utilities .

Dan jika ada, apakah ini menentukan perilaku ini?

Itu, dalam pengantar:

Ketika sebuah utilitas standar membaca file input yang dapat dicari dan berakhir tanpa kesalahan sebelum mencapai file-akhir, utilitas harus memastikan bahwa offset file dalam deskripsi file terbuka diposisikan dengan baik melewati byte terakhir yang diproses oleh utilitas. Untuk file yang tidak bisa dicari, keadaan file offset dalam deskripsi file terbuka untuk file itu tidak ditentukan.

headadalah salah satu utilitas standar , sehingga implementasi POSIX-conforming harus mengimplementasikan perilaku yang dijelaskan di atas.

GNU head memang mencoba untuk meninggalkan file deskriptor di posisi yang benar, tetapi tidak mungkin untuk mencari di pipa, jadi dalam pengujian Anda gagal untuk mengembalikan posisi. Anda dapat melihat ini menggunakan 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)
...

The readpengembalian 17 byte (semua input yang tersedia), headproses empat dari mereka dan kemudian mencoba untuk kembali 13 bytes, tetapi tidak bisa. (Anda juga dapat melihat di sini bahwa GNU headmenggunakan buffer 8 KiB.)

Ketika Anda memberi tahu headuntuk menghitung byte (yang bukan standar), ia tahu berapa byte yang dibaca, sehingga ia dapat (jika diimplementasikan dengan cara itu) membatasi pembacaannya. Inilah mengapa head -c 5tes Anda berfungsi: GNU headhanya membaca lima byte dan karenanya tidak perlu mencari untuk mengembalikan posisi deskriptor file.

Jika Anda menulis dokumen ke file, dan menggunakannya, Anda akan mendapatkan perilaku yang Anda cari:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc
Stephen Kitt
sumber
2
Satu dapat menggunakan utilitas line(sekarang dihapus dari POSIX / XPG tetapi masih tersedia di banyak sistem) atau read( IFS= read -r line) yang membaca satu byte pada suatu waktu untuk menghindari masalah.
Stéphane Chazelas
3
Perhatikan bahwa apakah head -c 5akan membaca 5 byte atau buffer penuh tergantung pada implementasinya (juga perhatikan bahwa head -citu bukan standar), Anda tidak dapat mengandalkan itu. Anda harus dd bs=1 count=5memiliki jaminan bahwa tidak lebih dari 5 byte akan dibaca.
Stéphane Chazelas
Terima kasih @ Stéphane, saya telah memperbarui -c 5deskripsinya.
Stephen Kitt
Perhatikan bahwa headbawaan ksh93membaca satu byte pada satu waktu dengan head -n 1ketika input tidak dapat dicari.
Stéphane Chazelas
1
@anton_rh, ddhanya berfungsi dengan benar dengan pipa bs=1jika Anda menggunakan yang countdibaca di pipa dapat mengembalikan kurang dari yang diminta (tapi setidaknya satu byte kecuali jika eof tercapai). GNU ddpunya iflag=fullblockyang bisa meringankan itu.
Stéphane Chazelas
6

dari POSIX

The kepala utilitas akan menyalin file input ke output standar, berakhir output untuk setiap file pada suatu titik yang ditunjuk.

Itu tidak mengatakan apa-apa tentang berapa banyak yang head harus dibaca dari input. Menuntutnya untuk membaca byte-by-byte akan konyol, karena akan sangat lambat dalam banyak kasus.

Ini, bagaimanapun, dibahas dalam readbuiltin / utility: semua cangkang yang dapat saya temukan readdari pipa satu byte pada suatu waktu dan teks standar dapat diartikan berarti bahwa ini harus dilakukan, untuk dapat membaca hanya satu baris:

The dibaca utilitas akan membaca garis logis tunggal dari input standar ke dalam satu atau lebih variabel shell.

Dalam kasus read, yang digunakan dalam skrip shell, kasus penggunaan umum akan menjadi seperti ini:

read someline
if something ; then 
    someprogram ...
fi

Di sini, input standar someprogramadalah sama dengan shell, tetapi dapat diharapkan bahwa someprogramakan membaca semua yang muncul setelah baris input pertama dikonsumsi oleh readdan bukan apa pun yang tersisa setelah buffered dibaca oleh read. Di sisi lain, menggunakan headseperti dalam contoh Anda jauh lebih jarang.


Jika Anda benar-benar ingin menghapus setiap baris lain, akan lebih baik (dan lebih cepat) untuk menggunakan beberapa alat yang dapat menangani seluruh input sekaligus, misalnya

$ 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'
ilkkachu
sumber
Tetapi lihat bagian “INPUT FILES” dari pengantar POSIX untuk volume 3 ...
Stephen Kitt
1
POSIX mengatakan: "Ketika utilitas standar membaca file input yang dapat dicari dan berakhir tanpa kesalahan sebelum mencapai akhir file, utilitas harus memastikan bahwa offset file dalam deskripsi file terbuka diposisikan dengan baik hanya melewati byte terakhir yang diproses oleh utilitas. Untuk file yang tidak dapat dicari, status file diimbangi dalam deskripsi file terbuka untuk file itu tidak ditentukan. "
AlexP
2
Perhatikan bahwa kecuali Anda menggunakan -r, readdapat membaca lebih dari satu baris (tanpa IFS=itu juga akan menghapus spasi dan tab terkemuka (dengan nilai default $IFS)).
Stéphane Chazelas
@AlexP, ya, Stephen baru saja menautkan bagian itu.
ilkkachu
Perhatikan bahwa headbawaan ksh93membaca satu byte pada satu waktu dengan head -n 1ketika input tidak dapat dicari.
Stéphane Chazelas
1
awk '{if (NR%2) == 1) print;}'
ijbalazs
sumber
Hellóka :-) dan selamat datang di situs! Catatan, kami lebih suka jawaban yang lebih rumit. Mereka harus bermanfaat bagi para googler di masa depan.
peterh