Bir işlemin stdout ve stderr'ını tek bir dosyaya yeniden yönlendirmek istiyorum. Bunu Bash'de nasıl yapabilirim?
Bir işlemin stdout ve stderr'ını tek bir dosyaya yeniden yönlendirmek istiyorum. Bunu Bash'de nasıl yapabilirim?
Yanıtlar:
Buraya bir bak . Olmalı:
yourcommand &>filename
(her ikisini de stdout
ve stderr
dosya adına yönlendirir ).
#!/bin/bash
yerine #!/bin/sh
bash gerektirdiğinden, bunun yerine ile başladığından emin olun .
do_something 2>&1 | tee -a some_file
Bu stdout ve Stdout'a stderr'yi yönlendirmek için gidiyor some_file
ve stdout'a yazdırmak.
do_something &>filename
vermez. +1.
Ambiguous output redirect.
neden bir fikrin?
$?
artık çıkış durumuyla do_something
değil, çıkış durumuyla ilgili yan etkisi olduğunu unutmayın tee
.
Sen yönlendirebilirsiniz stderr'yi için stdout ve stdout bir dosyaya:
some_command >file.log 2>&1
Bkz. Http://tldp.org/LDP/abs/html/io-redirection.html
Bu biçim, yalnızca bash ile çalışan en popüler &> biçiminden daha çok tercih edilir. Bourne kabuğunda, komutu arka planda çalıştırmak olarak yorumlanabilir. Ayrıca biçim daha okunabilir 2 (STDERR) 1'e (STDOUT) yönlendirilir.
DÜZENLEME: yorumlarda belirtildiği gibi sırayı değiştirdi
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Şimdi, basit yankı $ LOG_FILE dosyasına yazacak. Arka plan programı için kullanışlıdır.
Orijinal yazının yazarına,
Neyi başarmanız gerektiğine bağlıdır. Betiğinizden aradığınız bir komutun içine / dışına yönlendirmeniz gerekiyorsa, yanıtlar zaten verilmiştir. Benimki, söz konusu kod snippet'inden sonra tüm komutları / yerleşikleri (çatalları içerir) etkileyen geçerli komut dosyası içinde yeniden yönlendirmeyle ilgilidir .
Bir başka harika çözüm, hem bir std-err / out AND'e hem de bir günlükçüye veya günlük dosyasına yönlendirme ile ilgilidir. Bu işlevsellik, aynı anda birkaç dosya tanımlayıcısını (dosyalar, soketler, borular vb.) Yazabilen / ekleyebilen 'tee' komutu ile sağlanır: tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Yani, en başından. Diyelim ki / dev / stdout (FD # 1) ve / dev / stderr (FD # 2) 'ye bağlı terminalimiz var. Uygulamada, bir boru, soket veya başka bir şey olabilir.
Yukarıdaki satıra ve ayrıca buna sahip bir komut dosyası çalıştırmanın sonucu:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...Şöyleki:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Daha net resim görmek istiyorsanız betiğe şu 2 satırı ekleyin:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
kabuğa dosyaya STDOUT göndermesini söyler file.log
ve 2>&1
STDERR'u (dosya tanıtıcı 2) STDOUT'a (dosya tanıtıcı 1) yönlendirmesini söyler.
Not: liw.fi'nin işaret ettiği gibi sipariş önemlidir, 2>&1 1>file.log
çalışmaz.
Merakla, bu işe yarıyor:
yourcommand &> filename
Ancak bu bir sözdizimi hatası verir:
yourcommand &>> filename
syntax error near unexpected token `>'
Kullanmanız gerekenler:
yourcommand 1>> filename 2>&1
&>>
BASH 4 üzerinde çalışıyor gibi görünüyor:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Kısa cevap: Command >filename 2>&1
veyaCommand &>filename
Açıklama:
"Stdout" sözcüğünü stdout'a ve "stderror" kelimesini stderror'a yazdıran aşağıdaki kodu düşünün.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
'&' Operatörünün bash'a 2'nin bir dosya adı değil (stderr'yi gösteren) bir dosya tanıtıcısı olduğunu söylediğini unutmayın. '&' stdout
İşaretini dışarıda bırakırsak, bu komut stdout'a yazdırır ve "2" adlı bir dosya oluşturur ve stderror
oraya yazar.
Yukarıdaki kodu deneyerek, yeniden yönlendirme operatörlerinin tam olarak nasıl çalıştığını kendiniz görebilirsiniz. Örneğin, iki tanımlayıcıdan hangisinin aşağıdaki iki kod satırına 1,2
yönlendirildiğini değiştirerek /dev/null
stdout'tan her şeyi ve stderror'dan her şeyi siler (kalanları yazdırmak).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Şimdi, aşağıdaki kodun neden çıktı üretmediğini açıklayabiliriz:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Bunu gerçekten anlamak için, bu web sayfasını dosya tanımlayıcı tablolarında okumanızı şiddetle tavsiye ederim . Bu okumayı yaptığınızı varsayarsak, devam edebiliriz. Bash'in soldan sağa işlem yaptığını unutmayın; böylece Bash >/dev/null
önce (aynı olanı) görür 1>/dev/null
ve dosya tanımlayıcı 1'i stdout yerine / dev / null olarak gösterecek şekilde ayarlar. Bunu yaptıktan sonra Bash sağa doğru hareket eder ve görür 2>&1
. Bu, dosya tanıtıcısı 2'yi dosya tanıtıcısı 1 ile aynı dosyayı gösterecek şekilde ayarlar (dosya tanıtıcısı 1'in kendisini değil !!!!) işaretçilerdeki bu kaynağa bakındaha fazla bilgi için)) . Dosya tanımlayıcı 1 / dev / null değerini ve dosya tanımlayıcı 2, dosya tanımlayıcı 1 ile aynı dosyayı gösterdiğinden, dosya tanımlayıcı 2 artık / dev / null öğesini de gösterir. Bu nedenle, her iki dosya tanımlayıcısı da / dev / null değerini gösterir ve bu nedenle çıktı alınmaz.
Konsepti gerçekten anlayıp anlamadığınızı test etmek için, yeniden yönlendirme sırasını değiştirdiğimizde çıktıyı tahmin etmeye çalışın:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
Std. Hata
Buradaki mantık, soldan sağa değerlendirildiğinde Bash 2> & 1'i görür ve böylece dosya tanımlayıcı 2'yi dosya tanımlayıcı 1 ile aynı yere, yani stdout'a işaret edecek şekilde ayarlar. Daha sonra dosya tanımlayıcı 1'i (> / dev / null = 1> / dev / null)> / dev / null'a işaret edecek şekilde ayarlar, böylece genellikle standart çıkışa gönderilecek her şeyi siler. Böylece geriye kalan tek şey, alt kabukta (parantez içindeki kod) - yani "stderror" daki stdout'a gönderilmediği idi. Burada dikkat edilmesi gereken ilginç bir nokta, 1'in stdout'a bir işaretçi olmasına rağmen, 2'den 1'e doğru yönlendiricinin 2>&1
2 -> 1 -> stdout işaretçisi zincirini oluşturmamasıdır. 1 değerini / dev / null değerine yönlendirmenin bir sonucu olarak, kod2>&1 >/dev/null
işaretçi zincirine 2 -> 1 -> / dev / null verir ve böylece kod, yukarıda gördüğümüzün aksine hiçbir şey üretmez.
Son olarak, bunu yapmanın daha basit bir yolu olduğunu unutmayın:
Burada 3.6.4 bölümünden , &>
hem stdout hem de stderr'i yönlendirmek için operatörü kullanabileceğimizi görüyoruz . Bu nedenle, herhangi bir komutun hem stderr hem de stdout çıktısını (çıktıyı \dev\null
siler) yeniden yönlendirmek için basitçe
$ command &> /dev/null
veya örneğim durumunda yazıyoruz:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Temel çıkarımlar:
2>&1 >/dev/null
olduğu! = >/dev/null 2>&1
. Biri çıktı üretir, diğeri vermez!Son olarak şu harika kaynaklara bir göz atın:
Yeniden Yönlendirme üzerinde Bash Belgeler , Dosya Tanıtıcı Tablolar Bir Açıklama , İşaretçiler giriş
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
İlgili: syslog'a stdOut & stderr yazmak.
Neredeyse işe yarıyor, ama xinted'dan değil;
Ben stdout artı stderr çıktı bir günlük dosyası ve hala konsolda stderr yazılı bir çözüm istedim. Bu yüzden tee yoluyla stderr çıktısını çoğaltmam gerekiyordu.
Bulduğum çözüm bu:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
Durum için, "boru tesisatı" gerektiğinde şunları kullanabilirsiniz:
| &
Örneğin:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
veya
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Bu bash tabanlı çözümler STDOUT ve STDERR'ı ayrı ayrı borulandırabilir ("sort -c" nin STDERR'sinden veya STDERR'den "sort -h" ye).
Aşağıdaki işlevler, stdout / stderr ve bir logfile arasındaki çıkışları değiştirme işlemini otomatikleştirmek için kullanılabilir.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Betik içindeki kullanım örneği:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ Fernando-fabreti
Yaptıklarınıza ek olarak işlevleri biraz değiştirdim ve & - kapanışını kaldırdım ve benim için çalıştı.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
Böyle şeyleri kullanmayı düşündüğünüz durumlarda, exec 2>&1
mümkünse aşağıdaki gibi bash işlevlerini kullanarak kodu yeniden yazmak daha kolay okunur:
function myfunc(){
[...]
}
myfunc &>mylog.log