이것은 bash의 버그입니까? `return`은 파이프에서 호출 된 경우 기능을 종료하지 않습니다


16

요즘 배쉬에 이상한 문제가 있습니다. 스크립트를 단순화하려고 할 때이 작은 코드 조각을 생각해 냈습니다.

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

return인쇄하지 않고 기능을 종료 $?했어야합니까? 그럼 파이프만으로 돌아올 수 있는지 확인했습니다.

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

while루프 없이도 마찬가지입니다 .

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

내가 여기서 놓친 것이 있습니까? 구글 검색은 이것에 대해 아무것도 가져 오지 않았다! 내 bash 버전은 Debian Wheezy의 4.2.37 (1) 릴리스 입니다.


답장에서 제안한 설정에 문제가있어 스크립트가 예상 한대로 직관적으로 동작 할 수 있습니까?
jlliagre

@jlliagre 그것은 수천 줄에 다소 복잡한 스크립트입니다. 다른 것을 깨뜨릴 염려가 있으므로 함수 내에서 파이프를 실행하지 않는 것을 선호하므로 프로세스 대체로 대체했습니다. 감사!
Teresa e Junior

while재생산에 필요하지 않은 경우 처음 두 가지 예를 제거해보십시오 . 그것은 지점에서 산만 해집니다.
Monica와의 가벼움 경주

@LightnessRacesinOrbit while루프는로 파이프에 매우 일반적으로 사용됩니다 return. 두 번째 예는 요점에 더 직설적이지만, 아무도 사용하지 않을 것이라고 생각합니다 ...
Teresa e Junior

1
불행히도 내 정답이 삭제되었습니다 ... 지정되지 않은 작업을 수행함에 따라 회색 영역에 있습니다. 셸이 파이프를 해석하는 방식에 따라 동작이 달라지며 ksh가 sh 소스에서 파생 된 경우에도 Bourne 셸과 Korn 셸간에 차이가 있습니다. Bourne Shell에서 while 루프는 서브 쉘에 있으므로 bash와 같이 에코가 표시됩니다. ksh에서 while 루프는 포 그라운드 프로세스이므로 ksh는 예제와 함께 echo를 호출하지 않습니다.
schily

답변:


10

관련 : /programming//a/7804208/4937930

스크립트를 종료하거나 함수에서 exit또는 return서브 쉘로 리턴 할 수없는 것은 버그가 아닙니다 . 다른 프로세스에서 실행되며 기본 프로세스에 영향을 미치지 않습니다.

그 외에도, 정의되지 않은 스펙에서 아마도 문서화되지 않은 bash 동작을보고 있다고 가정합니다. 함수에서는 return최상위 레벨의 서브 쉘 명령에서 오류가 발생하지 않으며 다음 과 같이 작동 exit합니다.

IMHO 그것은 return주요 진술이 함수에 있는지 아닌지 에 따라 일관성이없는 행동에 대한 bash 버그입니다 .

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

산출:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23

오류의 자세한 표시가 문서화되어 있지 않을 수 있습니다. 그러나 return하위 쉘의 최상위 명령 시퀀스에서 작동하지 않으며 특히 하위 쉘을 종료하지 않는다는 사실은 기존 문서가 이미 기대 한 것입니다. OP는 사용 exit 1 || return 1하려는 위치를 사용할 return수 있으며 예상되는 동작을 가져와야합니다. 편집 : @herbert의 답변은 하위 return쉘의 최상위 수준 이 exit(그러나 하위 쉘에서만 ) 기능 한다는 것을 나타냅니다 .
dubiousjim

1
@dubiousjim 내 스크립트를 업데이트했습니다. 나는 return간단한 서브 쉘 시퀀스 에서 어떤 경우에도 런타임 오류로 주장해야 하지만 실제로는 기능이 수행되지 않을 때가 아닙니다. 이 문제는 gnu.bash.bug 에서도 논의 되었지만 결론은 없습니다.
yaegashi

1
while 루프가 서브 쉘에 있는지 또는 포 그라운드 프로세스인지에 대해 지정되지 않았으므로 귀하의 답변이 올바르지 않습니다. 실제 쉘의 구현에 관계없이, 그 return진술은 기능적이며 합법적입니다. 그러나 결과 동작은 지정되지 않습니다.
schily

파이프 구성 요소가 서브 쉘에있는 동안 bash 매뉴얼 페이지에 문서화되어 있지만 문서화되지 않은 동작이라고 작성해서는 안됩니다. POSIX가 허용되는 동작을 지정하는 동안 동작은 정의되지 않은 사양을 기반으로 작성하지 않아야합니다. bash가 POSIX 표준을 따르는 동안 bash 버그를 의심하지 않아야합니다.
jlliagre

17

버그는 bash아니지만 문서화 된 동작입니다 .

파이프 라인의 각 명령은 자체 서브 쉘에서 실행됩니다.

return명령 유효 함수 정의 내에 존재하지만, 물론 하부 쉘에있는, 그 다음 명령어 있도록 부모 쉘에 영향을주지 않는 것이다 echo관계없이 실행된다. 그럼에도 불구하고 POSIX 표준 은 파이프 라인을 구성하는 명령이 서브 쉘 (기본값) 또는 맨 위 (허용 된 확장자)에서 실행될 수 있기 때문에 휴대용 쉘 구조가 아닙니다 .

또한 다중 명령 파이프 라인의 각 명령은 서브 쉘 환경에 있습니다. 그러나 확장으로 파이프 라인의 일부 또는 모든 명령이 현재 환경에서 실행될 수 있습니다. 다른 모든 명령은 현재 쉘 환경에서 실행됩니다.

바라건대, bash몇 가지 옵션을 사용하여 예상대로 작동하도록 말할 수 있습니다.

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here

1
return함수를 종료하지 않기 때문에 bash: return: can only `return' from a function or sourced script함수가 반환했을 수도 있다는 잘못된 의미를 사용자에게 알려주는 대신 쉘이 방금 인쇄하면 더 의미가 없습니까?
Teresa e Junior

2
나는 서브 쉘 내부 의 리턴 이 유효 하다는 문서의 어느 곳도 보지 못했다 . 이 기능은 ksh에서 복사되었으며, 함수 외부의 return 문 또는 소스 스크립트는 exit 처럼 동작합니다 . 원래 Bourne 쉘이 확실하지 않습니다.
cuonglm

1
@jlliagre : 아마도 Teresa는 그녀가 요구하는 용어에 대해 혼란 스럽지만 return서브 쉘에서 bash를 실행하면 bash가 진단을 발행하는 것이 까다로운 이유를 알 수 없습니다 . 결국, $BASH_SUBSHELL변수에 의해 입증 된 것처럼 하위 쉘에 있음을 알고 있습니다. 가장 큰 문제는 이것이 잘못된 긍정으로 이어질 수 있다는 것입니다. 서브 쉘의 작동 방식을 이해하는 사용자 는 서브 쉘을 종료하는 return대신 사용하는 스크립트를 작성할 수 있습니다 exit. (물론 변수를 설정하거나 cd서브 쉘에서 할 수있는 유효한 경우도 있습니다 .)
Scott

1
@Scott 나는 상황을 잘 이해하고 있다고 생각합니다. 파이프는 서브 쉘을 작성하고 return실제 기능 내에 있으므로 실패하지 않고 서브 쉘에서 리턴합니다. 문제는 help return구체적으로 다음과 같습니다. Causes a function or sourced script to exit with the return value specified by N.설명서를 읽음으로써 모든 사용자는 적어도 실패하거나 경고를 인쇄하지만 결코 동작하지 않을 것이라고 기대합니다 exit.
Teresa e Junior

1
함수 return 의 서브 쉘 에서 (메인 쉘 프로세스에서) 함수에서 리턴 할 것으로 기대하는 사람 은 서브 쉘 을 잘 이해하지 못하는 것 같습니다. 반대로, 나는 서브 쉘을 이해하는 독자 return 가 서브 쉘 을 종료하는 기능 에서 서브 쉘을 기대할 것을 기대한다 exit.
Scott

6

POSIX 문서 당, 사용 return기능의 외부 또는 소스 스크립트은 지정되지 않습니다 . 따라서 처리 할 쉘에 따라 다릅니다.

시스템 V 쉘에있는 동안, 오류를보고합니다 ksh, return같은 기능의 외부 또는 소스 스크립트 동작합니다 exit. 대부분의 다른 POSIX 쉘과 schily의 osh 도 다음과 같이 작동합니다.

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

ksh그리고 zsh이러한 껍질에 파이프의 마지막 부분은 현재 쉘 대신 서브 쉘에서 실행 되었기 때문에 출력을하지 않았다. return 문은 함수를 호출 한 현재 쉘 환경에 영향을 미치므로 아무 것도 인쇄하지 않고 함수가 즉시 반환되도록합니다.

대화식 세션에서는 bash오류 만보고하지만 셸을 종료하지 않고 schily's osh오류를보고하고 셸을 종료했습니다.

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

( zsh대화 형 세션 및 출력 단자 할 일이 종료되지 않습니다 bash, yash그리고 schily's osh오류를보고하지만, 쉘을 종료하지 않았다)


1
함수 내부 에서 return사용 한다고 주장 할 수 있습니다 .
jlliagre

1
@jlliagre : 당신이 무슨 뜻 확실하지 무엇을, return내부 사용했다 서브 쉘 내부 기능은 제외 ksh하고 zsh.
cuonglm

2
함수 내부에있는 서브 쉘 내부에있는 것이 반드시 해당 함수 외부에 있다는 것을 의미하지는 않습니다. 즉 표준 상태에서는 파이프 라인 구성 요소가있는 함수 외부에있는 것으로 간주되지 않습니다. 이것은 Open Group이 명확히 할 가치가 있습니다.
jlliagre

3
아니에요 그것은 기능 밖입니다. 함수를 호출 한 쉘과 리턴 을 실행 한 서브 쉘 이 다릅니다.
cuonglm

문제를 올바르게 설명하는 귀하의 추론을 이해합니다. 내 요점은 POSIX 표준에 설명 된 쉘 문법에 따르고 파이프 라인은 함수 본문 인 복합 명령의 일부인 복합 목록의 일부입니다. 파이프 라인 구성 요소가 기능 외부에서 고려되어야한다고 언급 된 곳은 없습니다. 내가 차 안에 있고 차가 차고에 주차되어있는 것처럼, 나는 차고에도 있다고 가정 할 수 있습니다. ;-)
jlliagre

4

bash에서 파이프 라인의 각 명령이 하위 셸에서 실행되는 것으로 예상되는 동작이 있다고 생각합니다. 함수의 전역 변수를 수정하려고 시도하면 직접 대화 할 수 있습니다.

foo(){ x=42; : | x=3; echo "x==$x";}

그건 그렇고, 반환은 작동하지만 서브 쉘에서 반환됩니다. 다시 확인할 수 있습니다.

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

다음을 출력합니다 :

1
This should not be printed.

따라서 return 문이 서브 쉘을 올바르게 종료했습니다

.


2
따라서 함수를 종료하려면를 사용 foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?하면 결과가 나타납니다 2. 그러나 명확성을 위해 나는을 만들 return 1것이다 exit 1.
dubiousjim

그건 그렇고, 파이프 라인의 모든 멤버 (모두가 아닌)가 서브 쉘에서 실행 된다는 사실에 대한 입증이 있습니까?
Incnis Mrsi

@IncnisMrsi : jlliagre의 답변을 참조하십시오 .
Scott

1

더 일반적인 대답은 bash와 다른 쉘은 일반적으로 파이프 라인의 모든 요소를 ​​별도의 프로세스에 넣는 것입니다. 이것은 명령 행이

프로그램 1 | 프로그램 2 | 프로그램 3

어쨌든 프로그램은 일반적으로 별도의 프로세스에서 실행되기 때문에 (당신이 말하지 않는 한 ). 그러나 그것은 놀람으로 올 수 있습니다exec program

명령 1 | 명령 2 | 명령 3

여기서 일부 또는 모든 명령이 내장 명령입니다. 간단한 예는 다음과 같습니다.

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

좀 더 현실적인 예는

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

전체가 여기서 while... do... done루프 서브 프로세스에 투입되고, 그 변화되도록하는 t루프 종료 후 주 셀에 표시되지 않는다. 이것이 바로 여러분이하는 일입니다. while루프 로 파이핑하여 루프가 서브 쉘로 실행 된 다음 서브 쉘에서 복귀하려고합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.