bash: süreç ikamesinde hatalar nasıl yayılır?


19

Onlarla yürütülen bir komut başarısız olduğunda kabuk komut dosyalarımın başarısız olmasını istiyorum.

Genellikle ben ile yapmak:

set -e
set -o pipefail

(genellikle ekliyorum set -u)

Mesele, yukarıdakilerin hiçbirinin süreç ikamesi ile çalışmadığıdır. Bu kod "Tamam" yazdırır ve başarısız olmasını istiyorum iken, dönüş kodu = 0 ile çıkmak:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

Proses ikamesi için "boru arızası" ile eşdeğer bir şey var mı? Komutların çıktısını dosya oldukları gibi bir komuta iletmenin başka bir yolu var, ancak bu programlardan herhangi biri başarısız olduğunda bir hata mı oluşturuyor?

Fakirlerin insan çözümü, bu komutların stderr'e yazıp yazmadığını tespit eder (ancak bazı komutlar başarılı senaryolarda stderr'e yazar).

Başka bir daha posix uyumlu çözüm adlandırılmış borular kullanarak olurdu, ama derlenmiş koddan anında oneliners olarak inşa edilen bu komutlar-bu-kullanım-süreç-yerine koyma lauch gerekir ve adlandırılmış borular oluşturmak şeyler karmaşık (ekstra komutlar, bindirme hatası silme, vb.)


Adlandırılmış yöneltmeleri silmek için hataları yakalamanıza gerek yoktur. Aslında, bu hiç de iyi bir yol değil. Düşünün mkfifo pipe; { rm pipe; cat <file; } >pipe. Bu komut, bir okuyucu açılana kadar askıda kalacaktır pipeçünküopen() ve bu nedenle en kısa sürede bir okuyucu olarak orada pipefs için bağlantı pipeolduğunu rm'd ve sonra catkopyaları bu boru kabuğun tanımlayıcısı dışarı infile. Her neyse, bir işlem : <( ! : || kill -2 "$$")
altından

Adlandırılmış kanalların silinmesine ilişkin ipucu için teşekkürler. Ne yazık ki, $$ikame benim için çalışmaz, çünkü bu komut ikamesi, işlem ikamesi kullanan komut olarak yapılmaz, "kabuk olmayan" bir koddan (python) üretilen bir komut satırı içinde yapılır. Muhtemelen python'da alt süreç oluşturmalı ve onları programlı olarak yönlendirmeliyim.
juanleon

Öyleyse kullan kill -2 0.
mikeserv

Ne yazık ki "kill -2 0" pitonu öldürür (sinyal). Çok iş parçacıklı bir uygulamada iş mantığı gerçekleştirmek için sinyal işleyicileri yazmak, beklediğim bir şey değil :-)
juanleon

Eğer sinyallerle uğraşmak istemiyorsanız, neden bunları almaya çalışıyorsunuz? Her neyse, adlandırılmış boruların sonunda en basit çözüm olacağını umuyorum, eğer sadece doğru yapmak kendi çerçevenizi oluşturmayı ve kendi özel sevk görevlinizi kurmayı gerektiriyorsa. Bu engeli aş ve her şey bir araya geliyor.
mikeserv

Yanıtlar:


6

Yalnızca bu soruna geçici bir çözüm bulabilirsiniz:

cat <(false || kill $$) <(echo ok)
other_command

Komut dosyasının alt kabuğu SIGTERM, ikinci komut yürütülmeden önce d'dir ( other_command). echo okKomut "bazen" yürütülür: Sorun süreç değiştirmelerin asenkron olmasıdır. Hiçbir garantisi yoktur kill $$komut yürütülür önce veya sonraecho ok komuta. Bu, işletim sistemlerinin planlanmasıyla ilgilidir.

Şöyle bir bash betiği düşünün:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

Bu komut dosyasının çıktısı şunlar olabilir:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

Veya:

$ ./script
Terminated
$ pre
post

$ echo $?
143

Deneyin ve birkaç denemeden sonra çıktıda iki farklı sipariş göreceksiniz. İlkinde komut dosyası, diğer iki echokomutun dosya tanımlayıcısına yazabilmesi için sonlandırıldı . İkincisinde falseveya killkomutu muhtemelen echokomutlardan sonra planlanmıştır .

Daha doğrusu: Sistem çağrısı signal() ait killgönderir utillity SIGTERMkabukları sürece sinyal geç yapılması veya yankı daha erken planlandı (veya teslim edildi) write()sistem çağrıları.

Ancak komut dosyası durur ve çıkış kodu 0 değildir. Bu nedenle sorununuzu çözmelidir.

Başka bir çözüm , elbette, bunun için adlandırılmış borular kullanmaktır. Ancak, komut dosyasına, adlandırılmış yöneltmeleri veya yukarıdaki geçici çözümü uygulamanın ne kadar karmaşık olacağına bağlıdır.

Referanslar:


2

Kayıt için ve cevaplar ve yorumlar iyi ve yararlı olsa bile, biraz farklı bir şey uygulamaya son verdim (ana süreçte soruda bahsetmediğim sinyalleri alma konusunda bazı kısıtlamalar vardı)

Temel olarak, böyle bir şey yapmaya son verdim:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Sonra hata dosyasını kontrol ediyorum. Varsa, hangi alt komutun başarısız olduğunu biliyorum (ve error_file içeriği yararlı olabilir). Başlangıçta istediğim daha ayrıntılı ve hileli, ama tek katmanlı bir bash kommandında adlandırılmış borular oluşturmaktan daha az hantal.


Sizin için çalışan her şey genellikle en iyi yoldur. Özellikle geri gelip cevap verdiğiniz için teşekkürler - selfies benim favorilerim.
mikeserv

2

Bu örnekte killbirlikte nasıl kullanılacağı gösterilmektedir trap.

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

Ancak kill, alt işleminizden üst sürece bir dönüş kodu iletemezsiniz.


Kabul edilen cevaptaki bu varyasyonu beğendim
sehe

1

Eğer gibi benzer şekilde uygulamak $PIPESTATUS/ $pipestatuso desteklemeyen bir POSIX kabuğu ile , bir boru aracılığıyla onları etrafında geçerek komutların çıkış durumu edinebilirsiniz:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

Hangi verir:

ok
cat_code=0
false_code=1
echo_code=0

Veya pipefailsüreç ikamesini, onu desteklemeyen mermiler gibi elinizle kullanabilir ve uygulayabilirsiniz:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
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.