평가: $? 및 ${PIPESTATUS[@]}(bash)

평가: $? 및 ${PIPESTATUS[@]}(bash)

Bash 5.0에서는 캡처가 ${PIPESTATUS[@]}통과되기를 원합니다 eval. 그러나 마스크는 마스크 와 동일 하지 않은 eval것 같습니다 . 결과에서 추출하는 방법이 있나요 ? 아래 명령 문자열에 무언가를 추가해도 작동하지 않는 것 같습니다.${PIPESTATUS[@]}$?${PIPESTATUS[-1]}${PIPESTATUS[@]}eval&& array=( ${PIPESTATUS[@]} ) && export array

$?이것이 간단하지 않다고 가정하는 것이 맞습니까 ${PIPESTATUS[-1]}?

내 샘플 코드(루트로 실행):

#!/usr/bin/env bash

#without eval, ${PIPESTATUS[@]} has two entries as it should.
apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log 
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
    echo $status
done

echo ""
echo ""

#with eval, ${PIPESTATUS[@]} has one entry and is equal to $?
commandstring="apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log"
eval "$commandstring"
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
    echo $status
done

편집: PIPESTATUS에 대한 원래 설명에서 약간의 기술적 수정 사항을 수정했습니다. 또한 아래 답변을 바탕으로 몇 가지 설명이 있습니다.

  • 프로그래밍 방식으로 명령 문자열을 작성하고 있기 때문에 이것을 사용하고 있습니다 eval. 그 중 일부에는 bash -c... 다른 사용자로 명령을 실행하세요.
  • 설정을 살펴본 pipefail결과 때로는 작동할 수도 있지만 항상 작동하지는 않습니다. 파이프라인의 여러 단계 상태를 알아야 하는 경우도 있기 때문입니다. 설정 set -o pipefail한 다음 설정을 해제할 수 있으면 작동하지만 설정을 해제하는 방법을 모르고 단순히 서브셸을 종료하고 설정되지 않은 새 서브셸 pipefail로 계속 진행할 수 없습니다 . pipefail예를 들어 쉘 옵션을 설정 해제하는 방법은 무엇입니까 pipefail?
  • 포함된 배열을 내보내는 방법에 대한 위의 이해가 잘못 되었습니까 PIPESTATUS? 서브셸 PIPESTATUS에서 어떻게 간단히 내보낼 수 있나요 ?eval

편집 2: 아래 표시된 훌륭한 답변 덕분에 최종 결정은 리디렉션이 포함된 명령 문자열을 동적으로 조합하는 상황(평가가 필요한 이유)을 사용 set -o pipefail하고 실행 후에 수행한다는 것이었습니다 set +o pipefail.

또한 지난번 이후로 몇 년간의 bash 경험이 있기 때문에 동적 빌드 명령을 실행하는 모범 사례를 다시 연구하고 있습니다. 내 사용 사례 eval는 다음과 같습니다

  1. 명령에 가 포함될 수 있으므로 이를 실행하는 bash -c데 사용할 수 없습니다 .bash -c
  2. 리디렉션이 포함될 수 있습니다.>> log
  3. 동적 명령은 디버깅 목적으로 매개변수를 추가할 수도 있습니다.

내가 아는 한, 1에서는 다른 작업을 수행할 수 있고 3에서는 set -x [command]및 과 같은 trap것을 사용해야 합니다. 2에 대한 해결책은 아직 찾지 못했습니다.

답변1

$?마지막 명령 실행의 가장 오른쪽 구성 요소(또는 설정된 경우 가장 오른쪽 실패한 구성 요소)의 상태를 포함합니다 pipefail. 단, 이는 !prefix 키워드로 수정될 수 있습니다.

요소는 $PIPESTATUS이전 파이프라인의 각 구성요소의 종료 상태이며 !값에 영향을 주지 않습니다.

그 이후에는 (exit 1) | (exit 2) | (exit 3)( $PIPESTATUS1 2 3)이 되고 $?3이 됩니다. 그 이후에는 ! (exit 1) | (exit 2) | (exit 3)동일 $PIPESTATUS하지만 $?0이 되어 와 같습니다 $(( ! 3 )).

false또는 (exit 1)여전히 하나의 구성 요소가 있는 파이프입니다. 첫 번째 경우에는 간단한 명령이고 두 번째 경우에는 하위 쉘이므로 PIPESTATUS=(1)and $?= 1을 얻습니다.

이것은 똑같습니다. eval 'any shell code'단지 간단한 명령일 뿐입니다.

그 다음에 eval 'A | B' | C$PIPESTATUS종료 상태 eval(마지막으로 실행한 명령의 종료 상태) B와 종료 상태가 포함됩니다 C. (A | B) | C그건 그렇고, 마찬가지입니다.

다음을 수행할 수 있습니다.

eval '
  A | B
  saved_STATUS=$? saved_PIPESTATUS=( "${PIPESTATUS[@]}" )
'

호출하는 인터프리터에서 볼 수 있듯이 파이프라인 구성 요소의 상태를 유지하고 싶으므로 eval귀하의 경우에는 다음을 수행하십시오.

preserve_status='
  saved_STATUS=$? saved_PIPESTATUS=( "${PIPESTATUS[@]}" )
'
eval "$commandstring $preserve_status"
commandsPipestatus=( "${saved_PIPESTATUS[@]}" )
for status in "${commandsPipestatus[@]}"; do
    echo "$status"
done

그러나 여기서는 다음을 원하는 것 같습니다.

install_java() (
  set -o pipefail
  apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log
)

그건:

  • 변수가 아닌 함수에 코드 저장
  • 함수가 실패 하거나 실패할 경우 실패를 반환 pipefail하려면 이 옵션을 사용하십시오 . 함수 본문은 더 일반적인 서브쉘이 아니라 서브쉘입니다.apt-gettee명령 그룹command( { ...; })는 옵션이 로컬에서만 설정되었음을 나타냅니다. 최신 버전에서는 서브셸 없이도 함수에 로컬로 설정된 옵션을 bash사용할 수도 있습니다 .local -; set -o pipefail

그러나 일반 출력과 오류는 모두 apt-getstdout으로 전송됩니다.

을 사용하는 경우 zsh다음을 수행할 수 있습니다.

exec {log}>> ~/log # at the beginning of the script for instance.
apt-get install -y java-17-openjdk-amd64 >&1 >&$log 2>&2 2>&$log

stderr의 stdout을 apt-get로그와 해당 원래 대상으로 보냅니다. 내부적으로 ing이 완료되고 tee종료 상태가 유지됩니다.apt-get

다음과 같이 이를 위한 도우미 함수를 만들 수도 있습니다.

#! /bin/zsh -
die() { print -ru2 -C1 -- "$@"; exit 1; }
exec {log}>> ~/log
with_log() { "$@" >&1 >&$log 2>&2 2>&$log; }

with_log apt-get ... || die "Aborting..."

답변2

대신 함수를 사용하는 것이 eval더 나은 솔루션일 수 있습니다.

따라서 필요한 경우 여러 패키지를 설치할 수 있도록 허용하면 됩니다.

install() {
  apt-get install -y "${1}" 2>&1 | tee -a ~/log
}
packages=("java-17-openjdk-amd64")
for pkg in "${packages[@]}" ; do
  install pkg
  # replace below with actions based on status
  printf "%s\n" "${PIPESTATUS[@]}"
done

set -o pipefailapt-get명령을 모두 확인하지 않고 오류를 제거하는 방법이지만 PIPESTATUS로그에 쓸 수 없으면 상황이 약간 흐려질 수 있습니다.tee

편집: 추가 정보를 바탕으로

나는 당신이 겪고 있는 문제가 eval서브쉘(또는 적어도 그와 유사한 것)에서 명령을 실행하기 때문이라고 생각합니다. 종료 코드만 받는 것처럼 느껴집니다(가정이 틀려서 다행입니다).

명령에서 파이프 상태를 인쇄할 수 있지만 가져오는 것이 까다로울 수 있습니다.

이 괴물은 처리할 수 있는 한 가지 방법이지만 매우 복잡하고 텍스트 처리 측면에서 명령을 일부 사용자 정의해야 하며 결국에는 종료 코드만 받게 됩니다.

# just some stuff to print some text and exit with a non zero
# before the next pipeline step masks the exit code

foo='bash -c "echo hello;false" | tee log;echo PIPE ${PIPESTATUS[@]}'
# awk grabs the line with the pipe status text, pulls one of
# the exit codes from it and exits with it, otherwise prints
# the output as seen

eval "$foo" | awk '
  {if ($1=="PIPE"){print "just showing you I saw all the codes "$0;exit $2}; print $0}'
echo $?

awk다양한 파이프라인 상태를 캡처 하기 위해 출력을 제공하기 위해 뭔가를 할 수 있을 것 같지만 readarray, 그러면 매우 지저분해질 것입니다(이미 지저분한 상태임에도 불구하고).

상황이 이렇게 추악해지면 전체적인 접근 방식이 잘못된 것인지 궁금해지기 시작해야 합니다.

어쩌면 요구 사항을 더 자세히 설명하면 이를 달성하는 다른 방법이 나타날 수도 있습니다.

관련 정보