Linux bash komut dosyasında bir hata nasıl yakalanır?


13

Aşağıdaki betiği yaptım:

# !/bin/bash

# OUTPUT-COLORING
red='\e[0;31m'
green='\e[0;32m'
NC='\e[0m' # No Color

# FUNCTIONS
# directoryExists - Does the directory exist?
function directoryExists {
    cd $1
    if [ $? = 0 ]
            then
                    echo -e "${green}$1${NC}"
            else
                    echo -e "${red}$1${NC}"
    fi
}

# EXE
directoryExists "~/foobar"
directoryExists "/www/html/drupal"

Senaryo çalışıyor, ama yankılarımın yanında,

cd $1

yürütme başarısız.

testscripts//test_labo3: line 11: cd: ~/foobar: No such file or directory

Bunu yakalamak mümkün mü?


Sadece bir FYI, bunu çok daha basit yapabilirsiniz; test -d /path/to/directory(veya [[ -d /path/to/directory ]]bash içinde) belirli bir hedefin bir dizin olup olmadığını söyler ve sessizce yapar.
Patrick

@Patrick, bu sadece bir dizin olup olmadığını test eder, cdiçine girip giremediğinizi değil .
Stéphane Chazelas

@StephaneChazelas evet. İşlev adı directoryExists.
Patrick

Ayrıntılı bir cevabı burada görebilirsiniz: Bash betiğinde hatayı yükseltin .
codeforester

Yanıtlar:


8

Betiğiniz çalışırken dizinleri değiştirir, yani bir dizi göreceli yol adıyla çalışmaz. Daha sonra, yalnızca dizin varlığını kontrol etmek istediğinizi, kullanma yeteneğini değil cd, bu nedenle yanıtların hiç kullanması gerekmediğini yorumladınız cd. Revize. Kullanılması tput gelen ve renkleri man terminfo:

#!/bin/bash -u
# OUTPUT-COLORING
red=$( tput setaf 1 )
green=$( tput setaf 2 )
NC=$( tput setaf 0 )      # or perhaps: tput sgr0

# FUNCTIONS
# directoryExists - Does the directory exist?
function directoryExists {
    # was: do the cd in a sub-shell so it doesn't change our own PWD
    # was: if errmsg=$( cd -- "$1" 2>&1 ) ; then
    if [ -d "$1" ] ; then
        # was: echo "${green}$1${NC}"
        printf "%s\n" "${green}$1${NC}"
    else
        # was: echo "${red}$1${NC}"
        printf "%s\n" "${red}$1${NC}"
        # was: optional: printf "%s\n" "${red}$1 -- $errmsg${NC}"
    fi
}

( Metindeki kaçış dizileri üzerinde etkili olabilecek printfsorunlu yerine daha dokunulmaz kullanmak için düzenlenmiştir echo.)


Bu, dosya adları ters eğik çizgi karakterleri içerdiğinde (xpg_echo açık değilse) sorunları da giderir.
Stéphane Chazelas

12

set -eHata durumunda çıkış modunu ayarlamak için kullanın : basit bir komut sıfır dışında bir durum döndürürse (hatayı belirten), kabuk çıkar.

Dikkat edin, bu set -eher zaman tekme etmez. Test pozisyonlarda komutlar başarısız izin verilir (örneğin if failing_command, failing_command || fallback). Kabuktaki komutlar değil, yalnızca ebeveyn altkabuk çıkarken yol: set -e; (false); echo foogörüntüler foo.

Alternatif olarak veya ek olarak bash'de (ve ksh ve zsh, ancak düz sh değil), bir komutun ERRtuzakla sıfır dışında bir durum döndürmesi durumunda yürütülen bir komutu belirtebilirsiniz , örn trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR. Durumlar gibi o Not (false); …bunu çıkmak için ebeveyn neden olmayacak şekilde, ERR tuzağı, alt kabukta yürütülür.


Son zamanlarda biraz denedim ve ||tuzakları kullanmadan uygun hata işlemeyi kolayca yapmayı sağlayan davranışları düzeltmenin uygun bir yolunu keşfettim . Cevabımı gör . Bu yöntem hakkında ne düşünüyorsun?
skozin

@ sam.kozin Cevabınızı ayrıntılı olarak incelemek için zamanım yok, prensipte iyi görünüyor. Taşınabilirlik dışında, ksh / bash / zsh'ın ERR tuzağına göre faydaları nelerdir?
Gilles 'SO- kötü olmayı kes'

Muhtemelen tek fayda, işlev çalışmaya başlamadan önce ayarlanan başka bir tuzağın üzerine yazma riskiniz olmadığından, birleştirilebilirliktir. Bu, daha sonra diğer komut dosyalarından kaynak alıp kullanacağınız bazı ortak işlevleri yazarken kullanışlı bir özelliktir. Diğer bir avantaj tam POSIX uyumluluğu olabilir, ancak ERRyalancı sinyal tüm büyük kabuklarda desteklendiği için o kadar önemli değildir . İnceleme için teşekkürler! =)
skozin

@ sam.kozin Önceki yorumuma yazmayı unuttum: Bunu Kod İncelemesine göndermek ve sohbet odasına bir link göndermek isteyebilirsiniz .
Gilles 'SO- kötü olmayı bırak'

Öneri için teşekkürler, takip etmeye çalışacağım. Kod İnceleme hakkında bilmiyordum.
skozin

6

@Gilles cevabını genişletmek için :

Aslında, bir alt kabukta çalıştırsanız bile, operatörden sonra set -ekullanırsanız, komutların içinde çalışmaz ||; örneğin, bu işe yaramaz:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer

set -e

outer() {
  echo '--> outer'
  (inner) || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

Ancak ||temizlemeden önce dış fonksiyondan geri dönmesini önlemek için operatöre ihtiyaç vardır.

Bunu düzeltmek için kullanılabilecek küçük bir numara var: iç komutu arka planda çalıştırın ve hemen bekleyin. waitYerleşik iç komutun çıkış kodu döndürür ve şimdi kullandığınız ||sonra waitöylesine değil, iç fonksiyonu, set -eikincisi içeride düzgün çalışır:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup

set -e

outer() {
  echo '--> outer'
  inner &
  wait $! || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

İşte bu fikir üzerine inşa edilen genel fonksiyon. Kaldırmak eğer tüm POSIX uyumlu kabuklarda çalışmalıdır localanahtar kelimeleri yani hepsini yerine, local x=ysadece birlikte x=y:

# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
  local cmd="$1"; shift
  local exit_code=0

  local e_was_set=1; if ! is_shell_attribute_set e; then
    set -e
    e_was_set=0
  fi

  "$cmd" "$@" &

  wait $! || {
    exit_code=$?
  }

  if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then
    set +e
  fi

  if [ -n "$CLEANUP" ]; then
    RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@"
    return $?
  fi

  return $exit_code
}


is_shell_attribute_set() { # attribute, like "x"
  case "$-" in
    *"$1"*) return 0 ;;
    *)    return 1 ;;
  esac
}

Kullanım örneği:

#!/bin/sh
set -e

# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh


main() {
  echo "--> main: $@"
  CLEANUP=cleanup run inner "$@"
  echo "<-- main"
}


inner() {
  echo "--> inner: $@"
  sleep 0.5; if [ "$1" = 'fail' ]; then
    oh_my_god_look_at_this
  fi
  echo "<-- inner"
}


cleanup() {
  echo "--> cleanup: $@"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "$@"

Örneği çalıştırma:

$ ./so_3 fail; echo "exit code: $?"

--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127

$ ./so_3 pass; echo "exit code: $?"

--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0

Bu yöntemi kullanırken dikkat etmeniz gereken tek şey, ilettiğiniz komuttan yapılan Shell değişkenlerinin tüm değişikliklerinin runçağrı işlevine yayılmamasıdır, çünkü komut bir alt kabukta çalışır.


2

Tam olarak ne demek istediğini söylemiyorsun catch--- rapor et ve devam et; ileri işlem iptal mi?

Yana cdgetiriler başarısızlık üzerine sıfırdan farklı statü, bunu yapabilirsiniz:

cd -- "$1" && echo OK || echo NOT_OK

Sadece hata durumunda çıkabilirsiniz:

cd -- "$1" || exit 1

Veya kendi mesajınızı yankılayıp çıkın:

cd -- "$1" || { echo NOT_OK; exit 1; }

Ve / veya hata durumunda sağlanan hatayı bastırın cd:

cd -- "$1" 2>/dev/null || exit 1

Standartlara göre, komutlar hata mesajlarını STDERR'a (dosya tanımlayıcı 2) koymalıdır. Böylece 2>/dev/nullSTDERR tarafından bilinen "bit-bucket" a yönlendirilir diyor /dev/null.

(değişkenlerinizi alıntılamayı ve seçeneklerin sonunu işaretlemeyi unutmayın cd).


@Shanehane Chazelas alıntı ve işaretleme seçeneklerinin son noktasını iyi bir şekilde aldı. Düzenlediğiniz için teşekkürler.
JRFerguson

1

Aslında sizin durumunuz için mantığın geliştirilebileceğini söyleyebilirim.

CD yerine var olup olmadığını kontrol edin, var olup olmadığını kontrol edin ve dizine gidin.

if [ -d "$1" ]
then
     printf "${green}${NC}\\n" "$1"
     cd -- "$1"
else 
     printf "${red}${NC}\\n" "$1"
fi  

Ancak amacınız olası hataları susturmaksa cd -- "$1" 2>/dev/null, ancak bu gelecekte hata ayıklamayı zorlaştıracaktır. : En eğer test bayrakları kontrol edebilirsiniz Bash belgeler ise :


Bu yanıt $1değişkeni alıntılayamaz ve bu değişken boşluklar veya başka kabuk metakarakterleri içeriyorsa başarısız olur. Kullanıcının cdbuna izin verilip verilmediğini de kontrol edemez.
Ian D. Allen

Aslında belli bir dizinin var olup olmadığını kontrol etmeye çalışıyordum, mutlaka cd'ye değil. Ama daha iyi bilmediğim için, cd'ye çalışmayı denemeseydim bir hataya neden olacağını düşündüm, neden yakalamıyorsun? İhtiyacım olan şeyse [-d $ 1] hakkında bilmiyordum. Çok teşekkür ederim! (Java'yı tanıtmak için kullanıyorum ve bir if ifadesinde bir dizini kontrol etmek için Java'da yaygın değilim)
Thomas De Wilde
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.