내가 원할 때 종료되지 않는 스크립트가 있습니다.
동일한 오류가 있는 예제 스크립트는 다음과 같습니다.
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
나는 출력을 본다고 가정합니다 :
:~$ ./test.sh
1
:~$
하지만 실제로는 다음과 같습니다.
:~$ ./test.sh
1
2
:~$
명령 연결이 ()
어떤 방식으로 범위를 생성합니까? exit
스크립트가 아닌 경우 무엇을 종료합니까?
답변1
()
하위 쉘에서 명령을 실행하므로 exit
하위 쉘을 종료하고 상위 쉘로 돌아갈 수 있습니다. {}
현재 셸에서 명령을 실행하려면 중괄호를 사용하세요.
Bash 매뉴얼에서:
(목록)목록은 서브쉘 환경에서 실행됩니다. 쉘 환경에 영향을 미치는 변수 할당 및 내장 명령은 명령이 완료된 후에 더 이상 유효하지 않습니다. 반환 상태는 목록의 종료 상태입니다.
{ 목록; }list는 현재 쉘 환경에서만 실행됩니다. 목록은 줄 바꿈 또는 세미콜론으로 끝나야 합니다. 이를 그룹 명령이라고 합니다. 반환 상태는 목록의 종료 상태입니다. 메타 문자( 및 )와 달리 { 및 }는 예약어이므로 예약어를 인식할 수 있는 위치에 나타나야 합니다. 단어 분리를 유발하지 않으므로 공백이나 기타 쉘 메타 문자로 목록과 구분해야 합니다.
쉘 구문은 매우 일관적이며 하위 쉘은 ()
명령 대체(이전 스타일 `..`
구문 사용) 또는 프로세스 대체와 같은 다른 구성에도 참여하므로 다음은 현재 쉘에서 종료되지 않습니다.
echo $(exit)
cat <(exit)
명령이 명시적으로 내부에 배치될 때 하위 쉘이 관련된다는 것은 분명하지만 ()
, 하위 쉘이 이러한 다른 구조 내에서도 생성된다는 사실은 덜 분명합니다.
명령이 백그라운드에서 시작됩니다.
exit &
man bash
(이후 ) 때문에 현재 쉘을 종료하지 마십시오.명령이 제어 연산자 &에 의해 종료되면 쉘은 서브쉘의 백그라운드에서 명령을 실행합니다. 쉘은 명령이 완료될 때까지 기다리지 않고 상태 0을 반환합니다.
관로
exit | echo foo
여전히 서브쉘에서만 종료됩니다.
그러나 이와 관련하여 다른 껍질은 다르게 작동합니다. 예를 들어 AT&T는
bash
파이프라인의 모든 구성 요소를 별도의 하위 셸에 넣지만(lastpipe
작업 제어가 활성화되지 않은 호출에서 이 옵션을 사용하지 않는 한) AT&T는 현재 셸에서 마지막 부분을ksh
실행합니다zsh
(POSIX에서는 두 동작을 모두 허용합니다). 그러므로exit | exit | exit
기본적으로 bash에서는 아무것도 수행하지 않지만 다음으로 인해 zsh를 종료합니다.마침내
exit
.coproc exit
exit
서브쉘에서도 실행됩니다 .
답변2
서브쉘에서 실행하는 것은 exit
함정입니다:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
스크립트는 42를 인쇄하고 종료됩니다.서브쉘반환 코드를 사용하면 1
스크립트 실행이 계속됩니다. 의 반환 코드에 관계없이 의 반환 코드는 항상 0이므로 호출을 로 바꾸는 것도 echo $(CALC) || exit 1
도움이 되지 않습니다 . 그리고 전에 실행합니다 .echo
calc
calc
echo
exit
local
더 혼란스러운 것은 다음 스크립트에 표시된 것처럼 내장 스크립트로 래핑하여 효과를 차단하는 것입니다 . 입력 값의 유효성을 검사하는 함수를 작성할 때 이 문제를 우연히 발견했습니다. 예:
20141211.log
오늘의 파일 인 "년월일.log"라는 파일을 생성하고 싶습니다 . 합리적인 가치를 제공하지 못하는 사용자가 날짜를 입력한 것입니다. 따라서 내 함수에서는 fname
반환 값을 확인 date
하여 사용자 입력의 유효성을 확인합니다.
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
좋아 보인다. 스크립트 이름을 으로 지정하십시오 s.sh
. ./s.sh "Thu Dec 11 20:45:49 CET 2014"
이 파일은 사용자가 호출 스크립트를 사용하는 경우 생성됩니다 . 20141211.log
그러나 사용자가 를 입력하면 ./s.sh "Thu hec 11 20:45:49 CET 2014"
스크립트는 다음을 출력합니다.
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
이 행은 fname…
서브셸에서 잘못된 입력 데이터가 감지되었음을 나타냅니다. 그러나 명령이 항상 반환되기 때문에 exit 1
줄 끝 부분은 local …
트리거되지 않습니다 . 처형당하기 때문이죠local
0
local
뒤쪽에 $(fname)
따라서 반환 코드를 덮어씁니다. 따라서 스크립트는 계속 진행되며 touch
빈 매개변수를 사용하여 호출됩니다. 이 예는 간단하지만 실제 애플리케이션에서는 bash의 동작이 매우 혼란스러울 수 있습니다. 실제 프로그래머는 로컬을 사용하지 않는다는 것을 알고 있습니다. ☺
명확하게 말하면, 그렇지 않은 경우 local
잘못된 날짜를 입력하면 스크립트가 예상대로 중단됩니다.
해결책은 선을 분리하는 것입니다
local FNAME
FNAME=$(fname "$1") || exit 1
이 이상한 동작은 bash 매뉴얼 페이지의 문서와 일치합니다 local
. "local이 함수 외부에서 사용되거나 잘못된 이름이 제공되거나 name이 읽기 전용 변수가 아닌 한 반환 상태는 0입니다."
버그는 아니지만 bash의 동작은 직관적이지 않습니다. 그러나 나는 실행 순서를 알고 있으므로 local
손상된 할당을 가려서는 안 됩니다.
내 원래 답변에는 일부 부정확한 내용이 포함되어 있었습니다. mikeserv와 길고 철저한 논의 끝에 (감사합니다) 문제를 해결하기 시작했습니다.
답변3
실제 솔루션:
#!/bin/bash
function bla() {
return 1
}
bla || { echo '1'; exit 1; }
echo '2'
오류 그룹화는 bla
오류 상태가 반환될 때만 실행되며 exit
하위 셸 내에서는 실행되지 않으므로 전체 스크립트가 중지됩니다.
답변4
괄호는 a로 시작합니다.서브쉘종료는 해당 하위 쉘만 종료합니다.
다음 명령을 사용하여 종료 코드를 읽고 $?
이를 스크립트에 추가하여 서브셸이 종료될 때 종료되도록 할 수 있습니다.
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi
echo '2'