stdout'un COPY dosyasını günlük dosyasını bash betiğinin içinden yeniden yönlendir


235

Stdout bir dosyaya yeniden yönlendirmeyi biliyorum :

exec > foo.log
echo test

bu 'test' i foo.log dosyasına koyacaktır.

Şimdi çıktıyı günlük dosyasına yönlendirmek ve stdout'ta tutmak istiyorum

yani komut dosyasının dışından önemsiz bir şekilde yapılabilir:

script | tee foo.log

ama bunu betiğin içinde bildirmek istiyorum

denedim

exec | tee foo.log

ama işe yaramadı.


3
Sorunuz yanlış yazılmış. Ne zaman çağırmak 'exec> foo.log', senaryonun stdout'u olan dosya foo.log. Ben size çıkış foo.log giderek beri, foo.log ve tty için gitmek istediğiniz anlamına düşünüyorum edilir Stdout'a gidiyor.
William Pursell

ne yapmak istiyorum kullanmak | "exec" de. Bu benim için mükemmel olurdu, yani "exec | tee foo.log", ne yazık ki exec çağrısında boru yönlendirme kullanamazsınız
Vitaly Kushner

Yanıtlar:


297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Bunun basholmadığını unutmayın sh. Komut dosyasını ile çağırırsanız sh myscript.sh, satırları boyunca bir hata alırsınız syntax error near unexpected token '>'.

Sinyal tuzaklarıyla çalışıyorsanız, tee -ibir sinyal oluştuğunda çıkışın bozulmasını önlemek için bu seçeneği kullanmak isteyebilirsiniz . (Yorum için JamesThomasMoon1979'a teşekkürler.)


Bir boruya mı yoksa terminale mi yazdıklarına bağlı olarak çıkışlarını değiştiren araçlar ( lsörneğin, renkleri ve sütunlu çıktıyı kullanarak) yukarıdaki yapıyı bir boruya çıktıkları anlamına gelir.

Renklendirme / sütunlaştırmayı zorunlu kılma seçenekleri vardır (örn. ls -C --color=always). Bunun, renk kodlarının günlük dosyasına da yazılmasına ve daha az okunabilir olmasına neden olacağını unutmayın .


5
Çoğu sistemdeki tee arabelleğe alınır, bu nedenle komut dosyası bitene kadar çıktı gelmeyebilir. Ayrıca, bu tee alt süreçte değil, alt kabukta çalıştığından, bekleme, çağıran süreçle çıktıyı senkronize etmek için kullanılamaz. Ne istiyorsun bogomips.org/rainbows.git/commit/…

14
@Barry: POSIXtee , çıktısını arabelleğe almaması gerektiğini belirtir . Çoğu sistemde tampon yaparsa, çoğu sistemde bozulur. Bu teebenim çözümümle değil uygulamalarla ilgili bir problem .
DevSolar

3
@Sebastian: execÇok güçlü ama aynı zamanda çok ilgili. Geçerli stdout'u farklı bir dosya tanımlayıcıya "yedekleyebilir", ardından daha sonra kurtarabilirsiniz. Google "bash exec öğretici", orada birçok gelişmiş şeyler var.
DevSolar

2
@AdamSpiers: Barry'nin ne hakkında olduğundan da emin değilim. Bash's yeni süreçler başlatmayacak execşekilde belgelenmiştir , >(tee ...)boru / süreç ikamesi adı verilen bir standarttır ve &yönlendirmede elbette arka planla bir ilgisi yoktur ...? :-)
DevSolar

11
Ben geçen önermek -iiçin tee. Aksi takdirde, sinyal kesmeleri (tuzaklar) ana komut dosyasındaki stdout'u bozacaktır. Örneğin, a trap 'echo foo' EXITtuşuna ve ardından tuşuna basarsanız ctrl+c, " foo " ifadesini görmezsiniz . Ben de cevabını değiştirirdim exec &> >(tee -ia file).
JamesThomasMoon1979

173

Kabul edilen cevap STDERR'ı ayrı bir dosya tanımlayıcı olarak korumaz. Bunun anlamı

./script.sh >/dev/null

çıkışı olmaz barterminaline, yalnızca kayıt dosyasına için ve

./script.sh 2>/dev/null

çıktısı hem foove barterminaline. Açıkçası bu normal bir kullanıcının bekleyeceği davranış değildir. Bu, her ikisi de aynı günlük dosyasına eklenen iki ayrı tee işlemi kullanılarak düzeltilebilir:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(Yukarıdakilerin günlük dosyasını kısaltmayacağını unutmayın - bu davranışı istiyorsanız eklemeniz gerekir

>foo.log

komut dosyasının en üstüne yerleştirin.)

Arasında POSIX.1-2008 özellikleritee(1) bu çıkış tamponsuz olduğu gerektirir, yani bile satır tamponlu, nedenle STDOUT ve STDERR aynı satırda yer alabilir mümkündür, bu durumda foo.log, Günlük dosyası ne sadık bir yansıması olacak böylece Ancak bu da, terminalde olabilirdi olabilirdi bunun tam ayna değilse terminal görülebilir. STDOUT satırlarının STDERR satırlarından temiz bir şekilde ayrılmasını istiyorsanız, daha sonra kronolojik yeniden birleştirmeye izin vermek için muhtemelen her satırda tarih damgası önekleri olan iki günlük dosyası kullanmayı düşünün.


Bazı nedenlerden dolayı, benim durumumda, komut dosyası bir c-program system () çağrısından yürütüldüğünde, ana komut dosyası çıktıktan sonra bile iki tee alt işlemi var olmaya devam eder. Bu yüzden böyle tuzaklar eklemek zorunda kaldım:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko

15
Ben geçen önermek -iiçin tee. Aksi takdirde, sinyal kesmeleri (tuzaklar) koddaki stdout'u bozacaktır. Örneğin, siz trap 'echo foo' EXITve sonra tuşuna basarsanız ctrl+c, " foo " ifadesini görmezsiniz . Ben de cevabını değiştirirdim exec > >(tee -ia foo.log).
JamesThomasMoon1979

Buna dayanarak bazı "kaynaklanabilir" senaryolar oluşturdum. Böyle bir senaryoda bunları kullanabilir . logveya . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Sam Watkins

1
Bu yöntemin sorunu, iletilerin STDOUTönce toplu olarak ve sonra iletilerin STDERRgörünmesidir. Genellikle beklendiği gibi serpiştirilmezler.
CMCDragonkai

28

Meşgul kutusu, macOS bash ve bash olmayan kabuklar için çözüm

Kabul edilen cevap kesinlikle bash için en iyi seçimdir. Ben bash erişimi olmayan bir Busybox ortamında çalışıyorum ve exec > >(tee log.txt)sözdizimini anlamıyor . Ayrıca exec >$PIPE, başarısız ve asılı asılı adlandırılmış boru ile aynı ada sahip sıradan bir dosya oluşturmaya çalışarak düzgün yapmaz .

Umarım bu bash olmayan bir başkası için yararlı olacaktır.

Ayrıca, adlandırılmış bir boru kullanan herkes için güvenlidir rm $PIPE, çünkü bu borunun VFS'den bağlantısını keser, ancak onu kullanan işlemler bitinceye kadar hala bir referans sayımı sağlar.

$ * Kullanımının mutlaka güvenli olmadığını unutmayın.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here

Bu şimdiye kadar gördüğüm tek çözüm mac üzerinde çalışıyor
Mike Baglio Jr.

19

Komut dosyanızın içine tüm komutları parantez içine yerleştirin, örneğin:

(
echo start
ls -l
echo end
) | tee foo.log

5
bilgiçlikle, ayrıca kullanabilirsiniz parantez ( {})
glenn jackman

evet, bunu düşündüm, ama bu şu anki kabuk stdoutunun yeniden yönlendirilmesi değil, onun bir hile, aslında bir alt kabuk çalıştırıyorsunuz ve üzerinde düzenli bir piper yönlendirmesi yapıyorsunuz. düşünceler çalışır. Bu ve "tail -f foo.log &" çözümü ile ayrıldım. daha iyi bir yüzey olup olmadığını görmek için biraz bekler. Muhtemelen yerleşmezse;)
Vitaly Kushner

8
{} geçerli kabuk ortamında bir liste yürütür. (), bir alt kabuk ortamında bir liste yürütür.

Lanet olsun. Teşekkür ederim. Kabul edilen cevap benim için işe yaramadı, bir Windows sisteminde MingW altında çalışacak bir senaryo planlamaya çalışıyordu. İnanıyorum ki, uygulanmayan süreç ikamesinden şikayetçi oldu. Bu cevap, aşağıdaki değişiklikten sonra hem stderr hem de stdout'u yakalamak için gayet iyi çalıştı: `` -) | tee foo.log +) 2> & 1 | tee foo.log
Jon Carter

14

Syslog'a bir bash script günlüğü yapmanın kolay yolu. Komut dosyası çıktısı hem /var/log/syslogstderr aracılığıyla hem de aracılığıyla kullanılabilir . syslog, zaman damgaları dahil olmak üzere yararlı meta veriler ekler.

Bu satırı en üste ekle:

exec &> >(logger -t myscript -s)

Alternatif olarak, günlüğü ayrı bir dosyaya gönderin:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Bu gerektirir moreutils ( tszaman damgaları ekleyen komut için ).


10

Kabul edilen yanıtı kullanarak komut dosyam, komut dosyamın geri kalanını arka planda çalıştırarak son derece erken ('exec>> (tee ...)') dönmeye devam etti. Bu çözümü benim yolumdan alamadığım için başka bir çözüm buldum / soruna geçici bir çözüm buldum:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Bu, komut dosyasından çıktıyı işlemden, borudan her şeyi diske ve komut dosyasının orijinal stdout'una kaydeden 'tee'nin alt arka plan işlemine dönüştürür.

'Exec &>' ifadesinin hem stdout hem de stderr'i yeniden yönlendirdiğini, istersek bunları ayrı olarak yeniden yönlendirebileceğimizi veya yalnızca stdout istiyorsak 'exec>' olarak değiştirebileceğimizi unutmayın.

Senaryo başlangıcında borunun dosya sisteminden çıkarılsanız bile işlemler bitene kadar çalışmaya devam eder. Rm satırından sonra dosya adını kullanarak referans veremeyiz.


David Z'nin ikinci fikriyle benzer cevap . Yorumlarına bir göz atın. +1 ;-)
olibre

İyi çalışıyor. Anlamıyorum $logfilekısmını tee < ${logfile}.pipe $logfile &. Özellikle, set -xsadece genişletilmiş komut günlüğü satırlarını (from ) dosyaya yalnızca stdout '' değiştirmeden satırları değiştirerek gösterilen (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &ancak ilgili bir hata iletisi almak için değiştirmeye çalıştım $logfile. teeÇizgiyi biraz daha ayrıntılı olarak açıklayabilir misiniz ?
Chris Johnson

Bunu test ettim ve bu cevap STDERR'yi korumuyor gibi görünüyor (STDOUT ile birleştirildi), bu nedenle akışların hata tespiti veya diğer yeniden yönlendirme için ayrı olduğuna güveniyorsanız, Adam'ın cevabına bakmalısınız.
HeroCC


1

Exec tabanlı çözümlerden herhangi biriyle rahat olduğumu söyleyemem. Doğrudan tee kullanmayı tercih ederim, bu yüzden komut dosyası istendiğinde tee ile kendini çağırır:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

Bu, bunu yapmanızı sağlar:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

Bunu özelleştirebilirsiniz, örneğin tee = false değerini varsayılan yapın, TEE'nin günlük dosyasını tutmasını sağlayın, vb.


-1

Bunların ikisi de mükemmel bir çözüm değil, ama deneyebileceğiniz birkaç şey var:

exec >foo.log
tail -f foo.log &
# rest of your script

veya

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

İkincisi, betiğinizle ilgili bir sorun olduğunda sorun yaratabilecek bir boru dosyası bırakır (yani belki rmdaha sonra ana kabuğa koyabilirsiniz ).


1
tail, 2. komut dosyasında çalışan bir işlemi geride bırakacak tee veya bloke ile çalıştırmanız gerekecek ve bu durumda ilk birinde olduğu gibi işlemden çıkacaktır.
Vitaly Kushner

@Vitaly: Hata! Arka planı unuttum tee- ben düzenledim. Dediğim gibi, ikisi de mükemmel bir çözüm değil, ancak ana kabukları sona erdiğinde arka plan süreçleri öldürülecek, bu yüzden onları sonsuza kadar kaynaklarla ilgili endişelenmenize gerek yok.
David Z

1
Yikes: bunlar çekici görünüyor, ama kuyruk -f çıktısı da foo.log olacak. Exec önce kuyruk -f çalıştırarak düzeltebilirsiniz, ancak üst sona erdikten sonra kuyruk hala çalışıyor. Muhtemelen bir tuzak 0'da açıkça öldürmeniz gerekir.
William Pursell

Yeap. Komut dosyası arka plandaysa, işlemleri baştan sona bırakır.
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.