Как зафиксировать ошибку в сценарии удара Linux?

-- является наиболее часто используемым в команде для указания на конец опций. Это полезно, если Ваше имя файла начинается "-", или Ваш вход неизвестен. Вот пример его использования:

git diff --stat -- --file1 --file2

--file1 рассматривается как имя файла, а не другую опцию.

13
23.10.2013, 01:58
5 ответов

Ваши каталоги изменений сценария когда это работает, что означает это, не будут работать с серией относительных путей. Вы затем прокомментировали позже, что только хотели проверить на существование каталога, не способность использовать 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 это могло бы действовать на escape-последовательности в тексте.)

8
27.01.2020, 19:52
  • 1
    Это также фиксирует (если xpg_echo не идет), проблемы, когда имена файлов содержат символы обратной косой черты. –  Stéphane Chazelas 22.10.2013, 17:28

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

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

С другой стороны, или кроме того, в ударе (и ksh и zsh, но не простой sh), можно указать команду, это выполняется в случае, если команда возвращает ненулевое состояние, с ERR прерывание, например. trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR. Отметьте это в случаях как (false); …, ДОПУСКАТЬ ОШИБКУ прерывание выполняется в подоболочке, таким образом, это не может заставить родителя выходить.

12
27.01.2020, 19:52
  • 1
    , я экспериментировал немного и обнаружил удобный способ зафиксировать || поведение, которое включает, чтобы легко сделать надлежащую обработку ошибок, не используя прерывания. См. мой ответ. Что Вы думаете о том методе? попытка –  skozin 11.01.2016, 18:36
  • 2
    @sam.kozin, у меня нет времени для рассмотрения ответа подробно, это выглядит хорошим на принципе. Кроме мобильности, каковы преимущества по ksh/bash/zsh, ДОПУСКАЮТ ОШИБКУ прерывание? –  Gilles 'SO- stop being evil' 11.01.2016, 19:07
  • 3
    Вероятно, единственное преимущество является composability, поскольку Вы не рискуете перезаписывать другой капкан, который был поставлен перед функционированием выполнений. Который является полезной функцией, когда Вы пишете некоторую общую функцию, которую Вы позже получите и использовать из других сценариев. Другое преимущество могло бы быть полной совместимостью POSIX, хотя это не столь важно как ERR псевдосигнал поддерживается во всех главных оболочках. Спасибо за обзор! =) –  skozin 11.01.2016, 19:20
  • 4
    @sam.kozin я забыл писать в своем предыдущем комментарии: можно хотеть отправить это на Обзоре Кода и отправить ссылку в чат-комнате. –  Gilles 'SO- stop being evil' 11.01.2016, 19:24
  • 5
    Спасибо за предложение я попытаюсь следовать за ним. Не знал об Обзоре Кода. –  skozin 11.01.2016, 19:27

Вы не говорите, под чем точно Вы подразумеваете 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).

2
27.01.2020, 19:52
  • 1
    @Stephane Chazelas заключения в кавычки и сигнального конца опций, хорошо взятого. Спасибо за редактирование. –  JRFerguson 22.10.2013, 16:36

На самом деле для Вашего случая я сказал бы, что логика может быть улучшена.

Вместо 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
27.01.2020, 19:52
  • 1
    Этому ответу не удается заключить в кавычки $1 переменная и перестанет работать, если та переменная будет содержать пробелы или другие метасимволы оболочки. Этому также не удается проверить, есть ли у пользователя разрешение к cd в него. –  Ian D. Allen 22.10.2013, 16:38
  • 2
    , который я на самом деле пытался проверить, существовал ли определенный каталог, не обязательно CD к нему. Но потому что я не знал лучше, я думал, пробуя к CD к нему, вызвал бы ошибку, если бы не существовал итак, почему бы не поймать его? Я не знал о, если [-d 1$] это точно, в чем я нуждался. Так, спасибо много! (Я привык к proram Java, и проверяющий на каталог в, если заявление не является точно совместным в Java) –  Thomas De Wilde 22.10.2013, 21:30

В развитие ответа @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

Единственное, о чем вам нужно знать при использовании этого метода, это то, что все изменения переменных шелла, сделанные из команды, которую вы передаете в run, не распространятся на вызывающую функцию, потому что команда выполняется в подшелле.

5
27.01.2020, 19:52

Теги

Похожие вопросы