Stdout / stderr'ın serpiştirilmesini ne engeller?


14

Diyelim ki bazı işlemler yapıyorum:

#!/usr/bin/env bash

foo &
bar &
baz &

wait;

Yukarıdaki komut dosyası gibi çalıştırın:

foobarbaz | cat

anlayabildiğim kadarıyla, süreçlerden herhangi biri stdout / stderr'e yazdığında, çıktıları asla serpiştirilmez - her stdio satırı atomik gibi görünür. Bu nasıl çalışıyor? Hangi program her bir hattın atomik olduğunu kontrol eder?


3
Komutlarınız ne kadar veri veriyor? Birkaç kilobayt vermelerini sağlayın.
Kusalananda

Komutlardan birinin yeni satırdan önce birkaç kb çıktığı anlamına mı geliyorsunuz?
Alexander Mills

Hayır, şöyle bir şey: unix.stackexchange.com/a/452762/70524
muru

Yanıtlar:


23

Serpiştirirler! Sadece açık olmayan kısa çıkış patlamaları denediniz, ancak pratikte herhangi bir çıktının açık kalmamasını sağlamak zor.

Çıktı tamponlama

Programların çıktılarını nasıl arabelleğe aldıklarına bağlıdır . Stdio kütüphanesi onlar ediyoruz yazma kullandığı tamponlar çıktı daha verimli kılmak için zaman en programları kullanan. Program bir dosyaya yazmak için bir kütüphane işlevini çağırır çağırmaz veri çıktısı almak yerine, işlev bu verileri bir arabellekte saklar ve yalnızca arabellek dolduğunda veri çıktısı verir. Bu, çıktının gruplar halinde yapıldığı anlamına gelir. Daha doğrusu, üç çıkış modu vardır:

  • Tamponsuz: veriler tampon kullanılmadan hemen yazılır. Program çıktısını küçük parçalar halinde yazarsa, yavaş yavaş olabilir, örneğin karakter karakter. Bu standart hata için varsayılan moddur.
  • Tamamen arabelleğe alındı: veriler yalnızca tampon dolduğunda yazılır. Bir boruya veya normal bir dosyaya yazarken, stderr dışında bu varsayılan moddur.
  • Satır arabelleği: veriler her yeni satırdan sonra veya arabellek dolduğunda yazılır. Bu, stderr dışında bir terminale yazarken varsayılan moddur.

Programlar her dosyayı farklı davranacak şekilde yeniden programlayabilir ve arabelleği açıkça temizleyebilir. Bir program dosyayı kapattığında veya normal olarak çıktığında arabellek otomatik olarak temizlenir.

Aynı boruya yazılan tüm programlar satır arabelleğe alınmış mod kullanıyorsa veya arabelleksiz mod kullanıyorsa ve her satırı bir çıkış işlevine tek bir çağrı ile yazıyorsa ve satırlar tek bir yığın halinde yazacak kadar kısaysa, o zaman çıktı tüm satırların serpiştirilmesi olacaktır. Ancak programlardan biri tamamen arabelleğe alınmış mod kullanıyorsa veya çizgiler çok uzunsa, karışık çizgiler görürsünüz.

İşte iki programdan çıktıyı araya eklediğim bir örnek. Linux'ta GNU coreutils kullandım; bu yardımcı programların farklı sürümleri farklı davranabilir.

  • yes aaaaaaaasonsuza kadar satır ara belleğe alınmış modda eşdeğer bir şekilde yazar . Yardımcı yesprogram aslında bir seferde birden fazla satır yazar, ancak her çıktı verdiğinde, çıktı bir dizi satır olur.
  • echo bbbb; done | grep bbbbbtamamen arabellek modunda sonsuza kadar yazar . 8192 arabellek boyutu kullanır ve her satır 5 bayt uzunluğundadır. 5, 8192'yi bölmediğinden, yazılar arasındaki sınırlar genel olarak bir çizgi sınırında değildir.

Onları bir araya getirelim.

$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa

Gördüğünüz gibi, evet bazen grep'i kesintiye uğrattı ve tam tersi. Hatların sadece% 0.001'i kesildi, ancak oldu. Çıktı randomize edilir, böylece kesinti sayısı değişir, ancak her seferinde en az birkaç kesinti gördüm. Hatlar daha uzun olsaydı, kesikli hatların daha yüksek bir kısmı olurdu, çünkü tampon başına hat sayısı azaldıkça bir kesilme olasılığı artar.

Çıktı tamponlamasını ayarlamanın birkaç yolu vardır . Ana olanlar:

  • stdbuf -o0GNU coreutils ve FreeBSD gibi diğer bazı sistemlerde bulunan programla varsayılan ayarlarını değiştirmeden stdio kütüphanesini kullanan programlarda arabelleğe almayı kapatın . Alternatif olarak ile satır arabelleğe almayı seçebilirsiniz stdbuf -oL.
  • Program çıktısını sadece bu amaçla oluşturulan bir terminal üzerinden yönlendirerek satır arabelleğe alma işlemine geçin unbuffer. Bazı programlar başka şekillerde farklı davranabilir, örneğin grepçıktısı bir terminalse varsayılan olarak renkleri kullanır.
  • Programı yapılandırın, örneğin --line-bufferedGNU grep'e geçerek .

Yukarıdaki pasajı tekrar görelim, bu sefer her iki tarafta satır arabelleğe alma ile.

{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb

Yani bu sefer evet asla grep'i kesintiye uğratmadı, ama grep bazen evet'i kesintiye uğrattı. Neden sonra geleceğim.

Boru serpiştirme

Her program bir seferde bir satır çıkardığı ve satırlar yeterince kısa olduğu sürece, çıkış hatları düzgün bir şekilde ayrılacaktır. Ancak, bunun çalışması için çizgilerin ne kadar sürebileceğinin bir sınırı var. Borunun kendisinde bir transfer tamponu bulunur. Bir program bir boruya çıktı verdiğinde, veriler yazar programından borunun aktarım arabelleğine ve daha sonra borunun aktarım arabelleğinden okuyucu programına kopyalanır. (En azından kavramsal olarak - çekirdek bazen bunu tek bir kopyaya göre optimize edebilir.)

Borunun aktarım arabelleğine sığacakdan daha fazla kopyalanacak veri varsa, çekirdek her seferinde bir arabelleğe kopyalar. Aynı boruya birden çok program yazıyorsa ve çekirdeğin aldığı ilk program birden fazla arabellek yazmak istiyorsa, çekirdeğin aynı programı ikinci kez tekrar seçeceğine dair bir garanti yoktur. Örneğin, P arabellek boyutu ise, foo2 * P bayt baryazmak istiyor ve 3 bayt yazmak istiyorsa, o zaman bir araya ekleme olası P bayt foo, sonra 3 bayt barve P bayt'tır foo.

Yukarıdaki yes + grep örneğine geri dönersek, sistemimde, yes aaaabir seferde 8192 baytlık bir ara belleğe sığabilecek kadar çok satır yazıyor. Yazmak için 5 bayt (4 yazdırılabilir karakter ve yeni satır) olduğundan, her seferinde 8190 bayt yazar. Boru tamponu boyutu 4096 bayttır. Bu nedenle evetten 4096 bayt, daha sonra grep'ten bir miktar çıktı almak ve daha sonra evet'ten yazmanın geri kalanını almak mümkündür (8190 - 4096 = 4094 bayt). 4096 bayt, 819 satır aaaave yalnız bir alan bırakıyor a. Bu ayüzden bu yalnız ile bir çizgi ve ardından grep'ten bir yazma ile bir çizgi verir abbbb.

Olanların ayrıntılarını görmek getconf PIPE_BUF .istiyorsanız, sisteminizdeki boru arabellek boyutunu söyleyecek ve her program tarafından yapılan sistem çağrılarının tam bir listesini görebilirsiniz.

strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba

Temiz hat serpiştirme nasıl garanti edilir

Hat uzunlukları boru tampon boyutundan daha küçükse, hat tamponlaması çıktıda karışık bir çizgi olmayacağını garanti eder.

Çizgi uzunlukları daha büyük olabilirse, aynı boruya birden fazla program yazarken rastgele karıştırmayı önlemenin bir yolu yoktur. Ayrımı sağlamak için, her bir programı farklı bir boruya yazmanız ve satırları birleştirmek için bir program kullanmanız gerekir. Örneğin, GNU Parallel bunu varsayılan olarak yapar.


ilginç, bu nedenle, tüm satırların catatomik olarak yazıldığından emin olmak için iyi bir yol olabilir, böylece kedi işlemi ya foo / bar / baz'dan tüm satırları alır, ancak birinden bir buçuk satırdan bir satır almaz, vb. Bash betiği ile yapabileceğim bir şey var mı?
Alexander Mills

1
bu yüzlerce dosyamın olduğu ve awkaynı kimlik için iki (veya daha fazla) çıkış satırı üretildiğim durumum için de geçerli , find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }' ancak find -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'her kimlik için sadece bir satır üretti.
αғsнιη

Herhangi bir serpiştirmeyi önlemek için, Node.js gibi bir programlama env ile yapabilirim, ancak bash / shell ile nasıl yapılacağından emin değilim.
Alexander Mills

1
@JoL Bunun nedeni boru tamponunun dolmasıdır. Hikayenin ikinci bölümünü yazmak zorunda olduğumu biliyordum… Tamam.
Gilles 'SO- kötü olmayı bırak

1
@OlegzandrDenman TLDR ekledi: serpiştiriyorlar. Nedeni karmaşık.
Gilles 'SO- kötü olmayı kes'

1

http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P buna bir göz attı:

GNU xargs, birden fazla işi paralel olarak çalıştırmayı destekler. -P n, burada n paralel olarak yapılacak işlerin sayısıdır.

seq 100 | xargs -n1 -P10 echo "$a" | grep 5
seq 100 | xargs -n1 -P10 echo "$a" > myoutput.txt

Bu, birçok durum için iyi çalışır, ancak aldatıcı bir kusuru vardır: $ a ~ 1000 karakterden fazla içeriyorsa, eko atomik olmayabilir (çoklu yazma () çağrılarına bölünebilir) ve iki satırın oluşma riski vardır karışık olacak.

$ perl -e 'print "a"x2000, "\n"' > foo
$ strace -e write bash -c 'read -r foo < foo; echo "$foo"' >/dev/null
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 1008) = 1008
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 993) = 993
+++ exited with 0 +++

Açıkça aynı sorun, echo veya printf için birden fazla çağrı olduğunda ortaya çıkar:

slowprint() {
  printf 'Start-%s ' "$1"
  sleep "$1"
  printf '%s-End\n' "$1"
}
export -f slowprint
seq 10 | xargs -n1 -I {} -P4 bash -c "slowprint {}"
# Compare to no parallelization
seq 10 | xargs -n1 -I {} bash -c "slowprint {}"
# Be sure to see the warnings in the next Pitfall!

Her iş iki (veya daha fazla) ayrı yazma () çağrısından oluştuğundan, paralel işlerden çıktılar birlikte karıştırılır.

Karışık olmayan çıkışlara ihtiyacınız varsa, bu nedenle çıktının serileştirilmesini garanti eden bir araç kullanılması önerilir (GNU Paralel gibi).


Bu bölüm yanlış. xargs echoecho bash yerleşimini çağırmaz, echoyardımcı programdan $PATH. Her neyse, bu bash yankı davranışını bash 4.4 ile çoğaltamıyorum. Linux'ta, 4K'dan daha büyük bir boruya (/ dev / null değil) yazmanın atomik olduğu garanti edilmez.
Stéphane Chazelas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.