Neden (1. çıkış) komut dosyasından çıkmıyor?


47

İstediğimde çıkmayan bir betiğim var.

Aynı hatayı içeren bir örnek komut dosyası:

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

echo '2'

Çıktıyı görmeyi varsayardım:

:~$ ./test.sh
1
:~$

Ama aslında görüyorum ki:

:~$ ./test.sh
1
2
:~$

Does ()nasılsa zincirleme komut, kapsamı oluşturmak? exitSenaryo değilse, ne çıkacak?


5
Bu tek bir kelime cevabını çağırıyor: deniz kabuğu
Joshua

Yanıtlar:


87

()subshell komutları çalıştırır, bu yüzden subshell'den exitçıkıp ana kabuğa geri dönüyorsunuz. {}Geçerli kabuktaki komutları çalıştırmak istiyorsanız kaşlı ayraçları kullanın .

Bash el kitabından:

(list) liste alt kabuk ortamında yürütülür. Kabuğun ortamını etkileyen değişken atamaları ve yerleşik komutlar, komut tamamlandıktan sonra geçerli olmaz. İade durumu listenin çıkış durumudur.

{ liste; } liste sadece geçerli kabuk ortamında yürütülür. liste bir yeni satır veya noktalı virgül ile sonlandırılmalıdır. Bu bir grup komutu olarak bilinir. İade durumu listenin çıkış durumudur. Meta karakterlerden (ve) farklı olarak, {ve} sözcüklerinin ayrılmış kelimeler olduğunu ve ayrılmış bir sözcüğün tanınmasına izin verilen yerlerde olması gerektiğini unutmayın. Kelime molalarına neden olmadıkları için listeden boşlukla veya başka bir kabuk meta karakteriyle ayrılmaları gerekir.

Kabuk sözdiziminin oldukça tutarlı olduğunu ve alt kabuğun da ()komut değiştirme (eski stil `..`sözdizimiyle) ve işlem değiştirme gibi diğer yapılara da katıldığından bahsetmekte fayda var;

echo $(exit)
cat <(exit)

Komutlar açıkça yerleştirildiğinde alt kabukların yer aldığı açık olsa da (), daha az görünen gerçek şu ki bu diğer yapılara da yerleştirilmiş olmaları:

  • komut arka planda başladı

    exit &

    Geçerli kabuktan çıkmıyor çünkü (sonra man bash)

    Bir komut kontrol operatörü & tarafından sonlandırılırsa, kabuk komutu bir alt kabukta arka planda yürütür. Kabuk komutun bitmesini beklemez ve dönüş durumu 0'dır.

  • boru hattı

    exit | echo foo

    hala sadece alt kabuktan çıkar.

    Bununla birlikte, farklı mermiler bu konuda farklı davranır. Örneğin bash, boru hattının tüm bileşenlerini ayrı alt kabuklara koyar ( lastpipeiş denetiminin etkinleştirilmediği durumlarda seçeneği kullanmadığınız sürece ), ancak AT&T kshve zshmevcut kabuğun içindeki son kısmı çalıştırın (her iki davranışa da POSIX tarafından izin verilir). Böylece

    exit | exit | exit

    temelde bash'ta hiçbir şey yapmaz, fakat sonuncusu nedeniyle zsh'den çıkar exit.

  • coproc exitayrıca exitbir deniz kabuğu içinde çalışır .


5
Ah. Şimdi selefimin yanlış parantez kullandığı tüm yerleri bulmaya. İçgörü için teşekkürler.
Minix

10
Adam sayfasında aralık dikkatli not alın: {ve }onlar kelimeleri ayrılmıştır ve boşluklarla çevrili olması ve liste komut terminatör bitmelidir (noktalı virgül, satır, ve işareti), sözdizimi değildir
Glenn Jackman

İlgilenilmeyen, bu aslında başka bir işlem veya bir iç yığında ayrı bir ortam olma eğiliminde midir? Chdir'leri izole etmek için () çok kullanırım ve eski ise $$ etc kullandığım için şanslı olmalıyım.
Dan Sheppard

5
@DanSheppard Başka bir işlemdir ancak (echo $$)alt kabuk $$oluşturulmadan önce genişletildiğinden dolayı ana kabuk kimliğini yazdırır . Aslında, alt kabuk işlem kimliğini yazdırmak zor olabilir, bkz. Stackoverflow.com/questions/9119885/…
jimmij

@jimmij, Alt $$kabuk oluşturulmadan önce bu nasıl genişletilebilir ve $BASHPIDbir alt kabuk için doğru değeri nasıl gösterebilir ?
Joker

13

Yürütme exitbir kabuktaki bir tuzak olduğunu:

#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)

Komut dosyası 42 yazdırır, alt kabuktan dönüş koduyla çıkar 1ve komut dosyasıyla devam eder. echo $(CALC) || exit 1Geri dönüş kodu, geri dönüş kodundan echobağımsız olarak 0 olduğundan , aramanın değiştirilmesi bile yardımcı olmaz calc. Ve calcönce idam edilir echo.

Daha da şaşırtıcı olanı , aşağıdaki senaryoda olduğu gibi yerleşik yapıya exitsarılarak etkisini engelliyor local. Bir giriş değerini doğrulamak için bir fonksiyon yazdığımda problemin üstesinden geldim. Örnek:

20141211.logBugün "year month day.log" adlı bir dosya oluşturmak istiyorum . Tarih, makul bir değer sağlayamayan bir kullanıcı tarafından girilir. Bu nedenle, benim fonksiyonumda kullanıcı girişinin geçerliliğini doğrulamak için fnamedönüş değerini kontrol ediyorum date:

#!/bin/bash

doit ()
    {
    local FNAME=$(fname "$1") || exit 1
    touch "${FNAME}"
    }

fname ()
    {
    date +"%Y%m%d.log" -d"$1" 2>/dev/null
    if [ "$?" != 0 ] ; then
        echo "fname reports \"Illegal Date\"" >&2
        exit 1
    fi
    }

doit "$1"

İyi görünüyor. Komut dosyasını adlandırın s.sh. Kullanıcı betiği ile çağırırsa ./s.sh "Thu Dec 11 20:45:49 CET 2014", dosya 20141211.logoluşturulur. Bununla birlikte, kullanıcı yazarsa ./s.sh "Thu hec 11 20:45:49 CET 2014", komut dosyası çıktısını alır:

fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory

Satır fname…, alt kabukta hatalı giriş verilerinin tespit edildiğini söylüyor. Fakat çizginin exit 1sonunda local …asla tetiklenmez, çünkü localdirektif daima geri döner 0. Bunun nedeni local, daha sonra çalıştırılmasının $(fname)ve dönüş kodunun üzerine yazmasının nedenidir. Ve bu nedenle, komut dosyası devam eder ve touchboş bir parametre ile çağırır . Bu örnek basittir ancak bash'ın davranışı gerçek bir uygulamada oldukça kafa karıştırıcı olabilir. Biliyorum, gerçek programcılar locals kullanmıyor.

localAçıklığa kavuşturmak için: Komut dosyası olmadan geçersiz bir tarih girildiğinde komut dosyası beklendiği gibi iptal edilir.

Düzeltme çizgiyi gibi bölmektir

local FNAME
FNAME=$(fname "$1") || exit 1

Garip davranış, bash'ın localman sayfasındaki belgelere uyuyor : "Bir fonksiyon dışında yerel kullanılmadığı, geçersiz bir ad sağlandığı veya isim salt okunur bir değişken olmadığı sürece dönüş durumu 0'dır."

Hata olmamasına rağmen, bash'ın davranışının tersine dönük olduğunu hissediyorum. İnfazın sırasının farkındayım local, yine de kesilmiş bir ödevi maskelememeliyim.

İlk cevabım bazı yanlışlıklar içeriyordu. Mikeserv ile yapılan açıklayıcı ve derinlemesine bir tartışmadan sonra (bunun için teşekkür ederim) Onları tamir etmeye gittim.


@mikeserv: İlgililiği göstermek için bir örnek ekledim.
Hermannk

@mikeserv: Evet, haklısın. Daha da kötüsü bile. Ancak tuzak hala orada.
hermannk

@mikeserv: Üzgünüm, örneğim kırıldı. Testi unuttum doit().
Hermannk


1

Parantezler bir alt kabuk başlatır ve çıkış yalnızca bu alt kabuktan çıkar.

Çıkış kodunu birlikte okuyabilir ve alt kabuk çıkmışsa $?komut dosyasından çıkmak için betiğinize ekleyebilirsiniz:

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi

echo '2'

1

Gerçek çözüm:

#!/bin/bash

function bla() {
    return 1
}

bla || { echo '1'; exit 1; }

echo '2'

Hata gruplaması yalnızca blabir hata durumu döndürdüğünde yürütülür exitve tüm betiğin durması için bir alt kabukta bulunmaz.

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.