Başka birine aktarılan işlemin çıkış durumunu al


Yanıtlar:


256

Kullanıyorsanız , boru hattının her bir elemanının çıkış durumunu almak için dizi değişkenini bashkullanabilirsiniz PIPESTATUS.

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

Kullanıyorsanız zsh, dizi çağrılır pipestatus(durum önemlidir!) Ve dizi endeksleri birinden başlar:

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

Onları bir fonksiyon içinde değerleri kaybetmeyecek şekilde birleştirmek için:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

Yukarıdakileri çalıştırın bashveya zshaynı sonuçları elde edin; sadece bir tanesi retval_bashve retval_zshbelirlenecek. Diğeri boş olacak. Bu, bir işlevin bitmesini sağlar return $retval_bash $retval_zsh(tırnak işaretlerinin olmamasına dikkat edin!).


9
Ve pipestatuszsh olarak. Ne yazık ki diğer mermilerde bu özellik yoktur.
Gilles

8
Not: zsh içindeki diziler, 1. dizinde tersine başlayarak başlar, öyleyse öyle echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm

5
Tüm boru hattını şu şekilde kontrol edebilirsiniz:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0

7
@JanHudec: Belki de cevabımın ilk beş kelimesini okumalısınız. Ayrıca sorunun sadece POSIX-cevaplı bir soruyu nerede istediğini de belirtiniz.
Camh

12
@JanHudec: POSIX olarak etiketlenmedi. Neden cevabın POSIX olması gerektiğini düşünüyorsunuz? Belirtilmedi, bu yüzden nitelikli bir cevap verdim. Cevabımla ilgili yanlış bir şey yok, ayrıca diğer davaları ele almak için birden fazla cevap var.
Camh

237

Bunu yapmanın 3 yaygın yolu vardır:

Pipefail

İlk yol pipefailseçeneği ( ksh, zshveya bash) ayarlamaktır. Bu en basittir ve yaptığı temel olarak çıkış durumunu $?sıfır olmayan bir şekilde çıkmak için son programın çıkış koduna ayarlar (ya da hepsi başarılı bir şekilde çıkarsa sıfır).

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

Bash ayrıca, son boru hattındaki tüm programların çıkış durumunu içeren $PIPESTATUS( $pipestatusin zsh) adında bir dizi değişkenine sahiptir .

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

İhtiyacınız olan boru hattındaki belirli bir değeri almak için 3. komut örneğini kullanabilirsiniz.

Ayrı infazlar

Bu, çözümlerin en hantal halidir. Her komutu ayrı ayrı çalıştırın ve durumu yakalayın

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1

2
Kahretsin! PIPESTATUS hakkında yazı gönderecektim.
slm

1
Başvuru için bu SO sorusunda tartışılan başka birkaç teknik daha var: stackoverflow.com/questions/1221833/…
slm

1
Borulartatus çözüm @Patrick bash üzerinde çalışır, sadece biraz daha quastion ksh komut dosyası kullanmak durumunda pipestatus benzer bir şey bulabileceğimizi düşünüyorsunuz? , (bu arada ksh tarafından desteklenmeyen pipestatus'u görüyorum)
yael

2
@yael Kullanmıyorum ksh, ancak kısa bir bakışta, manpage sayfasına, desteklemiyor $PIPESTATUSya da benzeri bir şey yok. Yine de pipefailseçeneği destekliyor .
Patrick

1
Burada başarısız komutun statüsünü elde etmeme izin verdiği için pipefail ile gitmeye karar verdim:LOG=$(failed_command | successful_command)
vmrob

51

Bu çözüm, bash belirli özellikleri veya geçici dosyaları kullanmadan çalışır. Bonus: sonunda çıkış durumu aslında bir çıkış durumu olup dosyadaki bir dize değildir.

Durum:

someprog | filter

çıkış durumunu someprogve çıktısını almak istiyorsunuz filter.

İşte benim çözümüm:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

bu yapının sonucu, yapının stdoutundan filterve çıkış durumundan yapının çıkış durumu olarak stdout'tur someprog.


Bu yapı aynı zamanda {...}alt kabuklar yerine basit komut gruplama ile çalışır (...). alt kabukların, burada ihtiyaç duymadığımız bir performans maliyeti olan bazı etkileri vardır. Daha fazla bilgi için ince bash kılavuzunu okuyun: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

Maalesef, bash dilbilgisi, küme parantezleri için boşluk ve noktalı virgül gerektirir, böylece yapı çok daha geniş hale gelir.

Bu metnin geri kalanında subshell varyantını kullanacağım.


Örnek someprogve filter:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

Örnek çıktı:

filtered line1
filtered line2
filtered line3
42

Not: alt işlem, açık dosya tanımlayıcılarını üst öğeden miras alır. Bu, someprogaçık dosya tanımlayıcı 3 ve 4'ü devralacak demektir. Dosya tanımlayıcı 3'e someprogyazarsa çıkış durumu olur. Gerçek çıkış durumu göz ardı edilecektir, çünkü readsadece bir kez okur.

someprogDosya tanımlayıcı 3 veya 4'e yazabildiğinizden endişeleniyorsanız, aramadan önce dosya tanımlayıcılarını kapatmak en iyisidir someprog.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

Daha exec 3>&- 4>&-önce someprogçalıştırmadan önce dosya tanımlayıcısını kapatır, bu someprogyüzden someprogbu dosya tanımlayıcıları yoktur.

Aynı zamanda şöyle de yazılabilir: someprog 3>&- 4>&-


Yapının adım adım açıklaması:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

Aşağıdan yukarıya:

  1. Stdout'a yönlendirilmiş dosya tanımlayıcı 4 ile bir alt kabuk yaratılır. Bu, alt kabuktaki dosya tanımlayıcısına 4 yazdırılanın tüm yapının stdout'u olarak sona ereceği anlamına gelir.
  2. Bir boru oluşturulur ve soldaki ( #part3) ve sağdaki ( #part2) komutlar uygulanır. exit $xsAynı zamanda, borunun son komutudur ve bu, stdin'den gelen dizenin tüm yapının çıkış durumu olacağı anlamına gelir.
  3. Stdout'a yönlendirilmiş dosya tanımlayıcı 3 ile bir alt kabuk yaratılır. Bu, bu alt kabuktaki dosya tanımlayıcısına 3 yazdırılanın sona ereceği #part2ve tüm yapının çıkış durumu olacağı anlamına gelir.
  4. Bir boru oluşturulur ve soldaki ( #part5ve #part6) ve sağdaki ( filter >&4) komutlar uygulanır. Çıktısı filterolarak tanımlayıcı 4. dosyaya yönlendirilir #part1dosya tanımlayıcı 4 Stdout'a yönlendirildi. Bu, çıktının filtertüm yapının stdout olduğu anlamına gelir .
  5. Çıkış durumundan #part6dosya tanıtıcısına 3 yazdırılır. #part3Dosya tanıtıcısında 3 yönlendirildi #part2. Bu, çıkış durumunun #part6tüm yapının son çıkış durumu olacağı anlamına gelir .
  6. someprogIdam edildi. Çıkış durumu alınır #part5. Stdout, boru tarafından alınır #part4ve iletilir filter. filterİrade çıktısı sırayla açıklandığı gibi stdout'a ulaşacaktır.#part4

1
Güzel. Ben sadece yapabilirim fonksiyonu için(read; exit $REPLY)
jthill

5
(exec 3>&- 4>&-; someprog)basitleştirir someprog 3>&- 4>&-.
Roger Pate

Bu yöntem aynı zamanda alt { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
kabuklar

36

Tam olarak istediğin gibi olmasa da,

#!/bin/bash -o pipefail

Böylece borularınız son sıfır olmayan dönüşü döndürür.

biraz daha az kodlama olabilir

Düzenleme: Örnek

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1

9
set -o pipefailbetiğin içinde daha sağlam olması gerekir, örneğin birinin komut dosyasını çalıştırması durumunda bash foo.sh.
maxschlepzig

Bu nasıl çalışıyor? bir örnek var mı
Johan

2
-o pipefailPOSIX’de olmadığını unutmayın .
Ocak'ta

2
Bu BASH 3.2.25 (1) sürümünde çalışmıyor. / Tmp / ff 'nin en üstünde ben var #!/bin/bash -o pipefail. Hata:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez

2
@FelipeAlvarez: Bazı ortamlar (linux dahil) #!ilki dışındaki satırlardaki boşlukları ayrıştırmaz ve bu nedenle argüman olarak bir sonraki öğeyi kullanan /bin/bash -o pipefail /tmp/ffgerekli /bin/bash -o pipefail /tmp/ff- getopt(veya benzeri) ayrıştırma yerine bu olur için , böylece başarısız olur. Eğer bir sarmalayıcı yapacaksanız (şunu söylemiştiniz, sadece bunu yaptınız ve hatta sıraya koyun , bu işe yarayacaktı.) Ayrıca bakınız: en.wikipedia.org/wiki/Shebang_%28Unix%29optargARGV-obash-pfexec /bin/bash -o pipefail "$@"#!
0

22

Mümkün olduğunda ne yapacağımı çıkış kodunu fooiçine beslemektir bar. Örneğin, foohiçbir zaman yalnızca basamaklı bir çizgi oluşturmadığını biliyorsanız, o zaman çıkış kodunu sabitleyebilirim:

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

Veya çıktının foohiçbir zaman tam olarak bir çizgi içermediğini biliyorsam .:

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

Bu bar, son satır dışındaki herkes üzerinde çalışmanın ve son satırı çıkış kodu olarak geçirmenin bir yolu varsa, her zaman yapılabilir .

Eğer barkimin çıkış İhtiyacınız olmayan karmaşık bir boru hattı, farklı bir dosya tanımlayıcı üzerinde çıkış kodu yazdırarak bir kısmını atlayabilir.

exit_codes=$({ { foo; echo foo:"$?" >&3; } |
               { bar >/dev/null; echo bar:"$?" >&3; }
             } 3>&1)

Bundan sonra $exit_codesgenellikle foo:X bar:Y, ama olabilir bar:Y foo:Xeğer baronun bütün girdilere okumadan önce ya da şanssız iseniz kapanıyor. 512 bayta kadar olan borulara yazılan tüm birliklerde atomik olduğunu düşünüyorum, bu nedenle foo:$?ve bar:$?etiket dizeleri 507 baytın altında olduğu sürece parçalar birleştirilmeyecek.

Çıktısını yakalamanız gerekirse bar, zorlaşır. Yukarıdaki teknikleri, barhiçbir zaman bir çıkış kodu göstergesine benzeyen bir çizgi içermemesi için çıktısını düzenleyerek birleştirebilirsiniz .

output=$(echo;
         { { foo; echo foo:"$?" >&3; } |
           { bar | sed 's/^/^/'; echo bar:"$?" >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

Ve tabii ki, durumu saklamak için geçici bir dosya kullanmak için basit bir seçenek var . Basit, ama üretimde bu kadar basit değil :

  • Eşzamanlı olarak çalışan birden çok komut dosyası varsa veya aynı komut dosyası birkaç yerde bu yöntemi kullanıyorsa, farklı geçici dosya adları kullandıklarından emin olmanız gerekir.
  • Paylaşılan bir dizinde güvenli bir şekilde geçici bir dosya oluşturmak zordur. Genellikle, /tmpbir komut dosyasının dosya yazabildiğinden emin olduğunuz tek yer burasıdır. Kullanım mktempgünümüzde tüm ciddi Unix'lerde POSIX ama mevcut değildir.
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
Geçici dosya yaklaşımını kullanırken, tüm geçici dosyaları kaldıran EXIT için bir tuzak eklemeyi tercih ederim, böylece senaryo ölse bile çöp kalmayacak
miracle173

17

Boru hattından başlayarak:

foo | bar | baz

İşte sadece POSIX kabuğunu kullanan ve geçici dosyalar içermeyen genel bir çözüm:

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$error_statuses Her durumun hangi komutu yattığını söyleyecek indekslerle, başarısız olan işlemlerin durum kodlarını rasgele sırayla içerir.

# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null

$error_statusesTestlerimdeki alıntıları not edin ; onlar olmadan grepfarklılaşamazlar çünkü yeni satırlar boşluklara zorlanır.


12

Bu yüzden lesmana'nınki gibi bir cevaba katkıda bulunmak istedim, ama benimkinin belki de daha basit ve biraz daha avantajlı bir saf Bourne kabuklu çözümü olduğunu düşünüyorum:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

Bunun en iyisi içten açıklanabileceğini düşünüyorum - komut1 normal çıktısını stdout'ta (dosya tanımlayıcı 1) yürütecek ve yazdıracak, sonra bir kez yapılırsa, printf komutun stdout'unda komut kodunu çalıştıracak ve yazacaktır, ancak bu stdout yönlendirildi dosya tanıtıcısı 3.

Komut1 çalışırken, stdout komutu2'ye yöneltiliyor (printf'un çıkışı asla komut2'ye gelmiyor, çünkü onu 1'in yerine dosya tanımlayıcısına 3 (göndeririz). Daha sonra komut2'nin çıktısını dosya tanımlayıcı 4'e yeniden yönlendiririz, böylece dosya tanımlayıcı 1'in dışında kalır - çünkü dosya tanımlayıcı 1'in biraz daha sonra ücretsiz olmasını istiyoruz, çünkü dosya tanımlayıcı 3'teki printf çıktısını dosya tanımlayıcıya geri getireceğiz 1 - Çünkü komut ikame işlemi (backticks) yakalanacak ve değişkene yerleştirilecek olan şey budur.

Son sihir, önce exec 4>&1ayrı bir komut olarak yaptığımız şeydir - dış tanımlayıcı stdout'unun bir kopyası olarak dosya tanımlayıcı 4'ü açar. Komut yerine koyma, standartlar üzerine yazılmış komutları, içindeki komutlar perspektifinden ele geçirir - ancak komut2'nin çıktısı, komut yerine koyma söz konusu olduğunda dosya tanımlayıcısına (4) gidecektir, ancak komut yerine koyma, onu yakalamaz. Komut ikame işleminden "çıktıktan" sonra, betiğin genel dosya tanımlayıcısı 1'e etkin bir şekilde devam ediyor.

( exec 4>&1Ayrı bir komut olması gerekir, çünkü birçok genel kabuk, bir komut değiştirmenin içindeki bir dosya tanımlayıcısına yazmayı denediğinizde, değiştirmeyi kullanan "harici" komutunda açılmasından hoşlanmaz. Bunu yapmanın en basit yolu.

Komutların çıktıları birbirinden sıçramış gibi: Daha az teknik ve daha eğlenceli bir şekilde bakabilirsiniz: command1 boruları command2'ye, sonra printf çıkışı komut 2'nin üzerine atlar, böylece komut2 onu yakalamaz ve sonra komut 2'nin çıktısı aynen ikame tarafından algılanmak üzere tam zamanında çıktıkça değişken iktidarında sona erecek ve komut2'nin çıktısı standart çıktısına yazıldığı gibi neşeli bir şekilde devam eder. normal bir boruda.

Ayrıca, anladığım kadarıyla, $?hala borudaki ikinci komutun dönüş kodunu içerecektir, çünkü değişken atamalar, komut değişimleri ve bileşik komutların hepsi içlerindeki komutun dönüş koduna etkili bir şekilde şeffaftır, bu nedenle dönüş durumu komut2'nin yayılması gerekir - bu ve ek bir işlev tanımlamak zorunda kalmamak, bunun lesmana tarafından önerilenden biraz daha iyi bir çözüm olabileceğini düşünüyorum.

Uyarılar lesmana'nın bahsettiği gibi, komut 1'in bir noktada 3 veya 4 dosya tanımlayıcıları kullanarak bitmesi muhtemeldir, bu nedenle daha sağlam olması için şunları yapmalısınız:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

Örneğimde bileşik komutlar kullandığımı unutmayın, ancak alt kabuklar ( ( )bunun yerine kullanmak { }da işe yarayacaktır, ancak daha az verimli olabilirler.)

Komutlar, dosya tanımlayıcılarını, onları başlatan işlemden devralır, böylece ikinci satırın tamamı dosya tanımlayıcı dört'ü miras alır ve ardından izlenen bileşik komut 3>&1dosya tanımlayıcı 3'ü miras alır. Böylece, 4>&-iç bileşik komutunun 3>&-dosya tanımlayıcı dört'ü miras almayacağından ve dosya tanımlayıcı üçünü miras almayacağından emin olunur, böylece komut1 daha temiz, daha standart bir ortam elde eder. Ayrıca içini 4>&-yanına da taşıyabilirsiniz 3>&-, ancak neden kapsamını mümkün olduğu kadar kısıtlamadığını anlıyorum.

Dosyaların üç ve dördüncü dosya tanımlayıcılarını ne sıklıkta kullandıklarından emin değilim - bence çoğu zaman kullanılmayan dosya tanımlayıcılarını döndüren programların çoğu zaman sistem tanımlayıcılarını döndüren sistemler kullanır, ancak bazen kod tanımlayıcı 3'e doğrudan dosya yazar. tahmin (açık olup olmadığını görmek için bir dosya tanımlayıcısını denetleyen ve açıksa kullanıp kullanmadığını veya açık değilse farklı davranan bir program hayal edebiliyorum). Bu nedenle, ikincisi genel amaçlı durumlar için akılda tutulması ve kullanılması muhtemeldir.


İlginç görünüyor, ancak bu komutun ne yapmasını beklediğinizi tam olarak bilemiyorum ve bilgisayarım da; Ben olsun -bash: 3: Bad file descriptor.
G-Man

@ G-Man Doğru, genellikle kullandığım mermilerin (meşgul kutusuyla birlikte gelen kül) aksine, dosya tanıtıcılarına gelince ne yaptığı hakkında hiçbir fikrim olmadığını unutmaya devam ediyorum. Sizi mutlu eden bir geçici çözüm düşündüğümde size haber vereceğim. Bu arada kullanışlı bir debian kutunuz varsa, çizgi üzerinde deneyebilirsiniz, veya meşgul kutunuz varsa, busy / ash / sh ile deneyebilirsiniz.
mtraceur

@ G-Man Komutun ne yapmasını beklediğime ve diğer kabuklarda ne yapacağına gelince, stdout komutunu 1. komuttan yeniden yönlendirir; fd3 tekrar stdout'a geri döndü, böylece komut2'ye beklendiği gibi yöneltildi. Komut1 çıktığında, printf komutun yerine koymasıyla değişkene yakalanan çıkış durumunu başlatır ve yazdırır. Burada çok ayrıntılı bir döküm: stackoverflow.com/questions/985876/tee-and-exit-status/… Ayrıca, yorumunuz biraz hakarete yol açıyormuş gibi okunuyor mu?
mtraceur

Nereden başlayayım? (1) Hakaret ettiğiniz için üzgünüm. “İlginç görünüyor” ciddiyetle ifade edildi; Bu kadar kompakt bir şey beklediğiniz gibi çalıştığı gibi eğer harika olurdu. Bunun ötesinde, basitçe, çözümünüzün ne yapması gerektiğini anlamadığımı söyledim. Unix ile uzun zamandır çalışıyorum / oynuyorum (Linux'un varlığından beri) ve bir şeyi anlamıyorsam, belki de başkalarının anlayamayacağı bir bayrak. daha fazla açıklama gerekiyor (IMNSHO). … (Devamı)
G-Man

(Devamı) ... yana size çalkalama, “... [sen] sadece ortalama kişi daha her şey anladığımı ... düşünmek istiyorum” belki Yığın Borsası amacı bir komut yazma hizmeti olmak olmadığını unutmamak gerekir önemsiz biçimde farklı sorulara yönelik binlerce birebir çözümden kurtulma; ama insanlara kendi sorunlarını nasıl çözeceklerini öğretmek yerine. Ve bu amaçla, belki “ortalama bir insanın” anlayabileceği şeyleri yeterince iyi açıklamanız gerekir. Mükemmel bir açıklama örneği için lesmana'nın cevabına bakın. … (Devamı)
G-Man


7

lesmana'nın yukarıdaki çözümü, { .. }bunun yerine iç içe geçmiş alt işlemlerin ek yükü olmadan da yapılabilir (bu gruplandırılmış komutların her zaman noktalı virgülle bitmesi gerektiğini unutmayın). Bunun gibi bir şey:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

Ben bu yapı çizgi sürüm 0.5.5 ve bash sürüm 3.2.25 ve 4.2.42 ile kontrol ettim, bu nedenle bazı mermiler { .. }gruplandırmayı desteklemese bile, hala POSIX uyumludur.


NetBSD sh, pdksh, mksh, dash, bash dahil olmak üzere, denediğim çoğu mermi ile gerçekten iyi çalışıyor. Ancak AT&T Ksh (93s +, 93u +) veya zsh (4.3.9, 5.2) ile çalışmasını set -o pipefail, ksh veya herhangi bir sayıdaki serpilmiş waitkomutla bile çalışmasını sağlayamıyorum . Sanırım en azından, ksh için ayrıştırma sorunu olabilir, sanki alt kabukları kullanmaya devam edersem daha sonra düzgün çalışır, ancak bir ifksh için alt kabı türevini seçmekle birlikte, ancak diğerleri için bileşik komutları bırakarak başarısız olur .
Greg A. Woods,

4

Bu taşınabilirdir, yani herhangi bir POSIX uyumlu kabukla çalışır, geçerli dizinin yazılabilir olmasını gerektirmez ve aynı hile kullanan birden çok komut dosyasının aynı anda çalışmasına izin verir.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

Düzenleme: Burada Gilles 'yorumlarını takip eden daha güçlü bir sürüm:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2: ve burada dubiousjim yorumunu takiben biraz daha hafif bir değişken:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))

3
Bu birkaç nedenden dolayı işe yaramıyor. 1. Geçici dosya yazılmadan önce okunabilir. 2. Öngörülebilir bir isimle paylaşılan bir dizinde geçici bir dosya oluşturmak güvensizdir (önemsiz DoS, sembolik yarış). 3. Aynı komut dosyası bu numarayı birkaç kez kullanıyorsa, her zaman aynı dosya adını kullanır. 1'i çözmek için, boru hattı tamamlandıktan sonra dosyayı okuyun. 2 ve 3'ü çözmek için, rastgele oluşturulmuş bir adla veya özel bir dizinde geçici bir dosya kullanın.
Gilles

+1 $ {PIPESTATUS [0]} daha kolay, ancak Gilles'un bahsettiği sorunları bilen kişi buradaki temel fikir işe yarıyor.
Johan

Birkaç altkabuklarda kaydedebilirsiniz: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Bash ile daha kolay olduğuna katılıyorum, ancak bazı durumlarda Bash'in nasıl önleneceğini bilmek de buna değer.
dubiousjim

4

Yaygın çözümlerden birini kullanamamanız durumunda, @Patrik'in cevabına bir eklenti olarak atıfta bulunulmaktadır.

Bu cevap aşağıdakileri varsaymaktadır:

  • Sen bilmeyen bir kabuğu vardır $PIPESTATUSne deset -o pipefail
  • Paralel yürütme için bir boru kullanmak istiyorsunuz, bu yüzden geçici dosyalar yok.
  • Senaryoyu bölerseniz, muhtemelen ani bir elektrik kesintisi ile ek dağınıklığı istemezsiniz.
  • Bu çözümün okunması kolay ve okunması kolay olmalı.
  • Ek alt kabuklar tanıtmak istemezsiniz.
  • Mevcut dosya tanımlayıcılarını çözemezsiniz, bu nedenle stdin / out / err öğesine dokunulmamalıdır (ancak geçici olarak yeni bir tanesini tanıtabilirsiniz).

Ek varsayımlar. Hepsinden kurtulabilirsiniz, ancak bu tarifleri çok fazla püskürtür, bu yüzden burada ele alınmaz:

  • Bilmek istediğiniz tek şey PIPE'deki tüm komutların 0 çıkış koduna sahip olmasıdır.
  • Ek yan bant bilgisine ihtiyacınız yoktur.
  • Kabuğunuz tüm boru komutlarının geri dönmesini bekler.

Önce: foo | bar | bazAncak bu yalnızca son komutun ( baz) çıkış kodunu döndürür.

Aranıyor: borudaki komutlardan herhangi biri başarısız olursa, (doğru) $?olmamalıdır0

Sonra:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

# $? now is 0 only if all commands had exit code 0

Açıklaması:

  • İle bir tempfile oluşturulur mktemp. Bu genellikle hemen bir dosya oluşturur./tmp
  • Bu geçici dosya daha sonra yazma için FD 9'a ve okuma için FD 8'e yönlendirilir.
  • Sonra tempfile hemen silinir. Yine de, her iki FD'nin varlığı geçene kadar açık kalır.
  • Şimdi boru başlatıldı. Her adım, yalnızca bir hata olması durumunda FD 9'a eklenir.
  • Bunun waitiçin gerekli ksh, çünkü kshbaşka tüm komutların bitmesini beklemiyor. Ancak, bazı arka plan görevleri varsa istenmeyen yan etkilerin olduğunu lütfen unutmayın, bu nedenle varsayılan olarak yorumladım. Bekleme zarar vermezse, yorum yapabilirsiniz.
  • Daha sonra dosyanın içeriği okunur. O boşsa (bütün yaradıkları için) readdöner false, bu nedenle truebir hata olduğunu belirtir

Bu tek bir komut için eklenti yerine kullanılabilir ve sadece aşağıdakileri gerektirir:

  • Kullanılmayan FD 9 ve 8
  • Tempfile adını tutmak için tek bir ortam değişkeni
  • Ve bu tarif, IO yönlendirmesine izin veren herhangi bir kabuğa oldukça uyarlanabilir.
  • Ayrıca oldukça platform agnostik ve gibi şeyler gerekmez /proc/fd/N

Bug:

Bu komut dosyasında /tmpboş alan kalmazsa bir hata var . Bu yapay duruma karşı da korunmaya ihtiyacınız varsa, bunu aşağıdaki gibi yapabilirsiniz, ancak bunun dezavantajı vardır; bu, giriş sayısının , borudaki komut sayısına bağlı 0olmasına 000bağlı olarak biraz daha karmaşıktır:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

Taşınabilirlik notları:

  • kshve sadece son boru komut bekle benzer kabuklar ihtiyaç waituncommented

  • Son örnek printf "%1s" "$?"bunun yerine echo -n "$?"daha taşınabilir olduğundan bunun yerine kullanır . Her platform -ndoğru yorum yapmıyor.

  • printf "$?"aynı zamanda printf "%1s"senaryoyu gerçekten kırılmış bir platformda çalıştırmanız durumunda bazı köşe davalarını da yakalar. (Okuyun: programlamaya devam ederseniz paranoia_mode=extreme)

  • FD 8 ve FD 9, birden çok basamağı destekleyen platformlarda daha yüksek olabilir. AFAIR POSIX uyumlu bir kabuğun yalnızca tek basamakları desteklemesi gerekir.

  • Debian 8.2 ile test edildi sh, bash, ksh, ash, sashve hattacsh


3

Biraz önlem ile, bu çalışması gerekir:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)

Jlliagre gibi temizlik yapmaya ne dersin? Foo-status adında bir dosya bırakmıyor musunuz?
Johan

@Johan: Önerimi tercih ederseniz, oy kullanmaktan çekinmeyin;) Bir dosya bırakmaktan başka, birden fazla işlemin aynı anda çalışmasına izin verme avantajına sahiptir ve mevcut dizinin yazılabilir olması gerekmez.
jlliagre

2

Aşağıdaki 'if' bloğu yalnızca 'komut' başarılı olduğunda çalışır:

if command; then
   # ...
fi

Özellikle konuşursak, şöyle bir şey çalıştırabilirsin:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

Hangi haconf -makerwstdout ve stderr 'i "$ haconf_out"' a koyacak ve saklayacaktır. Döndürülen değer haconftrue ise, o zaman 'if' bloğu çalıştırılacak ve grep"$ haconf_out" u okuyacak, "Küme zaten yazılabilir" ile eşleştirmeye çalışacaktır.

Borular otomatik olarak kendilerini temizler dikkat edin; yönlendirme ile "$ haconf_out" işlemi bittiğinde kaldırmak için dikkatli olmalısınız.

pipefailBu işlevsellik elinizin altında değilse, o kadar zarif değil , ama meşru bir alternatif.


1
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$

0

(En azından bash ile) set -ebir taneyle açık bir şekilde boru arızasını taklit etmek ve boru hatasından çıkmak için alt kabuk kullanabilir

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

Bu foonedenle, bir nedenden ötürü başarısız olursa - programın geri kalanı yürütülmez ve komut dosyası ilgili hata koduyla çıkar. (Bu foo, hatanın nedenini anlamak için yeterli olan kendi hatasını yazdırdığını varsayar )


-1

EDIT : Bu cevap yanlış, ama ilginç, bu yüzden ileride başvurmak için bırakacağım.


!Komuta a eklemek dönüş kodunu tersine çevirir.

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command     # bash: bogus_command: command not found
echo $?                # 127

! ls | bogus_command   # bash: bogus_command: command not found
echo $?                # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #

1
Bunun alakasız olduğunu düşünüyorum. Örnekte, çıkış kodunu bilmek istiyorum, çıkış kodunu lstersine çevirmek istemiyorumbogus_command
Michael Mrozek

2
Bu cevabı geri çekmeyi öneriyorum.
maxschlepzig

3
Görünüşe göre ben bir aptalım. Aslında OP'nin istediğini yaptığını düşünmeden önce bir senaryoda kullandım. İyi bir şey, önemli bir şey için kullanmadım
Falmarri
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.