Katakanlah saya menjalankan beberapa proses:
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
Saya menjalankan skrip di atas seperti ini:
foobarbaz | cat
sejauh yang saya tahu, ketika salah satu proses menulis ke stdout / stderr, outputnya tidak pernah interleave - setiap baris stdio tampaknya atom. Bagaimana cara kerjanya? Utilitas apa yang mengontrol bagaimana setiap baris adalah atom?
Jawaban:
Mereka melakukan interleave! Anda hanya mencoba semburan keluaran pendek, yang tetap tidak bertele-tele, tetapi dalam praktiknya sulit untuk memastikan bahwa keluaran tertentu tetap tidak berbintik.
Output buffering
Tergantung bagaimana program buffer output mereka. The library stdio bahwa sebagian besar program menggunakan ketika mereka sedang menulis menggunakan buffer untuk membuat output lebih efisien. Alih-alih mengeluarkan data segera setelah program memanggil fungsi pustaka untuk menulis ke file, fungsi menyimpan data ini dalam buffer, dan hanya benar-benar menampilkan data setelah buffer telah terisi. Ini berarti bahwa output dilakukan dalam batch. Lebih tepatnya, ada tiga mode keluaran:
Program dapat memprogram ulang setiap file untuk berperilaku berbeda, dan secara eksplisit dapat membersihkan buffer. Buffer memerah secara otomatis ketika suatu program menutup file atau keluar secara normal.
Jika semua program yang menulis ke pipa yang sama baik menggunakan mode buffer-line, atau menggunakan mode unbuffered dan menulis setiap baris dengan satu panggilan ke fungsi output, dan jika baris cukup pendek untuk menulis dalam satu chunk, maka output akan menjadi interleaving seluruh baris. Tetapi jika salah satu program menggunakan mode buffered penuh, atau jika garis terlalu panjang, maka Anda akan melihat garis campuran.
Berikut adalah contoh di mana saya interleave output dari dua program. Saya menggunakan GNU coreutils di Linux; versi berbeda dari utilitas ini mungkin berperilaku berbeda.
yes aaaa
menulisaaaa
selamanya dalam apa yang pada dasarnya setara dengan mode buffer-line. Theyes
utilitas benar-benar menulis beberapa baris pada satu waktu, tetapi setiap kali memancarkan output, output adalah seluruh nomor baris.echo bbbb; done | grep b
menulisbbbb
selamanya dalam mode buffer penuh. Ini menggunakan ukuran buffer 8192, dan setiap baris panjangnya 5 byte. Karena 5 tidak membagi 8192, batas-batas antara penulisan tidak pada batas garis pada umumnya.Mari kita lemparkan mereka bersama.
Seperti yang Anda lihat, ya kadang-kadang terputus grep dan sebaliknya. Hanya sekitar 0,001% dari jalur yang terputus, tetapi itu terjadi. Outputnya diacak sehingga jumlah interupsi akan bervariasi, tetapi saya melihat setidaknya beberapa interupsi setiap waktu. Akan ada fraksi yang lebih tinggi dari garis terputus jika garis lebih panjang, karena kemungkinan gangguan meningkat ketika jumlah garis per buffer berkurang.
Ada beberapa cara untuk menyesuaikan buffering output . Yang utama adalah:
stdbuf -o0
ditemukan di GNU coreutils dan beberapa sistem lain seperti FreeBSD. Anda juga dapat beralih ke buffer linestdbuf -oL
.unbuffer
. Beberapa program mungkin berperilaku berbeda dengan cara lain, misalnyagrep
menggunakan warna secara default jika outputnya adalah terminal.--line-buffered
ke GNU grep.Mari kita lihat cuplikan di atas lagi, kali ini dengan garis penyangga di kedua sisi.
Jadi kali ini ya tidak pernah terputus grep, tapi kadang-kadang grep terputus ya. Saya akan datang ke mengapa nanti.
Interleaving pipa
Selama setiap program menghasilkan satu baris pada satu waktu, dan garis-garisnya cukup pendek, garis-garis output akan dipisahkan dengan rapi. Tapi ada batas berapa lama garis ini bisa bekerja. Pipa itu sendiri memiliki buffer transfer. Ketika suatu program menghasilkan ke suatu pipa, data disalin dari program penulis ke buffer transfer pipa, dan kemudian dari buffer transfer pipa ke program pembaca. (Setidaknya secara konseptual - kernel terkadang dapat mengoptimalkan ini menjadi satu salinan.)
Jika ada lebih banyak data untuk disalin daripada pas di buffer transfer pipa, maka kernel menyalin satu bufferful pada suatu waktu. Jika banyak program menulis ke pipa yang sama, dan program pertama yang dipilih kernel ingin menulis lebih dari satu bufferful, maka tidak ada jaminan bahwa kernel akan memilih program yang sama lagi untuk kedua kalinya. Sebagai contoh, jika P adalah ukuran buffer,
foo
ingin menulis 2 * P byte danbar
ingin menulis 3 byte, maka satu kemungkinan interleaving adalah P byte darifoo
, kemudian 3 byte daribar
, dan P byte darifoo
.Kembali ke contoh yes + grep di atas, pada sistem saya,
yes aaaa
kebetulan menulis baris sebanyak yang dapat ditampung dalam buffer 8192 byte dalam sekali jalan. Karena ada 5 byte untuk ditulis (4 karakter yang dapat dicetak dan baris baru), itu artinya ia menulis 8190 byte setiap waktu. Ukuran buffer pipa adalah 4096 byte. Oleh karena itu dimungkinkan untuk mendapatkan 4.096 byte dari yes, kemudian beberapa output dari grep, dan kemudian sisa penulisan dari yes (8190 - 4096 = 4094 byte). 4096 byte menyisakan ruang untuk 819 baris denganaaaa
dan satu-satunyaa
. Oleh karena itu satu baris dengan satu-satunya inia
diikuti oleh satu tulis dari grep, memberikan satu baris denganabbbb
.Jika Anda ingin melihat detail dari apa yang terjadi, maka
getconf PIPE_BUF .
akan memberi tahu Anda ukuran buffer pipa pada sistem Anda, dan Anda dapat melihat daftar lengkap panggilan sistem yang dibuat oleh setiap program denganBagaimana menjamin interleaving garis bersih
Jika panjang garis lebih kecil dari ukuran penyangga pipa, maka penyangga garis menjamin bahwa tidak akan ada garis campuran dalam output.
Jika panjang garis bisa lebih besar, tidak ada cara untuk menghindari pencampuran sembarang ketika beberapa program menulis ke pipa yang sama. Untuk memastikan pemisahan, Anda perlu membuat setiap program menulis ke pipa yang berbeda, dan menggunakan program untuk menggabungkan garis. Sebagai contoh, GNU Parallel melakukan ini secara default.
sumber
cat
secara atom, sehingga proses kucing menerima seluruh baris dari foo / bar / baz tetapi tidak setengah garis dari satu dan setengah garis dari yang lain, dll. Apakah ada yang bisa saya lakukan dengan skrip bash?awk
diproduksi dua (atau lebih) jalur output untuk ID yang sama denganfind -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
tetapi denganfind -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
benar hanya menghasilkan satu baris untuk setiap ID.http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P telah melihat ini:
sumber
xargs echo
tidak memanggil echo bash builtin, tetapiecho
utilitas dari$PATH
. Lagi pula saya tidak bisa mereproduksi perilaku bash echo dengan bash 4.4. Di Linux, menulis ke sebuah pipa (bukan / dev / null) yang lebih besar dari 4K tidak dijamin atom.