Bir değişkente standart hata nasıl saklanır


182

Diyelim ki şöyle bir senaryom var:

useless.sh

echo "This Is Error" 1>&2
echo "This Is Output" 

Başka bir kabuk betiğim daha var:

alsoUseless.sh

./useless.sh | sed 's/Output/Useless/'

"Bu hata" ya da useless.sh herhangi bir stderr bir değişkeni yakalamak istiyorum. Buna HATA diyelim.

Bir şey için stdout kullandığım dikkat edin. Stdout kullanmaya devam etmek istiyorum, bu yüzden stderr'ı stdout'a yönlendirmek bu durumda yardımcı olmaz.

Yani, temel olarak, yapmak istiyorum

./useless.sh 2> $ERROR | ...

ama bu kesinlikle işe yaramıyor.

Ayrıca yapabileceğimi de biliyorum

./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`

ama bu çirkin ve gereksiz.

Ne yazık ki, burada cevap gelmezse yapmam gereken şey budur.

Başka bir yol olduğunu umuyorum.

Daha iyi fikirleri olan var mı?


4
Stdout'u tam olarak ne kullanmak istiyorsunuz? Sadece konsolda görüntülemek istiyor musunuz? Yoksa çıktısını mı alıyorsunuz? Sadece konsol için stdout'u konsola ve stderr'i stdout'a yakalamak için yönlendirirsiniz:ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)
Tim Kersten

Yanıtlar:


91

Hata dosyasını yakalamak daha düzenli olur:

ERROR=$(</tmp/Error)

Kabuk bunu tanır ve catverileri almak için ' ' çalıştırmak zorunda değildir .

Daha büyük soru zor. Bunu yapmanın kolay bir yolu olduğunu sanmıyorum. Tüm boru hattını alt kabuğa oluşturmanız gerekir, sonunda nihai standart çıktısını bir dosyaya gönderirsiniz, böylece hataları standart çıktıya yönlendirebilirsiniz.

ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )

Yarı kolonun gerekli olduğuna dikkat edin (klasik kabuklarda - Bourne, Korn - kesin; muhtemelen Bash'de). ' {}', Ekli komutlar üzerinden G / Ç yönlendirmesi yapar. Yazıldığı gibi, hataları da sedyakalardı.

UYARI: Resmi olarak test edilmemiş kod - kendi sorumluluğunuzdadır.


1
Bilmediğim gerçekten çılgın bir numara olacağını ummuştum, ama öyle görünüyor. Teşekkürler.
psycotica0

9
Standart çıktıya ihtiyacınız yoksa, /dev/nullyerine yeniden yönlendirebilirsiniz outfile(Benim gibi iseniz, bu soruyu Google üzerinden buldunuz ve OP ile aynı gereksinimlere sahip değilsiniz)
Mark Eirich

2
Geçici dosyaları olmayan bir yanıt için buraya bakın .
Tom Hale

1
İşte bunu dosyalara yönlendirmeden yapmanın bir yolu; o takas oynuyor stdoutve stderrileri ve geri. Ancak burada belirtildiği gibi dikkat edin :
Bash'da

69

alsoUseless.sh

Bu, useless.shkomut dosyanızın çıktısını gibi bir komutla kanalize etmenizi sedve stderradlı bir değişkene kaydetmenizi sağlar error. Borunun sonucu stdoutgörüntüleme için gönderilir veya başka bir komuta iletilir .

Bunu yapmak için gereken yeniden yönlendirmeleri yönetmek için birkaç ekstra dosya tanımlayıcısı kurar.

#!/bin/bash

exec 3>&1 4>&2 #set up extra file descriptors

error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )

echo "The message is \"${error}.\""

exec 3>&- 4>&- # release the extra file descriptors

4
Dosya tanımlayıcılarını ayarlamak ve kapatmak için 'exec' kullanmak iyi bir tekniktir. Komut dosyası hemen sonra çıkarsa kapanmaya gerçekten gerek yoktur.
Jonathan Leffler

3
Nasıl hem yakalayacağı stderrve stdoutdeğişkenleri?
Gingi

Mükemmel. Bu dry_run, kuru çalıştırılan komutun başka bir dosyaya aktarılıp aktarılmadığına bakılmaksızın, bağımsız değişkenlerini yankılama ve çalıştırma arasında güvenilir bir seçim yapabilmeme yardımcı olur .
Mihai Danila

1
@ t00bs: readbir kanaldan girişi kabul etmez. Göstermeye çalıştığınız şeye ulaşmak için diğer teknikleri kullanabilirsiniz.
sonraki duyuruya kadar duraklatıldı.

2
Şununla daha basit olabilir: error = $ (./useless.sh | sed 's / Çıktı / Yararsız /' 2> & 1 1> & 3)
Jocelyn

64

Stderr'ı stdout'a, stdout'u / dev / null'a $()yönlendirdikten sonra geri tikleri kullanın veya yeniden yönlendirilen stderr'i yakalamak için:

ERROR=$(./useless.sh 2>&1 >/dev/null)

8
Boruyu örneğime dahil etmemin nedeni budur. Hala standart çıktıyı istiyorum ve başka şeyler yapmasını, başka yerlere gitmesini istiyorum.
psycotica0

Çıkışı yalnızca stderr'e gönderen komutlar için, yakalamanın basit yolu, örneğinPY_VERSION="$(python --version 2>&1)"
John Mark

9

Bu soru için, birçoğu stderr ve stdout'u ve çıkış kodunu aynı anda yakalamak istemediğiniz biraz daha basit bir kullanım senaryosuna sahip birçok kopya var .

if result=$(useless.sh 2>&1); then
    stdout=$result
else
    rc=$?
    stderr=$result
fi

başarı durumunda düzgün çıktı almayı veya hata durumunda stderr'de tanılama iletisini beklediğiniz ortak senaryoda çalışır.

Kabuğun kontrol deyimlerinin zaten $?başlık altında incelendiğine dikkat edin; yani benzeyen herhangi bir şey

cmd
if [ $? -eq 0 ], then ...

sadece beceriksiz, tek kelimeyle söylemenin bir yolu

if cmd; then ...

Bu benim için çalıştı: my_service_status = $ (service my_service status 2> & 1) Teşekkürler !!
JRichardsz

6
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}

1
commandburada kötü bir seçim, aslında bu isimde bir yerleşik var. yourCommandDaha açık olmasını sağlayabilir .
Charles Duffy

4

Okuyucunun yararı için bu tarif burada

  • stderr'ı bir değişkene yakalamak için oneliner olarak tekrar kullanılabilir
  • hala komutun dönüş koduna erişim sağlıyor
  • Geçici bir dosya tanımlayıcı 3'ü feda eder (elbette sizin tarafınızdan değiştirilebilir)
  • Ve bu geçici dosya tanımlayıcılarını iç komuta maruz bırakmaz

Eğer içine stderrbazılarını yakalamak istiyorsanız yapabilirsinizcommandvar

{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;

Daha sonra hepsine sahipsiniz:

echo "command gives $? and stderr '$var'";

Eğer commandbasittir (gibi bir şey değil a | b) Eğer iç bırakabilir {}koyma:

{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;

Kolay bir yeniden kullanılabilir bashfonksiyona sarılmış (muhtemelen sürüm 3 ve üstü için gereklidir local -n):

: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }

Açıklaması:

  • local -ntakma adlar "$ 1" (için değişken olan catch-stderr)
  • 3>&1 stdout noktalarını kaydetmek için dosya tanımlayıcı 3'ü kullanır
  • { command; } (veya "$ @") sonra çıkış yakalama içinde komutu yürütür $(..)
  • Burada tam siparişin önemli olduğunu lütfen unutmayın (yanlış şekilde yapmak dosya tanımlayıcılarını yanlış karıştırır):
    • 2>&1stderrçıktı yakalamaya yönlendiriyor$(..)
    • 1>&3dosya tanımlayıcı 3'te kaydedilen "dış" a geri çeken stdoutçıkıştan yeniden yönlendirir. FD 1'in daha önce nereye işaret ettiğini hala not edin : Çıktı yakalamaya$(..)stdoutstderr$(..)
    • 3>&-daha sonra gerekmediği için dosya tanımlayıcısını 3 kapatır, öyle ki commandaniden bilinmeyen bazı açık dosya tanımlayıcıları görünmez. Dış kabuğun hala FD 3 açık olduğunu, ancak commandgörmeyeceğini unutmayın.
    • İkincisi önemlidir, çünkü bazı programlar lvmbeklenmedik dosya tanımlayıcılarından şikayet ediyor. Ve lvmşikayet ediyor stderr- sadece ne yakalamak olacak!

Uygun şekilde uyarlanırsanız, bu tarifle başka bir dosya tanımlayıcı yakalayabilirsiniz. Tabii ki dosya tanımlayıcı 1 hariç (burada yönlendirme mantığı yanlış olurdu, ancak dosya tanımlayıcı 1 için var=$(command)her zamanki gibi kullanabilirsiniz ).

Bunun dosya tanımlayıcı 3'ü feda ettiğini unutmayın. Dosya tanımlayıcıya ihtiyacınız olursa, numarayı değiştirmekten çekinmeyin. Ama (1980 itibaren) bazı kabukları anlayabiliriz bilin 99>&1argüman olarak 9takip 9>&1(bunun için hiçbir sorun bash).

Ayrıca, bu FD 3'ü bir değişken üzerinden yapılandırılabilir hale getirmenin kolay olmadığını da unutmayın. Bu, işleri çok okunmaz hale getirir:

: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;

eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}

Güvenlik notu: İlk 3 argüman catch-var-from-fd-by-fdbir 3. taraftan alınmamalıdır. Onları her zaman açıkça "statik" bir şekilde verin.

Yani hayır-hayır-hayır catch-var-from-fd-by-fd $var $fda $fdb $command, bunu asla yapma!

Bir değişken değişken adı iletirseniz, en azından aşağıdaki gibi yapın: local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command

Bu yine de sizi her istismardan korumaz, ancak en azından yaygın komut dosyası hatalarını tespit etmeye ve önlemeye yardımcı olur.

Notlar:

  • catch-var-from-fd-by-fd var 2 3 cmd.. aynıdır catch-stderr var cmd..
  • shift || returndoğru sayıda argüman vermeyi unutmanız durumunda çirkin hataları önlemenin bir yoludur. Belki de kabuğun sonlandırılması başka bir yol olacaktır (ancak bu komut satırından test yapmayı zorlaştırır).
  • Rutin öyle yazılmıştır ki, anlaşılması daha kolaydır. İşlev gerekli olmayacak şekilde yeniden yazılabilir exec, ancak daha sonra gerçekten çirkinleşir.
  • Bu rutin olmayan için tekrar yazılabilir bashgerek olmadığı gibi bildik local -n. Ancak o zaman yerel değişkenleri kullanamazsınız ve çok çirkinleşir!
  • Ayrıca evals güvenli bir şekilde kullanıldığını unutmayın . Genellikle evaltehlikeli kabul edilir. Ancak bu durumda "$@"(keyfi komutları çalıştırmak için) kullanmaktan daha kötü değildir . Ancak lütfen burada gösterildiği gibi tam ve doğru alıntıları kullandığınızdan emin olun (aksi takdirde çok çok tehlikeli hale gelir ).

3

İşte böyle yaptım:

#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
    local tmpFile=$(mktemp)

    $2 2> $tmpFile

    eval "$1=$(< $tmpFile)"

    rm $tmpFile
}

Kullanım örneği:

captureStderr err "./useless.sh"

echo -$err-

Bu does geçici bir dosya kullanır. Ama en azından çirkin şeyler bir işleve sarılır.


@ShadowWizard Benim tarafımda küçük bir şüphe var. Fransızca'da, kolondan önce bir boşluk gelir. Bu kuralı yanlışlıkla İngilizce cevaplarla uyguluyorum. Kontrol ettikten sonra bu , ben yine bu hata yapmaz biliyorum.
Stephan

@Stephan şerefe, bu da burada tartışıldı . :)
Gölge Sihirbazı Senin için Kulak

1
Bunu yapmaktan daha güvenli yollar var eval. Örneğin, değişkeniniz kötü amaçlı bir değere ayarlanmışsa (veya hedef değişken adınız böyle bir değer içeriyorsa) printf -v "$1" '%s' "$(<tmpFile)"rastgele kod çalıştırma riskini almaz TMPDIR.
Charles Duffy

1
Benzer şekilde, rm -- "$tmpFile"daha sağlamdır rm $tmpFile.
Charles Duffy

2

Bu, zarif bir çözüm olmasını umduğum ilginç bir problem. Ne yazık ki, Bay Leffler'e benzer bir çözüm buldum, ancak daha iyi okunabilirlik için bir Bash işlevinin içinden yararsız olarak çağırabileceğinizi ekleyeceğim:

#! / Bin / bash

işlevsiz {
    /tmp/useless.sh | sed 's / Çıktı / Yararsız /'
}

HATA = $ (yararsız)
echo $ ERROR

Diğer tüm çıktı yönlendirmeleri geçici bir dosya ile desteklenmelidir.


2

POSIX

STDERR, bazı yönlendirme sihirleriyle yakalanabilir:

$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/

$ echo $error
ls: cannot access '/XXXX': No such file or directory

Komutun STDOUT (burada ls) borularının en içte yapıldığına dikkat edin { }. Basit bir komut yürütüyorsanız (örn. Bir boru değil), bu iç parantezleri kaldırabilirsiniz.

Sen boru olarak komuta dışında değil boru bir altkabuk kılar bashve zshve alt kabukta değişkene atama geçerli kabuğuna mevcut olmaz.

darbe

İçinde bash, dosya tanımlayıcı 3'ün kullanılmadığını varsaymamak daha iyi olur:

{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1; 
exec {tmp}>&-  # With this syntax the FD stays open

Bunun işe yaramadığını unutmayın zsh.


Genel fikir için bu cevaba teşekkürler .


Bu satırı ayrıntılarıyla açıklayabilir misiniz? 1> & $ tmp; {error = $ ({{ls -ld / XXXX / bin | tr o Z;} 1> & $ tmp;} 2> & 1); } {tmp}> & 1;
Thiago Conrado

1

Bu yazı, kendi amaçlarım için benzer bir çözüm bulmama yardımcı oldu:

MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`

O zaman MESAJIMIZ boş bir dize olmadığı sürece onu başka şeylere aktarırız. Bu, format_logs.py dosyamızın bir tür python istisnasıyla başarısız olup olmadığını bize bildirir.


1

Yakala VE Yazdır stderr

ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )

Yıkmak

$()Stdout'u yakalamak için kullanabilirsiniz , ancak bunun yerine stderr'i yakalamak istersiniz. Yani stdout ve stderr'ı değiştiriyorsun. Standart takas algoritmasında geçici depolama olarak fd 3 kullanımı.

Yakalamak VE yazdırmak istiyorsanız teeçoğaltmak için kullanın . Bu durumda çıkış teeÇekilecek $()yerine konsola halindeyken, ancak stderr'e daha (bir teebiz ikinci çıkış olarak kullanıp böylece) yine konsola gidecek teeözel dosyası üzerinden /dev/fd/2yana teebir fd yerine beklentiden bir dosya yolu numara.

NOT: Bu, tek bir satırda çok fazla yönlendirme ve sipariş önemlidir. $()stdout'unu kapma teeboru hattının sonunda ve kendisi yolları arasında stdout'a boru hattını ./useless.shait stdin'e teebiz stdin'i ve için stdout'u takas SONRA ./useless.sh.

./Useless.sh stdout'unu kullanma

OP hala stdout kullanmak istediğini söyledi ./useless.sh | sed 's/Output/Useless/'.

Sorun değil sadece stdout ve stderr takmadan ÖNCE yapın. Bir fonksiyona veya dosyaya (ayrıca-useless.sh) taşınmasını ve bunu yukarıdaki satırda ./useless.sh yerine çağırmanızı öneririm.

Ancak, stdout VE stderr CAPTURE istiyorsanız, o zaman ben $()sadece bir seferde bir tane yapacak ve değişkenleri geri veremez bir alt kabuk yapar çünkü geçici dosyaları geri düşmek zorunda düşünüyorum .


1

Tom Hale'nin cevabında biraz yineleyerek, yeniden yönlendirme yoga'sını daha kolay yeniden kullanmak için bir işleve sarmayı mümkün buldum. Örneğin:

#!/bin/sh

capture () {
    { captured=$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1
}

# Example usage; capturing dialog's output without resorting to temp files
# was what motivated me to search for this particular SO question
capture dialog --menu "Pick one!" 0 0 0 \
        "FOO" "Foo" \
        "BAR" "Bar" \
        "BAZ" "Baz"
choice=$captured

clear; echo $choice

Bunu daha da basitleştirmek neredeyse kesinlikle mümkündür. Özellikle ayrıntılı bir şekilde test etmedim, ancak hem bash hem de ksh ile çalışıyor gibi görünüyor.


0

Geçici bir dosyanın kullanımını atlamak istiyorsanız, işlem değiştirmeyi kullanabilirsiniz. Henüz işe yaramadı. Bu benim ilk denememdi:

$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'

Sonra denedim

$ ./useless.sh 2> >( ERROR=$( cat <() )  )
This Is Output
$ echo $ERROR   # $ERROR is empty

ancak

$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error

Bu yüzden süreç ikamesi genellikle doğru olanı yapıyor ... ne yazık ki, STDIN'i bir değişkenle yakalama girişiminde bir >( )şeyle sardığımda $(), içeriğini kaybediyorum $(). Bunun nedeni $(), artık ana işlemin sahip olduğu / dev / fd dosya tanımlayıcısına erişemeyen bir alt işlemi başlatmasıdır.

Süreç ikamesi bana artık STDERR'de olmayan bir veri akışı ile çalışma yeteneğini satın aldı, ne yazık ki istediğim gibi manipüle edemiyorum.


1
Eğer ./useless.sh 2> >( ERROR=$( cat <() ); echo "$ERROR" )yapsaydınız çıktısını görürsünüz ERROR. Sorun, işlem ikamesinin bir alt kabukta çalıştırılmasıdır, bu nedenle alt kabukta ayarlanan değer, ana kabuğu etkilemez.
Jonathan Leffler

0
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr

3
Bu iyi bir fikir gibi görünüyor, ancak Mac OSX 10.8.5'te yazdırılıyora=> b=>stderr
Heath Borders

3
@HeathBorders ile katılıyorum; bu gösterilen çıktıyı üretmez. Buradaki sorun a, bir alt kabukta değerlendirilen ve atanan ve alt kabuktaki atamanın üst kabuğu etkilememesidir. (Ubuntu 14.04 LTS ve Mac OS X 10.10.1'de test edildi.)
Jonathan Leffler

Aynı şey Windows GitBash için de geçerli. Yani işe yaramıyor. ( GNU bash, version 4.4.12(1)-release (x86_64-pc-msys))
Kirby

SLE 11.4
İkisi

Bu kod soruyu cevaplayabilirken, bu kodun soruyu neden ve / veya nasıl cevapladığı konusunda ek bağlam sağlamak uzun vadeli değerini artırır.
βε.εηοιτ.βε

0

Zsh dilinde:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )



0

YellowApple'ın cevabında iyileştirme :

Bu, stderr'i herhangi bir değişkene yakalamak için bir Bash işlevidir

stderr_capture_example.sh:

#!/usr/bin/env bash

# Capture stderr from a command to a variable while maintaining stdout
# @Args:
# $1: The variable name to store the stderr output
# $2: Vararg command and arguments
# @Return:
# The Command's Returnn-Code or 2 if missing arguments
function capture_stderr {
  [ $# -lt 2 ] && return 2
  local stderr="$1"
  shift
  {
    printf -v "$stderr" '%s' "$({ "$@" 1>&3; } 2>&1)"
  } 3>&1
}

# Testing with a call to erroring ls
LANG=C capture_stderr my_stderr ls "$0" ''

printf '\nmy_stderr contains:\n%s' "$my_stderr"

Test yapmak:

bash stderr_capture_example.sh

Çıktı:

 stderr_capture_example.sh

my_stderr contains:
ls: cannot access '': No such file or directory

Bu işlev, döndürülen bir dialogkomut seçimini yakalamak için kullanılabilir .

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.