Perintah bash berikut masuk ke loop infinte:
$ echo hi > x
$ cat x >> x
Saya bisa menebak bahwa cat
terus membaca dari x
setelah mulai menulis ke stdout. Yang membingungkan, bagaimanapun, implementasi tes saya sendiri terhadap kucing menunjukkan perilaku yang berbeda:
// mycat.c
#include <stdio.h>
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "rb");
char buf[4096];
int num_read;
while ((num_read = fread(buf, 1, 4096, f))) {
fwrite(buf, 1, num_read, stdout);
fflush(stdout);
}
return 0;
}
Jika saya menjalankan:
$ make mycat
$ echo hi > x
$ ./mycat x >> x
Itu benar tidak berulang. Mengingat perilaku cat
dan fakta yang ingin saya sampaikan stdout
sebelumnya fread
dipanggil lagi, saya berharap kode C ini terus membaca dan menulis dalam satu siklus.
Bagaimana kedua perilaku ini konsisten? Mekanisme apa yang menjelaskan mengapacat
loop sementara kode di atas tidak?
shell
files
io-redirection
cat
Tyler
sumber
sumber
cat x >> x
menyebabkan kesalahan; Namun, perintah ini disarankan dalam buku Kernighan dan Unix Pike sebagai latihan.cat
kemungkinan besar menggunakan panggilan sistem bukan stdio. Dengan stdio, program Anda mungkin caching EOFness. Jika Anda memulai dengan file yang lebih besar dari 4096 byte, apakah Anda mendapatkan loop tak terbatas?Jawaban:
Pada sistem RHEL tua saya punya,
/bin/cat
tidak tidak loop untukcat x >> x
.cat
memberikan pesan kesalahan "cat: x: file input adalah file output". Aku bisa menipu/bin/cat
dengan melakukan hal ini:cat < x >> x
. Ketika saya mencoba kode Anda di atas, saya mendapatkan "perulangan" yang Anda jelaskan. Saya juga menulis system call berbasis "kucing":Loop ini juga. Satu-satunya buffering di sini (tidak seperti untuk "mycat" berbasis stdio) adalah apa yang terjadi di kernel.
Saya pikir apa yang terjadi adalah file deskriptor 3 (hasil
open(av[1])
) memiliki offset ke file 0. Deskrip deskriptor 1 (stdout) memiliki offset 3, karena ">>" menyebabkan shell yang dipanggil untuk melakukanlseek()
pada deskriptor file sebelum menyerahkannya kecat
proses anak.Melakukan
read()
apa pun, apakah menjadi buffer stdio, atau poloschar buf[]
memajukan posisi deskriptor file 3. Melakukanwrite()
memajukan posisi deskriptor file 1. Kedua offset tersebut adalah angka yang berbeda. Karena ">>", deskriptor file 1 selalu memiliki offset lebih besar dari atau sama dengan offset deskriptor file 3. Jadi setiap program "seperti kucing" akan diulang, kecuali program tersebut melakukan buffering internal. Mungkin saja, bahkan mungkin, bahwa implementasi stdio dariFILE *
(yang merupakan jenis simbolstdout
danf
dalam kode Anda) yang menyertakan buffernya sendiri.fread()
sebenarnya dapat melakukan panggilan sistemread()
untuk mengisi buffer internal untuk mungkin atau mungkin tidak mengubah apa pun di dalamnyaf
. Ini mungkin atau mungkin tidak mengubah apa pun di bagian dalamstdout
. memanggilfwrite()
distdout
f
. Jadi "kucing" berbasis stdio mungkin tidak berulang. Atau mungkin. Sulit dikatakan tanpa membaca banyak kode libc yang jelek dan jelek.Saya melakukan
strace
pada RHELcat
- itu hanya melakukan suksesiread()
danwrite()
panggilan sistem. Tetapicat
tidak harus bekerja dengan cara ini. Itu mungkin untukmmap()
file input, kemudian lakukanwrite(1, mapped_address, input_file_size)
. Kernel akan melakukan semua pekerjaan. Atau Anda bisa melakukansendfile()
panggilan sistem antara deskriptor file input dan output pada sistem Linux. Sistem SunOS 4.x lama dikabarkan melakukan trik pemetaan memori, tapi saya tidak tahu apakah ada yang pernah menggunakan kucing berbasis sendfile. Dalam kedua kasus, "perulangan" tidak akan terjadi, karena keduanyawrite()
dansendfile()
memerlukan parameter panjang-untuk-transfer.sumber
fread
panggilan itu menembolok bendera EOF seperti yang disarankan Mark Plotnick. Bukti: [1] Kucing Darwin menggunakan membaca, bukan ketakutan; dan [2] Panggilan menakutkan Darwin __srefill yang diaturfp->_flags |= __SEOF;
dalam beberapa kasus. [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
adalahcat -u
- u untuk unbuffered .>>
harus diimplementasikan dengan memanggil open () denganO_APPEND
flag, yang menyebabkan setiap operasi penulisan (secara atomis) menulis ke akhir file saat ini, tidak peduli apa posisi deskriptor file sebelum membaca. Perilaku ini diperlukan untukfoo >> logfile & bar >> logfile
bekerja dengan benar, misalnya - Anda tidak dapat mengasumsikan bahwa posisi setelah akhir tulisan terakhir Anda sendiri masih merupakan akhir dari file.Implementasi kucing modern (sunos-4.0 1988) menggunakan mmap () untuk memetakan seluruh file dan kemudian memanggil 1x tulis () untuk ruang ini. Implementasi seperti itu tidak akan berulang selama memori virtual memungkinkan untuk memetakan seluruh file.
Untuk implementasi lain tergantung pada apakah file lebih besar dari buffer I / O.
sumber
cat
implementasi tidak buffer output mereka (-u
tersirat). Itu akan selalu berulang.Seperti yang ditulis dalam perangkap Bash , Anda tidak dapat membaca dari file dan menulisnya di pipa yang sama.
Solusinya adalah dengan menggunakan editor teks, atau variabel sementara.
sumber
Anda memiliki semacam kondisi balapan di antara keduanya
x
. Beberapa implementasicat
(mis. Coreutils 8.23) melarang bahwa:Jika ini tidak terdeteksi, perilaku jelas akan tergantung pada implementasinya (ukuran buffer, dll.).
Dalam kode Anda, Anda bisa mencoba menambahkan
clearerr(f);
setelahfflush
, jika berikutnyafread
akan mengembalikan kesalahan jika indikator akhir file diatur.sumber
i = i++;
perilaku C yang tidak terdefinisi, karenanya perbedaan.cat
.