리눅스 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.