Tüm kabuk komut dosyasının çıktısını komut dosyasının içinde nasıl yeniden yönlendirebilirim?


205

Bourne kabuk betiğinin tüm çıktısını bir yere yönlendirmek mümkün, ancak betiğin içindeki kabuk komutları ile?

Tek bir komutun çıktısını yeniden yönlendirmek kolaydır, ancak bunun gibi bir şey daha istiyorum:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Anlamı: komut dosyası etkileşimli olmayan bir şekilde çalıştırılırsa (örneğin, cron), her şeyin çıktısını bir dosyaya kaydedin. Kabuktan etkileşimli olarak çalıştırılırsa, çıkışın her zamanki gibi stdout'a gitmesine izin verin.

Bunu normalde FreeBSD periyodik yardımcı programı tarafından çalıştırılan bir komut dosyası için yapmak istiyorum. Günlük çalışmanın bir parçası, normalde her gün e-postayla görmeyi ummuyorum, bu yüzden gönderilmesini istemiyorum. Ancak, bu belirli bir komut dosyasının içindeki bir şey başarısız olursa, bu benim için önemli ve günlük işlerin bu bir bölümünün çıktısını yakalayıp e-postayla göndermek istiyorum.

Güncelleme: Joshua'nın cevabı spot-on, ama ben de stdout ve stderr'ı şu şekilde yapılan tüm komut dosyası etrafında kaydetmek ve geri yüklemek istedim:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4

2
$ TERM için test yapmak etkileşimli modu test etmenin en iyi yolu değildir. Bunun yerine, stdin'in bir tty olup olmadığını test edin (test -t 0).
Chris Jester-Young

2
Başka bir deyişle: if [! -t 0]; ardından exec> bir dosya 2> & 1; fi
Chris Jester-Young

1
Tüm iyilikler için buraya bakın: http://tldp.org/LDP/abs/html/io-redirection.html Temelde Joshua tarafından söylenenler. exec> dosya stdout'u belirli bir dosyaya yönlendirir, exec <dosya stdin'i dosyaya göre değiştirir, vb.
Loki

Güncelleme bölümünde, FD 3 ve 4'ü de şu şekilde exec 1>&3 2>&4 3>&- 4>&-
kapatmalısınız

Yanıtlar:


173

Sorunun güncellenmiş olarak ele alınması.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

'{...}' parantezleri bir birim G / Ç yönlendirmesi sağlar. Parantezler bir komutun görünebileceği yerlerde görünmelidir - basit bir şekilde, bir satırın başında veya bir noktalı virgülden sonra. ( Evet, bu daha kesin hale getirilebilir; Eğer tartışmak istiyorsanız, bana bildirin. )

Gösterdiğiniz yönlendirmelerle orijinal stdout ve stderr'i koruyabildiğinizden haklısınız, ancak daha sonra yukarıda gösterildiği gibi yeniden yönlendirilen kodu kapsamanız durumunda ne olduğunu anlamak için komut dosyasını daha sonra korumak zorunda olan insanlar için genellikle daha basittir.

Bash kılavuzunun ilgili bölümleri Gruplama Komutları ve G / Ç Yeniden Yönlendirme'dir . POSIX kabuğu belirtiminin ilgili bölümleri Bileşik Komutları ve G / Ç Yeniden Yönlendirme'dir . Bash bazı ekstra gösterimlere sahiptir, ancak aksi takdirde POSIX kabuk belirtimine benzer.


3
Bu, orijinal tanımlayıcıları kaydetmekten ve daha sonra geri yüklemekten çok daha açıktır.
Steve Madsen

23
Bunun gerçekten ne yaptığını anlamak için biraz googling yapmak zorunda kaldım, bu yüzden paylaşmak istedim. Kıvırcık parantezler , aslında anonim bir işlev oluşturan bir "kod bloğu" haline gelir . Kod bloğundaki her şey çıktısı yeniden yönlendirilebilir (bakınız bu bağlantıdan Örnek 3-2 ). Ayrıca bu küme parantezleri not yok bir başlatacak altkabuk , ancak benzer I / O yönlendirmeleri olabilir parantez kullanarak altkabuklarda ile yapılabilir.
chris

3
Bu çözümü diğerlerinden daha çok seviyorum. Yalnızca en temel G / Ç yönlendirmesi anlayışına sahip bir kişi bile olanları anlayabilir. Artı, daha ayrıntılı. Ve bir Pythoner olarak, verbose'u seviyorum.
John Red

Yapsan iyi olur >>. Bazı insanların alışkanlığı vardır >. Ekleme her zaman daha güvenli ve daha fazla tavsiye edilmemelidir. Birisi, bazı verileri aynı hedefe aktarmak için standart copy komutunu kullanan bir uygulama yazdı.
neverMind9

Bu uygulama ccc.bmw71 (.pro) (3C Pil Monitörü Widget, eski adıyla “Battery Monitor Widget”) her zaman pil geçmişini /sdcard/bmw_history.txt dosyasına aktarır. Eğer zaten varsa, tahmin edin ne, OVERWRITE! Bu, yanlışlıkla bazı pil geçmişini kaybetmeme neden oldu. Gün içinde limiti 30'dan çok yüksek bir sayıya ayarladım, bu da geçersiz kıldı ve tekrar 30'a koydu. Mevcut yedeklemeyi içe aktarmadan önce mevcut pil geçmişini dışa aktarmak istedim. Sonra oldu.
neverMind9

175

Genellikle bunlardan birini betiğin üstüne veya yakınına yerleştiririz. Komut satırlarını ayrıştıran komut dosyaları, ayrıştırmadan sonra yeniden yönlendirme yapar.

Stdout'u bir dosyaya gönderme

exec > file

stderr ile

exec > file                                                                      
exec 2>&1

stdout ve stderr dosyalarını dosyaya ekle

exec >> file
exec 2>&1

As Jonathan Leffler onun yorumunda belirtildiği :

execiki ayrı işi vardır. Birincisi, yürütülmekte olan kabuğu (script) yeni bir programla değiştirmektir. Diğeri, geçerli kabuktaki G / Ç yönlendirmelerini değiştiriyor. Bu hiçbir argümana sahip olmadan ayırt edilir exec.


7
Ben de bunun sonuna 2> & 1 ekleyin, sadece stderr de yakalanır. :-)
Chris Jester-Young

7
Bunları nereye koydun? Senaryonun üst kısmında mı?
colan

4
Bu çözümle, komut dosyasından çıkan yönlendirmeyi de sıfırlamak gerekir. Jonathan Leffler'in bir sonraki cevabı bu anlamda daha “başarısızlığa dayanıklı” dır.
Chuim

6
@JohnRed: execiki ayrı işi var. Birincisi, aynı komut dosyasını kullanarak geçerli komut dosyasını başka bir komutla değiştirmektir - diğer komutu argüman olarak belirtirsiniz exec(ve G / Ç yönlendirmelerini yaptığınız gibi değiştirebilirsiniz). Diğer iş, geçerli kabuk betiğindeki G / Ç yönlendirmelerini değiştirmeden değiştirmektir. Bu gösterim, argüman olarak bir komuta sahip olmamakla ayırt edilir exec. Bu yanıttaki gösterim "yalnızca G / Ç" değişkenidir - yalnızca yeniden yönlendirmeyi değiştirir ve çalışan komut dosyasının yerini almaz. ( setKomut benzer şekilde çok amaçlıdır.)
Jonathan Leffler

18
exec > >(tee -a "logs/logdata.log") 2>&1günlükleri ekrana yazdırır ve bunları bir dosyaya yazar
shriyog

32

Komut dosyasının tamamını şöyle bir işlev haline getirebilirsiniz:

main_function() {
  do_things_here
}

sonra komut dosyasının sonunda şu var:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Alternatif olarak, her çalışmayı her dosyada günlük dosyasına kaydedebilir ve yine de stdout'a çıktısını alabilirsiniz:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log

Bunu mu demek istediniz: main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez

Böyle bir boruda main_function kullanmayı seviyorum. Ancak bu durumda komut dosyanız orijinal dönüş değerini döndürmez. Bash durumunda çıkmanız ve 'exit $ {PIPESTATUS [0]}' komutunu kullanmanız gerekir.
rudimeier

7

Orijinal stdout ve stderr'i kaydetmek için şunları kullanabilirsiniz:

exec [fd number]<&1 
exec [fd number]<&2

Örneğin, aşağıdaki kod "walla1" ve "walla2" günlük dosyasına ( a.txt), "walla3" stdout'a, "walla4" stderr'e yazdırır.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6

1
Normalde, çıkışlar için girdi yeniden yönlendirme gösterimi yerine çıktı yeniden yönlendirme gösterimi kullanmak exec 5>&1ve kullanmak daha iyi olur exec 6>&2. Komut dosyası bir terminalden çalıştırıldığında, standart giriş de yazılabilir ve hem standart çıktı hem de standart hata, tarihsel bir tuhaflık sayesinde okunabilir (ya da 'vice'?): Terminal için açılır okuma ve yazma ve aynı açık dosya açıklaması üç standart G / Ç dosya tanımlayıcısının tümü için kullanılır.
Jonathan Leffler

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.