Jak zachytit chybu v linuxovém bash skriptu?

Vytvořil jsem následující skript:

# !/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" 

Skript funguje, ale kromě mého ozvěny, existuje také výstup, když

cd $1 

selže při provádění.

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

Je to možné chytit?

Komentáře

  • Jen pro informaci, můžete to udělat také mnohem jednodušší; test -d /path/to/directory (nebo [[ -d /path/to/directory ]] v bash) vám řekne, zda je daný cíl adresář, nebo ne, a bude to dělat tiše.
  • @Patrick, který pouze otestuje, zda je ‚ sa adresář, ne pokud do něj můžete cd.
  • @StephaneChazelas ano. Název funkce je directoryExists.
  • Podrobnou odpověď najdete zde: Zvýšit chybu ve skriptu Bash .

Odpověď

Váš skript při spuštění mění adresáře, což znamená, že s nimi nebude pracovat řada relativních názvů cest. Později jste komentovali, že jste chtěli pouze zkontrolovat existenci adresáře, nikoli schopnost používat cd, takže odpovědi nemusíte používat cd vůbec. Revidováno. Použití tput a barev z 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 } 

(Upraveno použít nezranitelnější printf místo problematických echo, které by mohly působit na únikové sekvence v textu.)

Komentáře

  • To také opraví (pokud není xpg_echo zapnuto) problémy, když názvy souborů obsahují znaky zpětného lomítka.

Odpověď

Pomocí set -e nastavte režim ukončení při chybě: pokud jednoduchý příkaz vrátí nenulový stav (indikující poruchu), shell se ukončí.

Pozor, set -e se vždy nespustí. Příkazy v testovacích pozicích mohou selhat (např. if failing_command, failing_command || fallback). Příkazy v subshellu vedou pouze k ukončení subshellu, nikoli nadřazeného: set -e; (false); echo foo zobrazí foo.

Alternativně nebo navíc v bash (a ksh a zsh, ale ne obyčejný sh), můžete určit příkaz, který se provede v případě, že příkaz vrátí nenulový stav, pomocí ERR pasti, např. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Pamatujte, že v případech, jako je (false); …, je past ERR spuštěna v subshellu, takže nemůže způsobit ukončení rodiče.

Komentáře

  • Nedávno jsem trochu experimentoval a objevil jsem pohodlný způsob opravy chování ||, který umožňuje snadné provádění správných chyb bez použití pastí. Viz moje odpověď . Co si o této metodě myslíte?
  • @ sam.kozin Ne ‚ nemáte čas podrobně zkontrolovat svou odpověď, vypadá to v zásadě dobře. Kromě přenositelnosti, jaké jsou výhody oproti ksh / bash / zsh ‚ s ERR trap?
  • Pravděpodobně jedinou výhodou je skladatelnost, protože vám ‚ nehrozí přepsání jiné pasti, která byla nastavena před spuštěním funkce. Což je užitečná funkce, když ‚ přepisování některých běžných funkcí, které budete později získávat a používat z jiných skriptů. Další výhoda může být plná POSIX kompatibilita, i když to není tak důležité, protože ERR pseudo-signál je podporován ve všech hlavních skořápkách. Děkujeme za recenzi! =)
  • @ sam.kozin Zapomněl jsem napsat do svého předchozího komentáře: můžete to přidat na Recenze kódu a zveřejnit odkaz v chatovací místnosti .
  • Děkuji za návrh, pokusím se ‚ sledovat to. Nevěděl jsem ‚ o kontrole kódu.

Odpovědět

Chcete-li rozšířit @Gilles „odpověď :

Ve skutečnosti set -e nefunguje uvnitř příkazů, pokud po nich použijete operátor ||, i když je spustíte v subshellu; např. by to nefungovalo:

 #!/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  

Ale || operátor je nutný, aby se zabránilo návratu z vnější funkce před vyčištěním.

K vyřešení tohoto problému lze použít malý trik: spusťte vnitřní příkaz na pozadí a poté okamžitě počkat na to.Vestavěný wait vrátí výstupní kód vnitřního příkazu a nyní po iv id = „8a710934b9“ používáte || >

, nikoli vnitřní funkce, takžeset -euvnitř této funkce funguje správně:

 #!/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  

Tady je obecná funkce, která staví na této myšlence. Pokud odstraníte local klíčová slova, tj. nahraďte všechny local x=y pouhými 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 }  

Příklad použití:

 #!/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 "$@"  

Spuštění příkladu:

 $ ./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  

The při použití této metody si musíte být vědomi pouze toho, že všechny úpravy proměnných prostředí provedené z com mand, který předáte run, se nebude šířit do volající funkce, protože příkaz běží v subshellu.

Odpovědět

Neříkáte, co přesně máte na mysli tím, že catch — nahlaste a pokračujte; přerušit další zpracování?

Jelikož cd při selhání vrací nenulový stav, můžete:

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

Při selhání můžete jednoduše ukončit:

cd -- "$1" || exit 1 

Nebo odezvu své vlastní zprávy a ukončení:

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

A / nebo potlačit chybu poskytnutou cd při selhání:

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

Standardy by příkazy měly dát chybové zprávy na STDERR (deskriptor souboru 2). 2>/dev/null tedy říká, že přesměrujete STDERR na „bit-bucket“ známý /dev/null.

(nezapomeňte citovat své proměnné a označit konec možností pro cd).

Komentáře

  • @Stephane Chazelasův bod nabídky a signalizace konce možností jsou dobře přijaty. Děkujeme za úpravy.

Odpověď

Vlastně pro váš případ bych řekl, že logiku lze vylepšit.

Místo cd a poté zkontrolujte, zda existuje, zkontrolujte, zda existuje, přejděte do adresáře.

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

Pokud je však vaším účelem umlčet možné chyby, pak cd -- "$1" 2>/dev/null, ale v budoucnu bude ladění obtížnější. Můžete zkontrolovat, zda testování vlajky na: Bash, pokud je dokumentace :

Komentáře

  • Tato odpověď selže uvést proměnnou $1 a selže, pokud bude proměnná obsahuje mezery nebo jiné metaznaky prostředí. Rovněž nedokáže zkontrolovat, zda má uživatel oprávnění cd do něj.
  • Vlastně jsem se snažil zkontrolovat, zda existuje určitý adresář, ne nutně cd . Ale protože jsem ‚ t nevěděl lépe, myslel jsem si, že když se pokusím o CD, způsobí to chybu, pokud neexistuje, tak proč ji nechytit? Nevěděl jsem ‚ o tom, zda [-d $ 1], které ‚ je přesně to, co jsem potřeboval. Takže mnohokrát děkuji! (Používal jsem ‚ k proramování Javy a kontrola adresáře v příkazu if není v Javě úplně běžná)

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *