Як виявити помилку в скрипті bash Linux?


13

Я зробив такий сценарій:

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

Сценарій працює, але окрім моїх відлунь, є також вихід, коли

cd $1

не вдається виконати.

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

Чи можна це зловити?


Просто FYI, ви також можете зробити це набагато простіше; test -d /path/to/directory(або [[ -d /path/to/directory ]]в bash) скаже вам, чи є дана ціль каталогом чи ні, і це зробить це тихо.
Патрік

@Patrick, це просто тестує, чи це каталог, а не якщо ви можете cdв нього ввійти.
Стефан Шазелас

@StephaneChazelas так. Назва функції - directoryExists.
Патрік

Дивіться детальну відповідь тут: підвищити помилку в сценарії Bash .
codeforester

Відповіді:


8

Ваш сценарій змінює каталоги під час запуску, а це означає, що він не працюватиме з низкою відносних імен. Потім ви прокоментували пізніше, що хочете перевірити лише наявність каталогу, а не вміння користуватися cd, тому відповіді взагалі не потрібно використовувати cd. Переглянуто. Використання tput та кольори від 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
}

(Відредаговано так, щоб використовувати більш невразливі printfзамість проблемних, echoякі можуть діяти на послідовності втечі в тексті.)


Це також виправляє (якщо ввімкнено функцію xpg_echo), коли назви файлів містять символи зворотної косої риси.
Стефан Шазелас

12

Використовуйте set -eдля встановлення режиму помилки виходу: якщо проста команда повертає ненульовий статус (що вказує на помилку), оболонка виходить.

Будьте обережні, що set -eне завжди відбувається удар. Команди на тестових позиціях дозволяються виходити з ладу (наприклад if failing_command, failing_command || fallback). Команди в допоміжній оболонці призводять лише до виходу з підклітини, а не батьківського: set -e; (false); echo fooвідображається foo.

Крім того, або, крім того, у bash (і ksh і zsh, але не звичайний sh), ви можете вказати команду, яка виконується у випадку, якщо команда повертає ненульовий статус, з ERRпасткою, наприклад trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR. Зауважте, що у таких випадках, як (false); …помилка ERR виконується в підпакеті, тому це не може змусити батьків вийти.


Нещодавно я трохи експериментував і виявив зручний спосіб фіксації ||поведінки, який дозволяє легко робити правильне поводження з помилками, не використовуючи пасток. Дивіться мою відповідь . Що ви думаєте про цей метод?
skozin

@ sam.kozin Я не маю часу детально переглянути вашу відповідь, це виглядає в принципі добре. Окрім портативності, які переваги над помилкою помилок ksh / bash / zsh?
Жил 'ТАК - перестань бути злим'

Напевно, єдиною перевагою є комбінованість, оскільки ви не ризикуєте перезаписати іншу пастку, встановлену перед запуском функції. Що є корисною функцією, коли ви пишете якусь загальну функцію, яку ви згодом будете використовувати та використовувати в інших сценаріях. Ще однією перевагою може бути повна сумісність POSIX, хоча це не так важливо, оскільки ERRпсевдосигнал підтримується у всіх основних оболонках. Дякуємо за відгук! =)
Скозін

@ sam.kozin Я забув написати в своєму попередньому коментарі: ви можете опублікувати це на Code Review та опублікувати посилання у чаті .
Жил "ТАК - перестань бути злим"

Дякую за пропозицію, я спробую виконати її. Не знав про перегляд коду.
skozin

6

Щоб розгорнути відповідь @Gilles :

Дійсно, set -eне працює всередині команд, якщо ви користуєтесь ||оператором після них, навіть якщо ви запускаєте їх у нижній частині; наприклад, це не працює:

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

Але ||оператор потрібен для запобігання поверненню із зовнішньої функції перед очищенням.

Існує невеликий фокус, який можна використати для виправлення цього: запустіть внутрішню команду у фоновому режимі, а потім негайно дочекайтеся її. waitВбудований повертають код завершення внутрішньої команди, і тепер ви використовуєте ||після того wait, а не внутрішньою функції, тому set -eпрацює належним чином всередині останній:

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

Ось загальна функція, яка ґрунтується на цій ідеї. Вона повинна працювати у всіх сумісних з POSIX оболонках, якщо ви вилучите localключові слова, тобто замініть всі local x=yпросто 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
}

Приклад використання:

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

Запуск прикладу:

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

Єдине, про що потрібно пам’ятати, використовуючи цей метод, - це те, що всі модифікації змінних Shell, виконані з команди, яку ви передаєте, runне поширюватимуться на функцію виклику, тому що команда працює в підзарядці.


2

Ви не кажете, що саме ви маєте на увазі під catch---, повідомте і продовжуйте; перервати подальшу обробку?

Оскільки cdпри відмові повертається ненульовий статус, ви можете зробити:

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

Ви можете просто вийти з ладу:

cd -- "$1" || exit 1

Або повторіть власне повідомлення та вийдіть:

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

І / або придушити помилку, надану при cdвідмові:

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

За стандартами команди повинні розміщувати повідомлення про помилки на STDERR (файловий дескриптор 2). Таким чином, 2>/dev/nullговорить переспрямування STDERR на "біт-відро", відоме /dev/null.

(не забудьте процитувати ваші змінні та позначити кінець варіантів для cd).


@Stephane Chazelas точка цитування та сигналізації закінчення варіантів добре прийнята. Дякуємо за редагування
JRFerguson

1

Власне для вашого випадку я б сказав, що логіку можна вдосконалити.

Замість cd, а потім перевірте, чи існує, перевірте, чи існує, тоді перейдіть до каталогу.

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

Але якщо ваша мета - приглушити можливі помилки cd -- "$1" 2>/dev/null, але це зробить налагодження в майбутньому важче. Ви можете перевірити тестування прапорів за адресою: Bash якщо документація :


Ця відповідь не може цитувати $1змінну і буде невдалою, якщо ця змінна містить пробіли або інші метахарактери оболонки. Він також не перевіряє, чи має користувач дозвіл cdна нього.
Ян Д. Аллен

Я насправді намагався перевірити, чи існує певний каталог, а не обов'язково CD в ньому. Але оскільки я не знав кращого, я думав, що спроба ввімкнути це може призвести до помилки, якщо її не існує, то чому б не зловити її? Я не знав про те, якщо [-d $ 1] це саме те, що мені потрібно. Тож велике спасибі! (Я звик пропрацьовувати Java та перевіряти, чи немає у Java заявки, якщо заява не зовсім поширена в Java)
Thomas De Wilde
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.