[bash] 여러 명령의 Bash 종료 상태를 효율적으로 확인

‘try’문과 같지만 bash 내에 여러 명령에 대해 pipefail과 비슷한 것이 있습니까? 나는 이런 식으로하고 싶다 :

echo "trying stuff"
try {
    command1
    command2
    command3
}

어느 시점에서든 명령이 실패하면 해당 명령의 오류를 제거하고 반향하십시오. 나는 다음과 같은 일을하고 싶지 않습니다.

command1
if [ $? -ne 0 ]; then
    echo "command1 borked it"
fi

command2
if [ $? -ne 0 ]; then
    echo "command2 borked it"
fi

그리고 등등 … 또는 같은 것 :

pipefail -o
command1 "arg1" "arg2" | command2 "arg1" "arg2" | command3

내가 믿는 (각각 틀렸다면 교정) 각 명령의 주장이 서로 간섭 할 것이기 때문입니다. 이 두 가지 방법은 엄청나게 길고 불쾌한 것처럼 보이므로 더 효율적인 방법을 원합니다.



답변

명령을 시작하고 테스트하는 기능을 작성할 수 있습니다. 명령으로 설정된 환경 변수를 가정 command1하고 가정하십시오 command2.

function mytest {
    "$@"
    local status=$?
    if (( status != 0 )); then
        echo "error with $1" >&2
    fi
    return $status
}

mytest "$command1"
mytest "$command2"


답변

“오류를 제거하고 반향”한다는 것은 무엇을 의미합니까? 명령이 실패하자마자 스크립트를 종료하려면 다음을 수행하십시오.

set -e    # DON'T do this.  See commentary below.

스크립트 시작시 (아래 경고에 유의) 오류 메시지를 에코하지 마십시오. 실패한 명령이 처리하도록하십시오. 다시 말해, 다음과 같은 경우 :

#!/bin/sh

set -e    # Use caution.  eg, don't do this
command1
command2
command3

stderr에 오류 메시지를 인쇄하는 동안 command2가 실패하면 원하는 것을 달성 한 것 같습니다. (내가 원하는 것을 잘못 해석하지 않는 한!)

결론적으로, 작성하는 모든 명령은 제대로 작동해야합니다. stdout 대신 stderr에 오류를보고해야하며 (질문의 샘플 코드는 stdout에 오류를 인쇄 함) 실패하면 0이 아닌 상태로 종료해야합니다.

그러나 나는 더 이상 이것이 좋은 습관이라고 생각하지 않습니다. set -e다른 버전의 bash로 의미를 변경했으며 간단한 스크립트에서는 잘 작동하지만 본질적으로 사용할 수없는 많은 경우가 있습니다. ( set -e; foo() { false; echo should not print; } ; foo && echo ok 여기서 의미를 고려하십시오 : 여기서 의미론은 다소 합리적이지만, 조기 종료 옵션 설정에 의존하는 함수로 코드를 리팩터링하면 쉽게 물릴 수 있습니다.) IMO 작성하는 것이 좋습니다 :

 #!/bin/sh

 command1 || exit
 command2 || exit
 command3 || exit

또는

#!/bin/sh

command1 && command2 && command3


답변

Red Hat 시스템에서 광범위하게 사용하는 일련의 스크립팅 기능이 있습니다. 그들은 시스템 기능을 사용하여 /etc/init.d/functions녹색 [ OK ]과 빨간색 을 인쇄합니다.[FAILED] 상태 표시기 합니다.

선택적으로 $LOG_STEPS실패한 명령을 기록하려는 경우 변수를 로그 파일 이름 .

용법

step "Installing XFS filesystem tools:"
try rpm -i xfsprogs-*.rpm
next

step "Configuring udev:"
try cp *.rules /etc/udev/rules.d
try udevtrigger
next

step "Adding rc.postsysinit hook:"
try cp rc.postsysinit /etc/rc.d/
try ln -s rc.d/rc.postsysinit /etc/rc.postsysinit
try echo $'\nexec /etc/rc.postsysinit' >> /etc/rc.sysinit
next

산출

Installing XFS filesystem tools:        [  OK  ]
Configuring udev:                       [FAILED]
Adding rc.postsysinit hook:             [  OK  ]

암호

#!/bin/bash

. /etc/init.d/functions

# Use step(), try(), and next() to perform a series of commands and print
# [  OK  ] or [FAILED] at the end. The step as a whole fails if any individual
# command fails.
#
# Example:
#     step "Remounting / and /boot as read-write:"
#     try mount -o remount,rw /
#     try mount -o remount,rw /boot
#     next
step() {
    echo -n "$@"

    STEP_OK=0
    [[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
}

try() {
    # Check for `-b' argument to run command in the background.
    local BG=

    [[ $1 == -b ]] && { BG=1; shift; }
    [[ $1 == -- ]] && {       shift; }

    # Run the command.
    if [[ -z $BG ]]; then
        "$@"
    else
        "$@" &
    fi

    # Check if command failed and update $STEP_OK if so.
    local EXIT_CODE=$?

    if [[ $EXIT_CODE -ne 0 ]]; then
        STEP_OK=$EXIT_CODE
        [[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$

        if [[ -n $LOG_STEPS ]]; then
            local FILE=$(readlink -m "${BASH_SOURCE[1]}")
            local LINE=${BASH_LINENO[0]}

            echo "$FILE: line $LINE: Command \`$*' failed with exit code $EXIT_CODE." >> "$LOG_STEPS"
        fi
    fi

    return $EXIT_CODE
}

next() {
    [[ -f /tmp/step.$$ ]] && { STEP_OK=$(< /tmp/step.$$); rm -f /tmp/step.$$; }
    [[ $STEP_OK -eq 0 ]]  && echo_success || echo_failure
    echo

    return $STEP_OK
}


답변

가치있는 것을 위해, 각 명령의 성공 여부를 확인하는 코드를 작성하는 짧은 방법은 다음과 같습니다.

command1 || echo "command1 borked it"
command2 || echo "command2 borked it"

여전히 지루하지만 적어도 읽을 수 있습니다.


답변

대안은 단순히 명령을 함께 결합하여 &&실패한 첫 번째 명령 이 나머지를 실행하지 못하게하는 것입니다.

command1 &&
  command2 &&
  command3

이것은 질문에서 요구 한 구문이 아니지만 설명하는 사용 사례의 일반적인 패턴입니다. 일반적으로 명령은 인쇄 실패에 대한 책임이 있으므로 수동으로 -q할 필요가 없습니다 (원하는 경우 오류를 끄는 플래그가있을 수 있음). 이러한 명령을 수정하는 기능이 있다면 다른 명령으로 감싸기보다는 실패시 소리를 내도록 편집합니다.


또한 다음을 수행 할 필요가 없습니다.

command1
if [ $? -ne 0 ]; then

간단히 말할 수 있습니다.

if ! command1; then

당신이 때 어떻게 검사 할 필요가 리턴 코드는 산술 컨텍스트 대신를 사용합니다 [ ... -ne:

ret=$?
# do something
if (( ret != 0 )); then


답변

러너 함수를 작성하거나을 set -e사용하는 대신 다음을 사용하십시오 trap.

trap 'echo "error"; do_cleanup failed; exit' ERR
trap 'echo "received signal to stop"; do_cleanup interrupted; exit' SIGQUIT SIGTERM SIGINT

do_cleanup () { rm tempfile; echo "$1 $(date)" >> script_log; }

command1
command2
command3

트랩은 심지어 행 번호와이를 트리거 한 명령의 명령 행에 액세스 할 수 있습니다. 변수는 $BASH_LINENO$BASH_COMMAND입니다.


답변

개인적으로 나는 여기 에서 볼 수 있듯이 가벼운 접근법을 선호합니다 .

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }
asuser() { sudo su - "$1" -c "${*:2}"; }

사용법 예 :

try apt-fast upgrade -y
try asuser vagrant "echo 'uname -a' >> ~/.profile"