Kenapa ada kondisi balapan
Kedua sisi pipa dieksekusi secara paralel, bukan satu demi satu. Ada cara yang sangat sederhana untuk menunjukkan ini: lari
time sleep 1 | sleep 1
Ini membutuhkan satu detik, bukan dua.
Shell memulai dua proses anak dan menunggu keduanya selesai. Kedua proses ini dijalankan secara paralel: satu-satunya alasan mengapa salah satu dari mereka akan melakukan sinkronisasi dengan yang lain adalah ketika harus menunggu yang lain. Titik sinkronisasi yang paling umum adalah ketika sisi kanan blok menunggu data untuk dibaca pada input standarnya, dan menjadi tidak terblokir ketika sisi kiri menulis lebih banyak data. Kebalikannya juga bisa terjadi, ketika sisi kanan lambat untuk membaca data dan sisi kiri blok dalam operasi penulisan sampai sisi kanan membaca lebih banyak data (ada penyangga di dalam pipa itu sendiri, dikelola oleh kernel, tetapi memiliki ukuran maksimum kecil).
Untuk mengamati titik sinkronisasi, perhatikan perintah berikut ( sh -x
mencetak setiap perintah saat dijalankan):
time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
Bermainlah dengan variasi sampai Anda merasa nyaman dengan apa yang Anda amati.
Diberi perintah majemuk
cat tmp | head -1 > tmp
proses di sebelah kiri melakukan hal berikut (Saya hanya mencantumkan langkah-langkah yang relevan dengan penjelasan saya):
- Jalankan program eksternal
cat
dengan argumen tmp
.
- Terbuka
tmp
untuk membaca.
- Meskipun belum mencapai akhir file, baca sepotong dari file dan tulis ke output standar.
Proses di sebelah kanan melakukan hal berikut:
- Mengarahkan output standar ke
tmp
, memotong file dalam proses.
- Jalankan program eksternal
head
dengan argumen -1
.
- Baca satu baris dari input standar dan tulis ke output standar.
Satu-satunya titik sinkronisasi adalah bahwa kanan-3 menunggu kiri-3 untuk memproses satu baris penuh. Tidak ada sinkronisasi antara kiri-2 dan kanan-1, sehingga bisa terjadi dalam urutan apa pun. Urutan apa yang terjadi tidak dapat diprediksi: tergantung pada arsitektur CPU, pada shell, pada kernel, di mana core dari proses yang dijadwalkan, pada apa yang mengganggu CPU terima sekitar waktu itu, dll.
Cara mengubah perilaku
Anda tidak dapat mengubah perilaku dengan mengubah pengaturan sistem. Komputer melakukan apa yang Anda perintahkan. Anda menyuruhnya memotong tmp
dan membaca tmp
secara paralel, sehingga ia melakukan dua hal secara paralel.
Ok, ada satu "pengaturan sistem" yang bisa Anda ubah: Anda bisa menggantinya /bin/bash
dengan program lain yang bukan bash. Saya harap ini akan berjalan tanpa mengatakan bahwa ini bukan ide yang baik.
Jika Anda ingin pemotongan terjadi sebelum sisi kiri pipa, Anda harus meletakkannya di luar pipa, misalnya:
{ cat tmp | head -1; } >tmp
atau
( exec >tmp; cat tmp | head -1 )
Saya tidak tahu mengapa Anda menginginkan ini. Apa gunanya membaca dari file yang Anda tahu kosong?
Sebaliknya, jika Anda ingin pengalihan output (termasuk pemotongan) terjadi setelah cat
selesai membaca, maka Anda perlu buffer penuh data dalam memori, misalnya
line=$(cat tmp | head -1)
printf %s "$line" >tmp
atau menulis ke file yang berbeda dan kemudian pindahkan ke tempatnya. Ini biasanya merupakan cara yang kuat untuk melakukan hal-hal dalam skrip, dan memiliki keuntungan bahwa file tersebut ditulis secara penuh sebelum terlihat melalui nama aslinya.
cat tmp | head -1 >new && mv new tmp
The MoreUtils koleksi mencakup program yang tidak hanya itu, disebut sponge
.
cat tmp | head -1 | sponge tmp
Cara mendeteksi masalah secara otomatis
Jika tujuan Anda adalah untuk mengambil skrip yang ditulis dengan buruk dan secara otomatis mencari tahu di mana mereka melanggar, maka maaf, hidup tidak sesederhana itu. Analisis Runtime tidak akan andal menemukan masalah karena terkadang cat
selesai membaca sebelum pemotongan terjadi. Analisis statis pada prinsipnya dapat melakukannya; contoh disederhanakan dalam pertanyaan Anda ditangkap oleh Shellcheck , tetapi mungkin tidak menangkap masalah serupa di skrip yang lebih kompleks.