GNU/Linux >> Belajar Linux >  >> Linux

Mengapa stdout perlu pembilasan eksplisit saat dialihkan ke file?

Anda salah menggabungkan fungsi IO buffered dan unbuffered. Kombinasi seperti itu harus dilakukan dengan sangat hati-hati terutama ketika kode harus portabel. (dan menulis kode yang tidak dapat dibawa-bawa adalah hal yang buruk...)
Yang terbaik adalah menghindari penggabungan IO buffered dan unbuffered pada deskriptor file yang sama.

IO yang disangga: fprintf() , fopen() , fclose() , freopen() ...

IO tanpa buffer: write() , open() , close() , dup() ...

Saat Anda menggunakan dup2() untuk mengarahkan ulang stdout. Fungsi tidak mengetahui buffer yang diisi oleh fprintf() . Jadi ketika dup2() menutup deskriptor lama 1 tidak menyiram buffer dan konten dapat dibuang ke keluaran yang berbeda. Dalam kasus Anda 2a, itu dikirim ke /dev/null .

Solusinya

Dalam kasus Anda, yang terbaik adalah menggunakan freopen() bukannya dup2() . Ini menyelesaikan semua masalah Anda:

  1. Ini menghapus buffer dari FILE asli sungai kecil. (kasus 2a)
  2. Menyetel mode buffering sesuai dengan file yang baru dibuka. (kasus 3)

Inilah implementasi yang benar dari fungsi Anda:

void RedirectStdout2File(const char* log_path) {
    if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}

Sayangnya dengan buffer IO Anda tidak dapat langsung mengatur izin dari file yang baru dibuat. Anda harus menggunakan panggilan lain untuk mengubah izin atau Anda dapat menggunakan ekstensi glibc yang tidak dapat dipindahkan. Lihat fopen() man page .


Pembilasan untuk stdout ditentukan oleh perilaku buffering-nya. Buffering dapat diatur ke tiga mode:_IOFBF (penyangga penuh:menunggu hingga fflush() jika memungkinkan), _IOLBF (penyangga baris:baris baru memicu flush otomatis), dan _IONBF (tulis langsung selalu digunakan). "Dukungan untuk karakteristik ini ditentukan oleh implementasi, dan dapat dipengaruhi melalui setbuf() dan setvbuf() fungsi." [C99:7.19.3.3]

"Pada permulaan program, tiga aliran teks telah ditentukan sebelumnya dan tidak perlu dibuka secara eksplisit—input standar (untuk membaca input konvensional), output standar (untuk menulis output konvensional), dan kesalahan standar (untuk menulis output diagnostik). Seperti yang awalnya dibuka, kesalahan standar stream tidak sepenuhnya disangga; input standar dan aliran output standar sepenuhnya disangga jika dan hanya jika aliran dapat ditentukan untuk tidak merujuk ke perangkat interaktif." [C99:7.19.3.7]

Penjelasan perilaku yang diamati

Jadi, yang terjadi adalah implementasi melakukan sesuatu yang khusus platform untuk memutuskan apakah stdout akan menjadi garis-buffer. Di sebagian besar implementasi libc, pengujian ini dilakukan saat aliran pertama kali digunakan.

  1. Perilaku #1 mudah dijelaskan:saat streaming adalah untuk perangkat interaktif, ini adalah buffer baris, dan printf() memerah secara otomatis.
  2. Kasus #2 sekarang juga diharapkan:saat kita mengalihkan ke file, aliran sepenuhnya disangga dan tidak akan dibilas kecuali dengan fflush() , kecuali jika Anda menulis gobloads data ke dalamnya.
  3. Akhirnya, kami memahami kasus #3 juga untuk implementasi yang hanya melakukan pemeriksaan pada fd yang mendasarinya satu kali. Karena kami memaksa buffer stdout untuk diinisialisasi di printf() pertama , stdout memperoleh mode buffer garis. Saat kami menukar fd untuk membuka file, itu masih buffer baris, jadi datanya dihapus secara otomatis.

Beberapa implementasi aktual

Setiap libc memiliki kebebasan dalam menginterpretasikan persyaratan ini, karena C99 tidak menentukan apa itu "perangkat interaktif", entri stdio POSIX juga tidak memperluas ini (lebih dari sekadar mengharuskan stderr terbuka untuk membaca).

  1. Glibc. Lihat filedoalloc.c:L111. Di sini kita menggunakan stat() untuk menguji apakah fd adalah tty, dan atur mode buffering yang sesuai. (Ini dipanggil dari fileops.c.) stdout awalnya memiliki buffer null, dan dialokasikan pada penggunaan pertama aliran berdasarkan karakteristik fd 1.

  2. Lib BSD. Sangat mirip, tetapi kode yang jauh lebih bersih untuk diikuti! Lihat baris ini di makebuf.c


Linux
  1. Mengapa Nilai Inode Berubah Saat Kami Mengedit Di Editor “vi”?

  2. Mengapa Opsi Ssh -t Menambahkan Cr &Lf Dalam Output yang Dialihkan?

  3. Mengapa `zip` Dalam A For Loop Bekerja Saat File Ada, Tapi Tidak Saat Tidak?

  1. Mengapa pipa shell ini keluar?

  2. Mengapa forking proses saya menyebabkan file dibaca tanpa batas

  3. Mengapa saya memerlukan bit 'eksekusi' dalam mode file pada sistem file Unix?

  1. Mengapa membuat gambar memberi saya file, bukan gambar?

  2. Mengapa dentang menghasilkan teks yang tidak dapat dipahami saat dialihkan?

  3. Ke mana perginya metadata saat Anda menyimpan file?