Linux bash 스크립트에서 오류를 잡는 방법은 무엇입니까?

Linux bash 스크립트에서 오류를 잡는 방법은 무엇입니까?

다음 스크립트를 만들었습니다.

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

이거 잡는게 가능해?

답변1

오류 종료 모드를 설정 하는 데 사용됩니다 set -e. 간단한 명령이 0이 아닌 상태(실패를 나타냄)를 반환하면 쉘이 종료됩니다.

이것이 set -e항상 작동하는 것은 아닙니다. 테스트 위치의 명령은 실패할 수 있습니다(예 if failing_command: failing_command || fallback). 하위 쉘의 명령은 상위 쉘이 아닌 하위 쉘만 종료하게 set -e; (false); echo foo합니다 foo.

대안으로 또는 추가로 bash(일반 sh는 아닌 ksh 및 zsh 포함)에서 트랩을 사용하여 명령이 0이 아닌 상태를 반환할 때 실행할 명령을 지정할 수 있습니다( ERR예: ) trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR. 비슷한 상황에서는 (false); …ERR 트랩이 하위 쉘에서 실행되므로 상위 쉘이 종료되지 않습니다.

답변2

스크립트는 실행 중에 디렉터리를 변경합니다. 즉, 일련의 상대 경로 이름을 사용할 수 없습니다. 그런 다음 나중에 디렉토리를 사용할 수 있는 능력이 아니라 디렉토리가 존재하는지 확인하고 싶었기 때문에 대답을 위해 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
}

(text의 이스케이프 시퀀스에서 작동할 수 있는 질문 대신 더 완벽한 사용을 위해 편집되었습니다 printf.)echo

답변3

연장하기 위해@gils의 답변:

실제로 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내부 명령의 종료 코드를 반환합니다. 이제 내부 함수가 아닌 ||after 를 사용하고 있으므로 후자 내부에서는 잘 작동합니다.waitset -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명령이 하위 쉘에서 실행되기 때문에 전달된 명령에서 수행된 쉘 변수에 대한 수정 사항이 호출 함수에 전파되지 않는다는 것입니다.

답변4

실제로 귀하의 경우에는 논리가 개선될 수 있다고 말하고 싶습니다.

cd 대신 존재하는지 확인하고 존재하는지 확인한 다음 해당 디렉토리로 이동하십시오.

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

그러나 가능한 오류를 제거하는 것이 목적이라면 cd -- "$1" 2>/dev/null나중에 디버깅이 더 어려워질 것입니다. 다음에서 if 테스트 플래그를 확인할 수 있습니다.Bash if 문서화:

관련 정보