Bash betiğinde, belirli bir koşul oluştuğunda betiğin tamamından nasıl çıkabilirim?


719

Bash bazı kodları test etmek için bir senaryo yazıyorum. Ancak, kod derleme ilk etapta başarısız olursa testleri çalıştırmak aptalca görünüyor, bu durumda testleri iptal edeceğim.

Tüm komut dosyasını bir while döngüsünün içine sarmadan ve sonları kullanmadan yapabileceğim bir yol var mı? Bir dun dun dun goto gibi bir şey mi?

Yanıtlar:


813

Bu ifadeyi deneyin:

exit 1

1Uygun hata kodlarıyla değiştirin . Ayrıca bkz . Özel Anlamlara Sahip Çıkış Kodları .


4
@CMCDragonkai, genellikle sıfır olmayan herhangi bir kod çalışır. Özel bir şeye ihtiyacınız yoksa, sadece 1tutarlı bir şekilde kullanabilirsiniz . Komut dosyasının başka bir komut dosyası tarafından çalıştırılması gerekiyorsa, kendi durum kodu kümenizi belirli bir anlamla tanımlamak isteyebilirsiniz. Örneğin, 1== test başarısız, 2== derleme başarısız. Komut dosyası başka bir şeyin parçasıysa, kodları orada kullanılan uygulamalarla eşleşecek şekilde ayarlamanız gerekebilir. Örneğin, test paketinin bir kısmı automake tarafından çalıştırıldığında, 77atlanan bir testi işaretlemek için kod kullanılır.
Michał Górny

21
hayır bu pencereyi de kapatır, sadece senaryodan
Toni Leigh

7
@ToniLeigh bash "pencere" kavramına sahip değildir, muhtemelen özel kurulumunuzun (örn. Bir terminal emülatörü) ne yaptığı konusunda kafanız karışmıştır.
Michael Foukarakis

4
@ToniLeigh "Pencereyi" kapatıyorsa, büyük olasılıkla exit #komutu bir komut dosyasına değil, bir işlevin içine koyuyorsunuzdur . (Bu durumda return #bunun yerine kullanın.)
Jamie

1
@Sevenearths 0 başarılı olduğu anlamına gelir, bu yüzden exit 0komut dosyasından çıkıp 0 döndürür (başarılı olduğu bu komut dosyasının sonucunu kullanabilecek diğer komut dosyalarını söyler)
VictorGalisson

689

Set -e kullan

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Komut dosyası, başarısız olan ilk satırdan sonra sona erer (sıfır olmayan çıkış kodunu döndürür). Bu durumda, komut-that-fails2 çalışmaz.

Her bir komutun dönüş durumunu kontrol etseydiniz betiğiniz şöyle görünürdi:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Set -e ile şöyle görünecektir:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Başarısız olan herhangi bir komut tüm komut dosyasının başarısız olmasına ve $? İle kontrol edebileceğiniz bir çıkış durumu döndürmesine neden olur. . Betiğiniz çok uzunsa veya çok fazla şey oluşturuyorsanız, her yere iade durumu kontrolleri eklerseniz oldukça çirkinleşir.


10
İle set -eYou hala edebilirsiniz yapmak bazı senaryoyu durmadan hataları ile komutlar exit: command 2>&1 || echo $?.
Adobe

6
set -ebir boru hattı veya komut yapısı sıfırdan farklı bir değer döndürürse komut dosyasını iptal eder. Örneğin foo || bar, yalnızca her ikisi foove barsıfır dışında bir değer döndürürse başarısız olur . Genellikle set -ebaşlangıçta eklerseniz iyi yazılmış bir bash betiği çalışır ve ekleme otomatik bir sağlık kontrolü olarak çalışır: bir şey ters giderse betiği iptal edin.
Mikko Rantalainen

6
Komutları bir araya getiriyorsanız, set -o pipefailseçeneği ayarlayarak bunlardan herhangi biri başarısız olursa da başarısız olabilirsiniz .
Jake Biesinger

18
Aslında olmadan deyimsel kod set -esadece olurdu make || exit $?.
Üçlü

4
Ayrıca var set -u. Bir göz atın gayri resmi bash Katı modda : set -euo pipefail.
Pablo A

236

Bir SysOps adamı bana bir zamanlar Üç Parmaklı Pençe tekniğini öğretti:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Bu işlevler * NIX OS ve kabuk aromasına dayanıklıdır. Bunları komut dosyanızın başına (bash veya başka türlü), try()ifadenize ve kodunuza yerleştirin.

açıklama

( uçan koyun yorumuna dayanarak ).

  • yell: komut dosyası adını ve tüm bağımsız değişkenleri şuraya yazdırın stderr:
    • $0 betiğin yoludur;
    • $* hepsi argüman.
    • >&2>stdout'u & borusuna yönlendirme2 anlamına gelir . borunun1stdout kendisi olurdu .
  • dieile aynıdır yell, ancak "başarısız" anlamına gelen 0 dışı bir çıkış durumuyla çıkar .
  • tryyalnızca sol taraf başarısız olduğunda sağ tarafı değerlendiren ||(boolean OR) öğesini kullanır .
    • $@tüm argümanlar tekrar, ama farklı .

3
Komut dosyası oluşturmak için oldukça yeniyim. Yukarıdaki işlevlerin nasıl yürütüldüğünü açıklayabilir misiniz? Bilmediğim yeni bir sözdizimi görüyorum. Teşekkürler.
kaizenCoder

15
yell : $0betiğin yoludur. $*hepsi argüman. >&2" >stdout'u &boruya yönlendir " anlamına gelir 2. boru 1'in kendisi stdout olacaktır. böylece bağırma önekleri stderr'e komut adı ve baskılar ile tüm argümanları. die , bağırma ile aynı şeyi yapar , ancak "başarısız" anlamına gelen 0 dışı bir çıkış durumuyla çıkar. try , boole yöntemini kullanır veya ||yalnızca sol taraf başarısız olmazsa doğru tarafı değerlendirir. $@tüm argümanlar tekrar, ama farklı . umut her şeyi açıklıyor
uçan koyun

1
Ben die() { yell "$1"; exit $2; }bir mesaj ve çıkış kodu ile geçebileceği şekilde değiştirdim die "divide by zero" 115.
Mark Lakata

3
Nasıl kullanacağımı görebiliyorum yellve die. Ancak, tryçok fazla değil. Buna bir örnek verebilir misiniz?
kshenoy

2
Hmm, ama bunları bir senaryoda nasıl kullanıyorsun? Ne demek istediğini anlamıyorum "try () ifade ve kod üzerinde".
TheJavaGuy-Ivan Milosavljević

33

Senaryoyu ile çağırmak olacaksa source, kullanabilirsiniz return <x>nereye <x>komut çıkış durumu (hata veya yanlış bir sıfır olmayan bir değer kullanmak) olacaktır. Ancak, yürütülebilir bir komut dosyasını (yani doğrudan dosya adıyla) çağırırsanız, return ifadesi bir şikayet ile sonuçlanır (hata iletisi "return: yalnızca bir işlevden veya kaynaklı komut dosyasından` `geri dönebilir").

Eğer exit <x>bunun yerine kullanılan komut dosyası çalıştırıldığında, sourcebu senaryoyu başladı kabuktan çıkan sonuçlanacaktır, ancak beklendiği gibi çalıştırılabilir bir komut dosyası sadece sona erer.

Aynı komut dosyasında her iki durumu da ele almak için şunu kullanabilirsiniz:

return <x> 2> /dev/null || exit <x>

Bu, hangi çağrının uygun olabileceğini ele alacaktır. Bu ifadeyi komut dosyasının en üst düzeyinde kullanacağınız varsayılmaktadır. Komut dosyasının bir işlev içinden doğrudan çıkmasını önermem.

Not: <x>sadece bir sayı olması gerekiyordu.


Bir fonksiyonun içinde bir dönüş / çıkış olan bir komut dosyasında benim için çalışmıyor, yani kabuk mevcut olmadan fonksiyonun içinden çıkmak mümkün, ancak fonksiyonun arama kodunun doğru kontrolünü kontrol etmek için fonksiyon çağrısı yapmadan mümkün ?
Ocak

@jan Arayanın dönüş değerleriyle yaptığı (veya yapmadığı), işlevden nasıl geri döndüğünüzden (yani, çağrılma ne olursa olsun, kabuktan çıkmadan) nasıl tamamen diktir (yani bağımsızdır). Esas olarak bu soru-cevap kısmının bir parçası olmayan arayan koduna bağlıdır. Hatta terzi arayanın ihtiyaçlarına işlevin dönüş değeri olabilir, ancak bu yanıtın gelmez bu dönüş değeri ne olabilir sınırlamak ...
Kavadias

11

Genellikle hataları işlemek için run () adlı bir işlev içerir. Yapmak istediğim her çağrı bu işleve geçirilir, böylece bir hataya çarpıldığında tüm komut dosyası çıkar. Bunun set -e çözümü üzerindeki avantajı, bir satır başarısız olduğunda komut dosyasının sessizce çıkmaması ve sorunun ne olduğunu size söyleyebilmesidir. Aşağıdaki örnekte, komut dosyası false çağrısında çıktığı için 3. satır yürütülmez.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

1
Adamım, bir sebepten dolayı, bu cevabı gerçekten çok seviyorum. Biraz daha karmaşık olduğunu biliyorum, ama çok kullanışlı görünüyor. Ve bash uzmanı olmadığım için, mantığımın hatalı olduğuna inanmamı sağlıyor ve bu metodolojide bir sorun var, aksi takdirde başkalarının daha fazla övgü vereceğini hissediyorum. Peki, bu işlevin sorunu nedir? Burada dikkat etmem gereken bir şey var mı?

Eval kullanma nedenimi hatırlamıyorum, cmd_output = $ (1 $) ile iyi çalışıyor
Joseph Sheedy

Bunu karmaşık bir dağıtım sürecinin bir parçası olarak uyguladım ve harika çalıştı. Teşekkür ederim ve işte bir yorum ve bir oylama.
fuzzygroup

Gerçekten inanılmaz bir iş! Bu, iyi çalışan en basit ve en temiz çözümdür. Benim için FOR döngüleri bir komut almayacağım çünkü FOR döngüler bir komut önce ekledi set -e option. O argümanlarla olduğundan Sonra komut, ben bash meselelerinin gibi önlemek için tek tırnak kullanılır: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Not Çalıştırmak için işlevin adını değiştirdim.
Tony-Caffe

1
evalkeyfi girişi kabul ederseniz potansiyel olarak tehlikelidir, ancak aksi halde bu oldukça hoş görünür.
dragon788

5

ifYapı yerine , kısa devre değerlendirmesinden yararlanabilirsiniz :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Dönüşüm operatörünün önceliği nedeniyle gerekli olan parantez çiftini not edin. $?, en son denilen komutun kodundan çıkmak için ayarlanmış özel bir değişkentir.


1
eğer command -that --fails || exit $?parantez olmadan çalışırsam, echo $[4/0]onlara ihtiyacımız olan şey nedir?
Anentropik

2
@Anentropic @skalee Parantezlerin öncelik ile ilgisi yoktur, istisna işleme ile ilgilidir. Sıfıra bölme, kod 1 ile kabuktan hemen çıkmaya neden olur. Parantez (yani basit echo $[4/0] || exit $?) bash olmadan, echoitaat etmek yerine asla yürütmez ||.
19'da bobbogo

1

Ben de aynı sorum var ama bunu soramaz çünkü yinelenir.

Komut dosyası biraz daha karmaşık olduğunda, çıkış kullanılarak kabul edilen yanıt çalışmaz. Koşulu denetlemek için bir arka plan işlemi kullanırsanız, çıkış bir alt kabukta çalıştığından yalnızca bu işlemden çıkar. Senaryoyu öldürmek için, onu açıkça öldürmelisin (en azından bildiğim tek yol bu).

İşte nasıl yapılacağı hakkında küçük bir komut dosyası:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Bu daha iyi bir cevap ama yine de eksik bir bumba bölümünden nasıl kurtulacağımı gerçekten bilmiyorum .

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.