programing

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

goodcopy 2023. 4. 8. 11:52
반응형

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

여러 명령어에 대해 pipefail과 유사한 것이 있습니까?예를 들어 bash 내에 'try' 문이 있습니다.저는 다음과 같은 것을 하고 싶습니다.

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"

"drop-out and echo the error"는 무슨 뜻입니까?명령어가 실패하는 즉시 스크립트가 종료되도록 하려면 다음 명령을 수행합니다.

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 -ebash를 는 사용할 수 를 들어 다음과 같이 할 수 있습니다.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[ variable ]변수입니다.

사용.

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

하는 대신, "Runner Functions"를 합니다.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"

저는 bash에서 거의 완벽한 try&catch 구현을 개발했습니다.이것에 의해, 다음과 같은 코드를 작성할 수 있습니다.

try 
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch 
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"

여러분은 심지어 시험 포획 블록을 그들 안에 둥지를 틀 수도 있어요!

try {
    echo 'Hello'

    try {
        echo 'Nested Hello'
        false
        echo 'This will not execute'
    } catch {
        echo "Nested Caught (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'This will not execute too'

} catch {
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}

코드는 제 bash 보일러 플레이트/프레임워크의 일부입니다.또한 역추적 오류 처리 및 예외 처리(및 기타 뛰어난 기능)를 통해 시도 및 캐치(try & catch)에 대한 아이디어를 더욱 확장합니다.

다음은 Try & Catch만을 담당하는 코드입니다.

set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0

# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
           __oo__insideTryCatch+=1; ( set -e;
           trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "

Exception.Capture() {
    local script="${BASH_SOURCE[1]#./}"

    if [[ ! -f /tmp/stored_exception_source ]]; then
        echo "$script" > /tmp/stored_exception_source
    fi
    if [[ ! -f /tmp/stored_exception_line ]]; then
        echo "$1" > /tmp/stored_exception_line
    fi
    return 0
}

Exception.Extract() {
    if [[ $__oo__insideTryCatch -gt 1 ]]
    then
        set -e
    fi

    __oo__insideTryCatch+=-1

    __EXCEPTION_CATCH__=( $(Exception.GetLastException) )

    local retVal=$1
    if [[ $retVal -gt 0 ]]
    then
        # BACKWARDS COMPATIBILE WAY:
        # export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
        # export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
        export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
        export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
        export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
        return 1 # so that we may continue with a "catch"
    fi
}

Exception.GetLastException() {
    if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
    then
        cat /tmp/stored_exception
        cat /tmp/stored_exception_line
        cat /tmp/stored_exception_source
    else
        echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
    fi

    rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
    return 0
}

자유롭게 사용, 포크, 투고를 할 수 있습니다.GitHub에 있습니다.

run() {
  $*
  if [ $? -ne 0 ]
  then
    echo "$* failed with exit code $?"
    return 1
  else
    return 0
  fi
}

run command1 && run command2 && run command3

첫 번째 답변에 코멘트를 할 수 없습니다.단, cmd_output=$($@) 명령을 실행하려면 새 인스턴스를 사용해야 합니다.

#!/bin/bash

function check_exit {
    cmd_output=$($@)
    local status=$?
    echo $status
    if [ $status -ne 0 ]; then
        echo "error with $1" >&2
    fi
    return $status
}

function run_command() {
    exit 1
}

check_exit run_command

이 실에 걸려 넘어지는 어패류 사용자용.

let let 렛츠고foo★★★★★★★(반품)하지 않지만(return)" 종료 코드를 평소처럼 설정하는 함수입니다.
를 피하기 $status함수를 호출한 후 다음을 수행할 수 있습니다.

foo; and echo success; or echo failure

그리고 한 줄에 끼기엔 너무 긴 경우:

foo; and begin
  echo success
end; or begin
  echo failure
end

위의 @john-kugelman의 멋진 솔루션을 RedHat 이외의 시스템에서 사용할 수 있습니다.이러한 솔루션을 사용하려면 , 코드에 다음의 행을 코멘트 아웃 합니다.

. /etc/init.d/functions

그리고 마지막에 아래 코드를 붙여주세요.완전 공개:이것은 Centos 7에서 가져온 상기 파일의 관련 부분을 직접 복사하여 붙여넣은 것입니다.

MacOS 및 Ubuntu 18.04에서 테스트 완료.


BOOTUP=color
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_WARNING="echo -en \\033[1;33m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"

echo_success() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
    echo -n $"  OK  "
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 0
}

echo_failure() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo -n $"FAILED"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
}

echo_passed() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo -n $"PASSED"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
}

echo_warning() {
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL
    echo -n "["
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo -n $"WARNING"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "]"
    echo -ne "\r"
    return 1
} 

사용할 때ssh접속문제와 리모트명령어 에러코드로 인한 문제를 구별해야 합니다.errexit(set -e) 모드. 다음 기능을 사용합니다.

# prepare environment on calling site:

rssh="ssh -o ConnectionTimeout=5 -l root $remote_ip"

function exit255 {
    local flags=$-
    set +e
    "$@"
    local status=$?
    set -$flags
    if [[ $status == 255 ]]
    then
        exit 255
    else
        return $status
    fi
}
export -f exit255

# callee:

set -e
set -o pipefail

[[ $rssh ]]
[[ $remote_ip ]]
[[ $( type -t exit255 ) == "function" ]]

rjournaldir="/var/log/journal"
if exit255 $rssh "[[ ! -d '$rjournaldir/' ]]"
then
    $rssh "mkdir '$rjournaldir/'"
fi
rconf="/etc/systemd/journald.conf"
if [[ $( $rssh "grep '#Storage=auto' '$rconf'" ) ]]
then
    $rssh "sed -i 's/#Storage=auto/Storage=persistent/' '$rconf'"
fi
$rssh systemctl reenable systemd-journald.service
$rssh systemctl is-enabled systemd-journald.service
$rssh systemctl restart systemd-journald.service
sleep 1
$rssh systemctl status systemd-journald.service
$rssh systemctl is-active systemd-journald.service

기능적인 방법으로 상태 확인

assert_exit_status() {

  lambda() {
    local val_fd=$(echo $@ | tr -d ' ' | cut -d':' -f2)
    local arg=$1
    shift
    shift
    local cmd=$(echo $@ | xargs -E ':')
    local val=$(cat $val_fd)
    eval $arg=$val
    eval $cmd
  }

  local lambda=$1
  shift

  eval $@
  local ret=$?
  $lambda : <(echo $ret)

}

사용방법:

assert_exit_status 'lambda status -> [[ $status -ne 0 ]] && echo Status is $status.' lls

산출량

Status is 127

가정하다

alias command1='grep a <<<abc'
alias command2='grep x <<<abc'
alias command3='grep c <<<abc'

어느 하나

{ command1 1>/dev/null || { echo "cmd1 fail"; /bin/false; } } && echo "cmd1 succeed" &&
{ command2 1>/dev/null || { echo "cmd2 fail"; /bin/false; } } && echo "cmd2 succeed" &&
{ command3 1>/dev/null || { echo "cmd3 fail"; /bin/false; } } && echo "cmd3 succeed"

또는

{ { command1 1>/dev/null && echo "cmd1 succeed"; } || { echo "cmd1 fail"; /bin/false; } } &&
{ { command2 1>/dev/null && echo "cmd2 succeed"; } || { echo "cmd2 fail"; /bin/false; } } &&
{ { command3 1>/dev/null && echo "cmd3 succeed"; } || { echo "cmd3 fail"; /bin/false; } }

수율

cmd1 succeed
cmd2 fail

지루하다.하지만 가독성은 나쁘지 않다.

언급URL : https://stackoverflow.com/questions/5195607/checking-bash-exit-status-of-several-commands-efficiently

반응형