리눅스 bash 스크립트에서 오류를 잡는 방법?


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

이것을 잡을 수 있습니까?


참고로이 작업을 훨씬 간단하게 수행 할 수도 있습니다. test -d /path/to/directory(또는 [[ -d /path/to/directory ]]bash)는 주어진 대상이 디렉토리인지 아닌지를 알려주고 조용히 수행합니다.
— Patrick

@Patrick, 디렉토리가 아닌지 테스트 cd합니다.
— Stéphane Chazelas

@StephaneChazelas 예. 기능 이름은 directoryExists입니다.
— Patrick

자세한 답변 은 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가 켜져 있지 않은 경우)가 수정됩니다.
— Stéphane Chazelas

12

set -e오류 발생시 종료 모드 설정에 사용하십시오 . 간단한 명령이 0이 아닌 상태 (실패 표시)를 리턴하면 쉘이 종료됩니다.

그주의 set -e항상 걷어차하지 않습니다. 테스트 위치에서 명령이 실패 할 수 있습니다 (예를 들어 if failing_command, failing_command || fallback). 서브 쉘의 명령은 부모가 아닌 서브 쉘 만 종료합니다 : set -e; (false); echo foodisplays foo.

또는 bash (및 ksh 및 zsh (단, 일반 sh는 아님))에서 ERR트랩 과 함께 명령이 0이 아닌 상태를 반환하는 경우에 실행되는 명령을 지정할 수 있습니다 ( 예 :) trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR. 와 같은 경우 (false); …서브 쉘에서 ERR 트랩이 실행되므로 부모를 종료시킬 수 없습니다.


최근에 나는 약간의 실험을하고 편리한 ||동작 수정 방법을 발견 하여 트랩을 사용하지 않고도 적절한 오류 처리를 쉽게 수행 할 수있었습니다. 내 답변을 참조하십시오 . 그 방법에 대해 어떻게 생각하십니까?
— skozin

@ sam.kozin 귀하의 답변을 자세하게 검토 할 시간이 없습니다. 원칙적으로 좋아 보입니다. 이식 성과는 별도로 ksh / bash / zsh의 ERR 트랩에 비해 어떤 이점이 있습니까?
— Gilles 'SO- 악 그만

아마도 기능 실행 전에 설정된 다른 트랩을 덮어 쓸 위험이 없기 때문에 유일한 장점은 구성 가능성입니다. 이는 나중에 다른 스크립트에서 소싱하여 사용할 공통 기능을 작성할 때 유용한 기능입니다. ERR의사 신호가 모든 주요 쉘에서 지원되는 것처럼 중요하지는 않지만 POSIX와의 완전한 호환성이 또 다른 이점 일 수 있습니다 . 검토해 주셔서 감사합니다! =)
— skozin

@ sam.kozin 이전 의견에 글을 쓰는 것을 잊었습니다. 코드 검토에 이를 게시하고 채팅방에 링크 를 게시 할 수 있습니다 .
— Gilles 'SO- 악 그만

제안 주셔서 감사합니다, 나는 그것을 따르려고 노력할 것입니다. 코드 검토에 대해 몰랐습니다.
— 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

이 방법을 사용할 때 알아야 run할 것은 명령이 서브 쉘에서 실행되기 때문에 사용자가 전달한 명령에서 수행 된 쉘 변수의 모든 수정이 호출 함수로 전파되지 않는다는 것입니다.


2

당신은 catch---보고하고 계속해서 정확히 무엇을 의미하는지 말하지 않습니다 . 추가 처리를 중단 하시겠습니까?

cd실패시 0이 아닌 상태를 리턴 하므로 다음을 수행 할 수 있습니다.

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/nullSTDERR을에서 알려진 "비트 버킷"으로 리디렉션한다고합니다 /dev/null.

(변수를 인용하고 옵션의 끝을 표시하는 것을 잊지 마십시오 cd).


@Stephane Chazelas 인용 및 옵션 종료 옵션의 요점을 잘 파악했습니다. 편집 해 주셔서 감사합니다.
— JR 페 거슨

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향후 디버깅이 더 어려워집니다. 다음에서 if 테스트 플래그를 확인할 수 있습니다. Bash if documentation :


이 답변은 $1변수 를 인용 하지 못하며 변수에 공백이나 다른 쉘 메타 문자가 포함되어 있으면 실패합니다. 또한 사용자에게 권한이 있는지 확인하지 못합니다 cd.
— Ian D. Allen

실제로 특정 디렉토리가 존재하는지 확인하려고했지만 실제로는 CD가 아닙니다. 그러나 더 잘 알지 못했기 때문에 CD를 넣으려고하면 존재하지 않으면 오류가 발생할 것이라고 생각했습니다. if [-d $ 1]에 대해 정확히 몰랐습니다. 감사합니다! (저는 Java를 프롬 밍하고, if 문에서 디렉토리를 확인하는 것이 Java에서는 흔하지 않습니다)
— Thomas De Wilde
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.