Set -eu `kullanılırken EXIT ve ERR tuzaklarının doğru davranışı


27

ERR ve EXIT tuzakları ile birlikte set -e( errexit), set -u( nounset) kullanırken bazı garip davranışlar gözlemliyorum . İlgili gözüküyorlar, bu yüzden onları bir soruya koymak mantıklı görünüyor.

1) set -uERR tuzaklarını tetiklemez

  • Kod:

    #!/bin/bash
    trap 'echo "ERR (rc: $?)"' ERR
    set -u
    echo ${UNSET_VAR}
  • Beklenen: ERR tuzağı çağrılıyor, RC! = 0
  • Fiili: ERR tuzağı olduğu değil RC == 1 olarak adlandırılan
  • Not: set -esonucu değiştirmez

2) set -euBir EXIT tuzağında çıkış kodunu kullanmak 1 yerine 0

  • Kod:

    #!/bin/bash
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
  • Beklenen: EXIT tuzağı aranıyor, RC == 1
  • Gerçek: EXIT tuzağı denir, RC == 0
  • Not: Kullanırken set +e, RC == 1. Başka bir komut bir hata attığında, EXIT tuzağı uygun RC'yi döndürür.
  • Düzenleme: Bu konuda, SO'nun kullanılan Bash sürümüyle ilgili olabileceğini düşündüren ilginç bir yorum içeren bir SO yazısı var . Bu pasajı Bash 4.3.11 ile test etmek bir RC = 1 olur, bu yüzden daha iyidir. Maalesef Bash'in (3.2.51'den) tüm ana bilgisayarlarda yükseltilmesi şu anda mümkün değil, bu yüzden başka bir çözüm bulmalıyız.

Bu davranışlardan herhangi birini açıklayan var mı?

Bu konuların araştırılması çok başarılı olmadı, bu, Bash ayarları ve tuzakları üzerindeki yayınların sayısının verilmesi şaşırtıcıydı. Yine de bir forum konusu var , ancak sonuç oldukça tatmin edici değil.


3
Sanırım 4'ten itibaren bashstandarttan ayrıldı ve alt kabuklara tuzaklar koymaya başladım. Tuzağın geri döndüğü andan itibaren aynı ortamda yürütülmesi gerekiyordu, ancak bashbunu bir süredir yapmadı.
mikeserv

1
Bir dakika bekle - bir çözüm mü yoksa bir açıklama mı istiyorsun? Ve eğer bir çözüm istiyorsan, tam olarak ne için bir çözüm? Ne olmak istiyorsun? set -eve set -uher ikisi de özel olarak yazılmış bir kabuğunu öldürmek için tasarlanmıştır . Bunları, uygulamalarını tetikleyebilecek koşullarda kullanmak , komut dosyası yazılan bir kabuğu öldürür . Hiçbir haricinde bu kaçış yoktur değil bunun yerine bunları kullanmak ve testin bir kod sırayla uyguladığınızda bu koşullar için. Yani, temel olarak, iyi bir kabuk kodu yazabilir veya kullanabilirsiniz set -eu.
mikeserv

2
Aslında, neden -uERR tuzağını tetiklemeyeceğine dair yeterli bilgi bulamadığım için her ikisini de arıyorum (bu bir hata, bu yüzden tuzağı tetiklememeli) veya hata kodu 1 yerine 0'dır. İkincisi daha sonraki sürümde zaten düzeltilmiş olan bir hata gibi görünüyor, bu yüzden. Ancak, kabuk değerlendirmedeki hataların (parametre genişlemesi) ve komutlardaki gerçek hataların iki farklı şey gibi göründüğünü anlamadıysanız, ilk bölümün anlaşılması oldukça zordur. Çözüm için, önerdiğiniz gibi, artık -eugerektiğinde manuel olarak kontrol etmekten kaçınmaya çalışıyorum .
dvdgsng

1
@dvdsng - Güzel. Gideceğiniz yol budur - senaryoyu cevap olarak yazıp kendinize ödül vermelisiniz. Bu seçenekleri gerçekten beğenmedim - istisnaların güvenli bir şekilde ele alınmasına izin vermiyorlar.
mikeserv

1
@dvdsng - bu seçeneklerden herhangi birinin yararlı olabileceği yer olsa da, altyazı bağlamındadır. Ve böylece onları daha önce kullanmak için kullandığınız şeyin aşağıdaki gibi bir alt kabuk bağlamında yerelleştirilebileceği düşünülebilir (set -u; : $UNSET_VAR). Bu tür şeyler de iyi olabilir - &&ara sıra çok şey düşürebilirsiniz : (set -e; mkdir dir; cd dir; touch dirfile)sürüklenmemi yakalarsanız. Sadece bunlar kontrol edilen bağlamlardır - onları global seçenek olarak ayarladığınızda kontrolü kaybedersiniz ve kontrol edilirsiniz. Yine de, genellikle daha verimli çözümler vardır.
mikeserv

Yanıtlar:


15

Kimden man bash:

  • set -u
    • Belirsiz değişkenleri ve parametreleri, özel parametreler dışında "@"ve "*"parametre genişletme işlemini yaparken bir hata olarak kabul edin. -iBelirsiz bir değişkende veya parametrede genişleme girişiminde bulunulursa, kabuk bir hata mesajı yazdırır ve etkileyici değilse sıfır olmayan bir durumdan çıkar.

POSIX bir durumunda, bildiren genleşme hatası , bir etkileşimsiz kabuk çıkmak zorundadır genişletme bir kabuk özel bir yerleşiği ya ile ilişkili zaman (bir ayrım olduğu bashdüzenli yine göz ardı eder ve böylece belki de değildir) ya da herhangi bir diğer yardımcı yanında .

  • Shell Hatalarının Sonuçları :
    • Bir genişleme hata tanımlanan kabuk açılımları oluşur biridir Kelime Expansions gerçekleştirilmektedir (örneğin, "${x!y}"çünkü, !geçerli bir operatör değildir) ; bir uygulama , genişletme sırasında değil de, simge belirleme sırasında tespit edebiliyorsa bunları sözdizimi hataları olarak değerlendirebilir.
    • [A] n etkileşimli kabuk çıkmadan standart hataya bir teşhis mesajı yazacaktır.

Ayrıca man bash:

  • trap ... ERR
    • Bir sigspec ise ERR komut arg bir boru hattı zaman çalıştırılır (tek bir basit bir komut meydana gelebilen) , bir liste ya da bir bileşik, komutu aşağıdaki koşullara sıfır olmayan bir çıkış durumu, konu verir:
      • ERR başarısız komut hemen aşağıdaki komutu listenin bir parçası ise tuzak yürütülmez whileveya untilanahtar kelime ...
      • ... bir ififadede testin bir parçası ...
      • ... finalden sonraki komut hariç bir &&veya ||listede gerçekleştirilen komutun bir parçası &&veya ||...
      • ... bir boru hattındaki herhangi bir komut ama sonuncusu ...
      • ... veya komutun dönüş değeri kullanılarak ters çevriliyorsa !.
    • Bunlar errexit -e seçeneğinin uyduğu şartlar .

ERR tuzağının tamamen başka bir komutun geri dönüşünün değerlendirilmesi ile ilgili olduğuna dikkat edin . Ancak bir genişletme hatası oluştuğunda, hiçbir şey döndürecek bir komut çalıştırılmaz. Örneğinizde , echo asla gerçekleşmez - çünkü kabuk argümanlarını değerlendirir ve genişletir, -uancak mevcut, komut dosyasıyla kabuktan hemen çıkışa neden olmak için açık bir kabuk seçeneği tarafından belirtilen bir nset değişkeni ile karşılaşır .

Ve böylece , eğer varsa , EXIT tuzağı çalıştırılır ve kabuk, 0'dan farklı bir teşhis mesajı ve çıkış durumu ile çıkar - aynen olması gerektiği gibi.

Rc: 0'a gelince , bunun bir tür versiyona özgü bir hata olmasını bekliyorum - muhtemelen aynı anda gerçekleşen EXIT için iki tetikleyiciyle ve diğerinin çıkış kodunu (gerçekleşmemesi gereken) alıyor . Ve yine de, bashtarafından yüklendiği şekliyle güncel bir ikili dosya ile pacman:

bash <<\IN
    printf "shell options:\t$-\n"
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
IN

İlk satırı ekledim, böylece kabuğun koşullarının bir komut dosyası kabuğununki olduğunu görebilirsiniz - etkileşimli değildir . Çıktı:

shell options:  hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)

En son değişikliklerden bazı önemli notlar :

  • Eşzamansız komutların $?doğru ayarlanmamasına neden olan bir hata düzeltildi .
  • Tarafından üretilen neden hata iletileri bu bir hata düzeltildi genleşme hataları içinde forkomutları yanlış hat numarasına sahip olacak.
  • Kaynaklanan bir hata düzeltildi SIGINT ve SIGQUIT sinyalini olmamak için trapyüksek verim sağlar yılında asenkron altkabuk komutlar.
  • İkinci ve daha sonraki SIGINT'in etkileşimli mermiler tarafından göz ardı edilmesine neden olan kesinti işleme sorunu giderildi .
  • Kabuk artık trapbu sinyallerin işleyicilerini çalıştırırken sinyal alımını engellemiyor ve çoğu trap işleyicinin yinelemeli olarak çalışmasına izin veriyor ( trapbir trapişleyici çalışırken devam eden işleyicileri) .

Sanırım en alakalı ya da son olan ya da ikisinin birleşimidir. Bir trapişleyici , doğası gereği asenkrondir, çünkü tüm işi asenkron sinyalleri beklemek ve kullanmaktır . Ve ikisini aynı anda -euve ile tetiklersiniz $UNSET_VAR.

Ve belki de sadece güncellemelisin, ama kendini seviyorsan, tamamen farklı bir kabukla yapacaksın.


Parametre genişlemesinin nasıl farklı şekilde ele alındığına dair açıklama için teşekkürler. Bu bana bir çok şeyi temizledi.
dvdgsng

Size ödül veriyorum çünkü açıklamanız çok yardımcı oldu.
dvdgsng

@dvdgsng - Gracias. Meraktan, çözümünle hiç geldin mi?
mikeserv

9

(Bash 4.2.53 kullanıyorum). Bölüm 1 için, bash man sayfası sadece "standart hataya bir hata mesajı yazacak ve etkileşimli olmayan bir kabuk çıkacak" der. Bir ERR tuzağının aranacağını söylemedi, eğer yapması faydalı olacağını kabul etmeme rağmen.

Pragmatik olmak gerekirse, gerçekten istediğiniz tanımsız değişkenlerle daha net bir şekilde başa çıkmaksa, kodunuzun çoğunu bir işlevin içine koymak, sonra da bu işlevi bir alt kabukta çalıştırmak ve dönüş kodunu ve stderr çıktısını kurtarmaktır. İşte fonksiyonun "cmd ()" olduğu bir örnek:

#!/bin/bash
trap 'rc=$?; echo "ERR at line ${LINENO} (rc: $rc)"; exit $rc' ERR
trap 'rc=$?; echo "EXIT (rc: $rc)"; exit $rc' EXIT
set -u
set -E # export trap to functions

cmd(){
 echo "args=$*"
 echo ${UNSET_VAR}
 echo hello
}
oops(){
 rc=$?
 echo "$@"
 return $rc # provoke ERR trap
}

exec 3>&1 # copy stdin to use in $()
if output=$(cmd "$@" 2>&1 >&3) # collect stderr, not stdout 
then    echo ok
else    oops "fail: $output"
fi

Benim bash ben alıyorum

./script my stuff; echo "exit was $?"
args=my stuff
fail: ./script: line 9: UNSET_VAR: unbound variable
ERR at line 15 (rc: 1)
EXIT (rc: 1)
exit was 1

güzel, aslında değer katan pratik bir çözüm!
Florian Heigl
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.