Sipariş edilen STDOUT / STDERR nasıl yakalanır ve zaman damgası / ön ekler nasıl eklenir?


25

Neredeyse incelemiş bulunuyoruz tüm mevcut benzer soruları boşuna.

Sorunu ayrıntılı olarak anlatayım:

Bazı katılımsız komut dosyaları çalıştırıyorum ve bunlar standart çıktı ve standart hata satırları üretebiliyorum, bunları bir terminal emülatörü tarafından görüntülendikleri şekilde tam sırada yakalamak ve daha sonra bunlara "STDERR:" ve "STDOUT:" gibi bir önek eklemek istiyorum.

Kullanıma hazır olmamak için boruları ve hatta epoll tabanlı yaklaşımı kullanmayı denedim. Ben bunda usta olmasam da çözümün pty kullanımında olduğunu düşünüyorum. Ayrıca Gnome’un VTE’nin kaynak kodunu da inceledim, ancak bu çok verimli olmadı.

İdeal olarak, bunu başarmak için Bash yerine Go'yu kullanırdım ama başaramadım. Tamponlama nedeniyle boruların doğru bir sıra sırasını korumayı otomatik olarak yasakladığı görülüyor.

Birisi benzer bir şey yapabildi mi? Yoksa bu imkansız mı? Bence bir terminal emülatörü bunu yapabilirse, o zaman değil - belki de PTY (leri) farklı şekilde ele alan küçük bir C programı oluşturarak mı?

İdeal olarak, bu 2 akışı okumak için zaman uyumsuz bir girdi kullanırdım (STDOUT ve STDERR) ve sonra ihtiyaçlarımı ikinci olarak yeniden yazdırırım, ancak girdi sırası çok önemli!

NOT: Stderred'in farkındayım ancak bu benim için Bash betikleriyle çalışmıyor ve bir önek eklemek için kolayca düzenlenemez (temelde bol miktarda sistem çağrısında bulunur).

Güncelleme: iki listenin altına eklendi

(Tutarlı bir sonuç elde etmek için verilen örnek betiğe ikinci alt rasgele gecikmeler eklenebilir)

Güncelleme: Bu sorunun çözümü de çözecek bu diğer soru @Gilles belirttiği gibi,. Ancak burada ve orada ne istendiğini yapmanın mümkün olmadığı sonucuna vardım . Her 2>&1iki akış kullanıldığında, pty / pipe düzeyinde doğru şekilde birleştirilir, ancak akışları ayrı ayrı ve doğru sırada kullanmak için, aslında, sistem çağrısı çekmeyi içeren ve birçok yönden kirli olarak görülebilen bir stderred yaklaşımı kullanılmalıdır .

Birisi yukarıdakileri engelleyebilirse bu soruyu güncellemek için can atıyorum.


1
İstediğin bu değil mi? stackoverflow.com/questions/21564/...
slm

@slm muhtemelen değil, çünkü OP'nin farklı dizelere farklı dizeler hazırlaması gerekiyor.
peterph

Siparişin neden bu kadar önemli olduğunu paylaşır mısınız? Belki de sorununun başka bir yolu olabilir ...
peterph

@peterph bu bir önkoşuldur, eğer tutarlı bir çıktı elde edemezsem, onu okumaktan / kafamdan karıştırmak yerine / dev / null'a göndermeyi tercih ederim :) 2> & 1 mesela siparişi koruyor, ancak türe izin vermiyor özelleştirme Bu soru sormak
Deim0s

Yanıtlar:


12

İşlemleri kullanabilirsiniz. Belirli bir komutun her iki çıkışını da etiketleme yapan iki sedörneğe (biri için stderrdiğeri için stdout) besleyen basit sarmalayıcı .

#!/bin/bash
exec 3>&1
coproc SEDo ( sed "s/^/STDOUT: /" >&3 )
exec 4>&2-
coproc SEDe ( sed "s/^/STDERR: /" >&4 )
eval $@ 2>&${SEDe[1]} 1>&${SEDo[1]}
eval exec "${SEDo[1]}>&-"
eval exec "${SEDe[1]}>&-"

Birkaç şeyi not edin:

  1. Birçok insan için (benim de dahil olmak üzere) bir sihirdir - bir nedenden ötürü (aşağıdaki bağlantılı cevaba bakınız).

  2. Ara sıra birkaç satır değişmeyeceğinin garantisi yok - bunların hepsi işlemlerin zamanlamasına bağlı. Aslında, zaman zaman bir noktada olacağı garanti edilir. Bu kesinlikle aynı düzeni korur, sen hem veri işlemek zorunda dedi stderrve stdinaksi çekirdek zamanlayıcı (ve) onun bir karmaşa yapabilirsiniz, aynı süreçte.

    Sorunu doğru anlarsam, kabuğa her iki akışı da bir işleme (AFAIK yapılabilir) yönlendirmek için talimat vermeniz gerektiği anlamına gelir. Sorun, o süreç ilk önce ne yapılması gerektiğine karar vermeye başladığında başlar - her iki veri kaynağını da sorgulamak zorunda kalır ve bir noktada bir akışı işleyeceği bir duruma girer ve veriler bitmeden önce her iki akışa da ulaşır. Ve işte tam olarak bozulduğu yer. Aynı zamanda, çıktı sistemlerini aynı şekilde sarmanın stderred, muhtemelen istediğiniz sonucu elde etmenin tek yolu olduğu anlamına gelir (ve o zaman bile bir şey çok işlemcili bir sistemde çok iş parçacıklı hale geldiğinde sorun yaşayabilirsiniz).

İşlemler, Stéphane'nin Bash'deki coproc komutunu nasıl kullandığınız konusundaki mükemmel cevabını mutlaka okuyun. derinlemesine içgörü için.


Cevabınız için teşekkürler @peterph, ancak özellikle siparişi korumak için yollar arıyorum. Not: Tercümanınızın kullandığınız işlem değiştirme nedeniyle bash olması gerektiğini düşünüyorum ( ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpectedbetiğinizi kopyalayıp / yapıştırarak
alırım

Büyük olasılıkla bu yüzden, ben de koştum bashile /bin/sh(ben orada vardı neden olmasın emin olun).
Peterph

Akış karışıklığının nerede olabileceği ile ilgili olarak soruyu biraz güncelledik.
peterph

1
eval $@oldukça buggy. "$@"Eğer argümanlarınızı tam bir komut satırı olarak çalıştırmak istiyorsanız kullanın - bir yorum katmanı eklenmesi, evaltahmin edilmesi zor (ve potansiyel olarak kötü niyetli, dosya isimlerini veya başka bir şekilde kontrol etmediğiniz diğer içerikleri geçiyorsanız). argümanlar) davranışlar ve hatta moreso'dan alıntı yapılamaması (isimleri boşluklarla birden fazla kelimeye ayırır, daha önce değişmeden alıntılanmış olsalar bile globları genişletir).
Charles Duffy

1
Ayrıca, yeterince modern olan coprocesses bash'ında, bir değişkende adlandırılmış dosya tanımlayıcılarını kapatmanız gerekmez eval . exec {SEDo[1]}>&-olduğu gibi çalışacak (evet, kasıtlı $olmadan önce bir eksikliği {).
Charles Duffy

5

Yöntem 1. Dosya tanımlayıcılarını ve awk'yi kullanma

Bu SO Q&A'nın çözümlerini kullanan böyle bir şeye ne dersiniz: Zaman çizelgelerini metin satırlarına hazırlamak için bir Unix yardımcı programı var mı? ve bu SO Q&A başlıklı: kabuk STDOUT ve STDERR kabuk komut dosyasında iki farklı işleme? .

Yaklaşım

Adım 1, Bash'de çağrıldığında zaman damgası mesajını gerçekleştirecek 2 fonksiyon yaratıyoruz:

$ msgOut () {  awk '{ print strftime("STDOUT: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
$ msgErr () {  awk '{ print strftime("STDERR: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }

2. Adımda, istediğiniz mesajları almak için yukarıdaki işlevleri kullanırsınız:

$ { { { ...command/script... } 2>&3; } 2>&3 | msgErr; } 3>&1 1>&2 | msgOut

Örnek

Burada STDOUT'a yazacak a, 10 saniye uyuyacak ve daha sonra STDERR'e çıktı yazacak bir örnek verdim . Bu komut dizisini yukarıdaki yapımıza koyduğumuzda, belirttiğiniz şekilde mesajlaşıyoruz.

$ { { echo a; sleep 10; echo >&2 b; } 2>&3 | \
    msgErr; } 3>&1 1>&2 | msgOut
STDERR: 2014-09-26 09:22:12 a
STDOUT: 2014-09-26 09:22:22 b

Yöntem 2. Not çıkışı kullanma

İstediğiniz şeyi yapacak paketin bir annotate-outputparçası olarak adlandırılan bir araç var devscripts. Tek kısıtlama sizin için komut dosyalarını çalıştırması gerektiğidir.

Örnek

Yukarıdaki örnek komut dizimizi şöyle bir betiğe koyarsak mycmds.bash:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Daha sonra böyle çalıştırabiliriz:

$ annotate-output ./mycmds.bash 
09:48:00 I: Started ./mycmds.bash
09:48:00 O: a
09:48:10 E: b
09:48:10 I: Finished with exitcode 0

Çıktının formatı zaman damgası kısmı için kontrol edilebilir ancak bunun ötesinde kontrol edilemez. Ama aradığın şeye benzer bir çıktı, bu yüzden tasarıya uygun olabilir.


1
Maalesef, bu aynı zamanda bazı çizgilerin yer değiştirmesi sorununu da çözmüyor.
Peterph

kesinlikle. Bence bu sorunun cevabı "mümkün değil". stderredSizinle olan olay , çizgilerin sınırlarını kolayca belirleyemez (denemek haksızlık olur). Birinin bana bu problemle ilgili yardım edebileceğini görmek istedim ama görünüşe göre herkes sorunun temelini oluşturan tek kısıtlamadan ( emir ) vazgeçmek istiyor
Deim0s

Yöntem 1'in 2. adımı, {doğru çalışması için ön tarafta bir başkasını gerektirir.
Austin Hanson
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.