bash에 "goto"문이 있습니까?


204

bash에 "goto"문이 있습니까? 나는 그것이 나쁜 습관으로 여겨지지만 특별히 "고토"가 필요하다는 것을 알고 있습니다.


4
아니요, gotobash 에는 없습니다 (적어도 command not found나를 위해 말합니다 ). 왜? 더 좋은 방법이있을 가능성이 있습니다.
Niklas B.

153
그는 이유가있을 수 있습니다. goto다양한 관련 작업이 완료 될 때까지 한 시간 동안 기다리지 않고 큰 스크립트를 디버깅하기 위해 많은 코드를 건너 뛰기 때문에이 질문을 찾았 습니다. 나는 goto프로덕션 코드에서 확실히 a 를 사용 하지는 않지만 내 코드를 디버깅하기 위해 인생을 무한정 쉽게 만들었고 그것을 제거 할 때 더 쉽게 발견 할 수있었습니다.
Karl Nicoll

30
@delnan 그러나 goto가 없으면 어떤 것이 더 복잡해질 수 있습니다. 실제로 사용 사례가 있습니다.
glglgl

71
나는이 고토 신화에 아프다! goto에는 아무런 문제가 없습니다! 당신이 쓰는 모든 것은 결국 goto가됩니다. 어셈블러에는 goto 만 있습니다. 고급 프로그래밍 언어에서 goto를 사용해야하는 좋은 이유는 예를 들어 깨끗하고 읽기 쉬운 방식으로 중첩 루프에서 뛰어 내리는 것입니다.
Alexander Czutro

25
"고토 피하기"는 훌륭한 규칙입니다. 다른 규칙과 마찬가지로 3 단계로 학습해야합니다. 첫째, 두 번째 특성이 될 때까지 규칙을 따르십시오. 둘째, 규칙의 이유를 이해하는 법을 배웁니다. 셋째, 규칙을 따르는 방법과 규칙의 이유에 대한 포괄적 인 이해를 바탕으로 규칙에 대한 예외를 배우십시오. "goto"를 사용하는 것과 같은 위의 단계를 건너 뛰지 마십시오. ;-)
Jeff Learman 2016 년

답변:


75

아니 없어; 참조 §3.2.4 "복합 명령의"에 수동 강타 참조 제어 구조에 대한 정보는 어떻게 존재합니다. 특히 break및 의 언급은와 continue같이 유연하지 goto않지만 일부 언어에서보다 Bash에서 더 유연하며 원하는 것을 달성하는 데 도움이 될 수 있습니다. (무엇이든지 원하는 것은 ...)


10
"일부 언어보다 Bash에서 더 유연합니다"를 확장 할 수 있습니까?
user239558

21
@ user239558 : 일부 언어 는 가장 안쪽의 루프 에서만 break또는 continue가장 안쪽의 루프 에서만 허용 하는 반면 Bash에서는 얼마나 많은 레벨의 루프를 점프할지 지정할 수 있습니다. (그리고 임의의 루프 로 break또는 continue임의의 루프 를 허용하는 언어조차도 대부분 정적으로 표현해야합니다 (예 : break foo;레이블이 지정된 루프에서 끊어짐) foo-Bash에서는 동적으로 표현 break "$foo"됩니다 ( 예 : $foo루프에서 끊어짐) . )
ruakh

필요한 기능으로 함수를 정의하고 코드의 다른 부분에서 함수를 계산하는 것이 좋습니다.
blmayer 2016 년

@ruakh 사실, break LABEL_NAME;Bash의 역겨운 것이 아니라 많은 언어가 허용 break INTEGER;합니다.
사파이어

1
@Sapphire_Brick : 네, 댓글에 답장을했다고 언급했습니다.
ruakh

124

디버깅을 위해 큰 스크립트의 일부를 건너 뛰기 위해이 도구를 사용하는 경우 (Karl Nicoll의 의견 참조) false가 좋은 옵션이 될 수있는 경우 ( "false"가 항상 사용 가능한지 확실하지 않은 경우 / bin / false에 있음) :

# ... Code I want to run here ...

if false; then

# ... Code I want to skip here ...

fi

# ... I want to resume here ...

디버깅 코드를 제거해야 할 때 어려움이 있습니다. "거짓이면"구조는 매우 간단하고 기억에 남지만 일치하는 fi를 어떻게 찾습니까? 편집기에서 들여 쓰기를 차단할 수있는 경우 건너 뛴 블록을 들여 쓰기 할 수 있습니다 (완료되면 다시 되돌리려 고 함). 또는 fi 라인에 대한 의견이지만, 기억해야 할 것이 있어야합니다. 프로그래머에게 매우 의존적이라고 생각합니다.


6
예, false항상 가능합니다. 그러나 코드 블록이 있으면 실행하고 싶지 않은 경우 주석 처리하십시오. 또는 삭제하십시오 (나중에 복구해야하는 경우 소스 제어 시스템을보십시오).
키이스 톰슨

1
코드 블록이 너무 길어서 한 번에 한 줄을 어리석게 주석 처리 할 수 ​​없으면 다음 요령을 참조하십시오. stackoverflow.com/questions/947897/… 그러나 이것들은 텍스트 편집기가 처음부터 끝까지 일치하는 데 도움이되지 않으므로 크게 향상되지는 않습니다.
Camille Goudeseune 2016 년

4
"false 인 경우"는 코드를 주석 처리하는 것보다 훨씬 낫습니다. 왜냐하면 동봉 된 코드가 계속 유효한 코드이기 때문입니다. 코드를 주석 처리하는 가장 좋은 변명은 코드를 실제로 삭제해야하지만 기억해야 할 것이 있기 때문에 주석 일 뿐이며 더 이상 "코드"가 아닙니다.
Jeff Learman 2016 년

1
'#if false;'라는 주석을 사용합니다. 그렇게하면 검색하고 디버그 제거 섹션의 시작과 끝을 모두 찾을 수 있습니다.
Jon

이 답변은 OP의 질문에 어떤 식 으로든 응답하지 않기 때문에 투표해서는 안됩니다.
Viktor Joras

54

실제로 일부 디버그 또는 데모 요구에 유용 할 수 있습니다.

Bob Copeland 솔루션 http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant을 발견했습니다 .

#!/bin/bash
# include this boilerplate
function jumpto
{
    label=$1
    cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$')
    eval "$cmd"
    exit
}

start=${1:-"start"}

jumpto $start

start:
# your script goes here...
x=100
jumpto foo

mid:
x=101
echo "This is not printed!"

foo:
x=${x:-10}
echo x is $x

결과 :

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101

36
"배쉬를 어셈블리 언어처럼 보이게하려는 노력은 완성에 가까워졌습니다." - 와. 와우
Brian Agnew

5
내가 바꿀 유일한 것은 레이블 : start:이 구문 오류가 아닌 것처럼 시작되도록 레이블을 만드는 것 입니다.
Alexej Magura

5
다음과 같이 시작하면 더 좋습니다 : cmd = $ (sed -n "/ # $ label : / {: a; n; p; ba};"$ 0 | grep -v ': $') 시작 => 스크립트 오류를 방지 할이
access_granted

2
흠, 내 실수 일 가능성이 높다. 게시물을 수정했습니다. 감사합니다.
Hubbitus

2
set -x무슨 일이 일어나고 있는지 이해하는 데 도움이
John Lin

30

casebash에서 goto를 시뮬레이션 할 수 있습니다 :

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

생산 :

bar
star

4
이 필요합니다 bash v4.0+. 그러나 이것은 일반적인 목적이 goto아니라 case진술 의 대체 옵션입니다 .
mklement0

3
나는 이것이 대답이어야한다고 생각합니다. 주어진 명령에서 스크립트의 재개 실행을 지원하기 위해 갈 필요가 있습니다. 이것은 모든면에서 의미 론적, 고토 및 의미 론적 및 구문 설탕이 귀엽지 만 반드시 필요한 것은 아닙니다. 훌륭한 솔루션, IMO.
nathan g

1
@nathang, 그것은 여부 대답은 귀하의 경우는 영업 이익에 대해 질문 일반적인 경우의 부분 집합 메쉬 발생 여부에 따라 달라집니다. 불행히도,이 질문은 일반적인 경우에 대해 묻기 때문에이 대답은 너무 좁아서 정확하지 않습니다. ( 그 이유 때문에 그 질문이 너무 광범위 하게 종결 되어야하는지 여부 는 다른 논의입니다).
Charles Duffy

1
goto는 선택 이상의 것입니다. goto 기능은 흐름과 같은 루프를 만들어 어떤 조건에 따라 장소로 이동할 수 있음을 의미합니다.
Sergio Abreu

19

bash 스크립트를 테스트 / 디버깅하고 있고 하나 이상의 코드 섹션을 건너 뛰려면 단순히 대부분의 방법과 달리 나중에 쉽게 찾아 제거 할 수있는 매우 간단한 방법이 있습니다. 전술 한 바와).

#!/bin/bash

echo "Run this"

cat >/dev/null <<GOTO_1

echo "Don't run this"

GOTO_1

echo "Also run this"

cat >/dev/null <<GOTO_2

echo "Don't run this either"

GOTO_2

echo "Yet more code I want to run"

스크립트를 다시 정상으로 되돌리려면로 줄을 삭제하십시오 GOTO.

goto명령을 별칭으로 추가하여이 솔루션을 구체화 할 수도 있습니다 .

#!/bin/bash

shopt -s expand_aliases
alias goto="cat >/dev/null <<"

goto GOTO_1

echo "Don't run this"

GOTO_1

echo "Run this"

goto GOTO_2

echo "Don't run this either"

GOTO_2

echo "All done"

별칭은 일반적으로 bash 스크립트에서 작동하지 않으므로 shopt이를 수정 하려면 명령이 필요합니다 .

의 기능을 활성화 / 비활성화 goto하려면 조금 더 필요합니다.

#!/bin/bash

shopt -s expand_aliases
if [ -n "$DEBUG" ] ; then
  alias goto="cat >/dev/null <<"
else
  alias goto=":"
fi

goto '#GOTO_1'

echo "Don't run this"

#GOTO1

echo "Run this"

goto '#GOTO_2'

echo "Don't run this either"

#GOTO_2

echo "All done"

그런 다음 export DEBUG=TRUE스크립트를 실행하기 전에 수행 할 수 있습니다 .

레이블은 주석이므로 ' 'no-op goto로 설정 goto하여 '를 비활성화하면 구문 오류가 발생하지 :않습니다.goto 명령문 합니다.

어떤 종류의 goto솔루션을 사용할 때마다 과거에 점프하는 코드가 나중에 의존하는 변수를 설정하지 않도록주의해야합니다. 이러한 정의를 스크립트의 맨 위 또는 바로 위로 이동해야 할 수도 있습니다 당신의 goto진술.


Lauren의 솔루션은 좋은 점과 나쁜 점에 빠지지 않고 멋지다. 당신은 내 투표를 얻었다.
Mogens TrasherDK

1
그것은 건너 뛰기와 같으며, if(false)더 이해가 되는 것 같습니다.
vesperto

2
goto "label"을 인용하면 here-doc 확장 / 평가 되지 않으므로 버그를 찾기 어려운 버그를 피할 수 있습니다 (인용되지 않은 경우 매개 변수 확장, 명령 대체 및 산술 확장이 가능합니다. 부작용). 당신은 또한 이것을 조금 조정 alias goto=":<<"하고 cat완전히 생략 할 수 있습니다.
mr.spuratic

예, vesperto, 'if'문을 사용할 수 있으며 많은 스크립트 에서이 작업을 수행했지만 매우 혼란스럽고 특히 점프 점을 자주 변경하려는 경우 제어하고 추적하기가 훨씬 어렵습니다.
Laurence Renshaw

12

다른 사람들은 이미 gotobash에 직접 동등한 기능 이 없으며 (함수, 루프 및 중단과 같은 가장 가까운 대안을 제공함) 명확하게 설명했지만 루프 플러스 break를 사용하여 특정 유형의 goto 문을 시뮬레이션하는 방법을 설명하고 싶습니다 .

이것이 가장 유용한 상황은 특정 조건이 충족되지 않으면 코드 섹션의 시작 부분으로 돌아 가야 할 때입니다. 아래 예제에서 while 루프는 ping이 테스트 IP 로의 패킷 삭제를 중지 할 때까지 계속 실행됩니다.

#!/bin/bash

TestIP="8.8.8.8"

# Loop forever (until break is issued)
while true; do

    # Do a simple test for Internet connectivity
    PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]")

    # Exit the loop if ping is no longer dropping packets
    if [ "$PacketLoss" == 0 ]; then
        echo "Connection restored"
        break
    else
        echo "No connectivity"
    fi
done

7

이 솔루션 에는 다음과 같은 문제가있었습니다.

  • 으로 끝나는 모든 코드 줄을 무차별 적으로 제거 :
  • 줄의 label:아무 곳 이나 레이블로 취급

다음은 고정 ( shell-check깨끗한) 버전입니다.


#!/bin/bash

# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
function goto
{
 local label=$1
 cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
 eval "$cmd"
 exit
}

start=${1:-start}
goto "$start"  # GOTO start: by default

#start:#  Comments can occur after labels
echo start
goto end

  # skip: #  Whitespace is allowed
echo this is usually skipped

# end: #
echo end

6

원하는 결과를 얻을 수있는 기능이 하나 더 있습니다 : command trap. 예를 들어 정리 목적으로 사용할 수 있습니다.


4

gotobash 에는 없습니다 .

다음은 trap뒤로 만 점프하는 더러운 해결 방법입니다.)

#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT

echo foo
goto trap 2> /dev/null
echo bar

산출:

$ ./test.sh 
foo
I am
here now.

이런 방식으로 사용해서는 안되며 교육 목적으로 만 사용해야합니다. 이것이 작동하는 이유는 다음과 같습니다.

trap코드 흐름의 변화를 달성하기 위해 예외 처리를 사용하고 있습니다. 이 경우 trap스크립트가 종료되는 원인을 발견하고 있습니다. 명령 goto이 존재하지 않으므로 일반적으로 스크립트를 종료하는 오류가 발생합니다. 이 오류는 잡히면되고 trap, 그리고는 2>/dev/null일반적으로 표시되는 오류 메시지를 숨 깁니다.

존재하지 않는 명령 (또는 그와 같은 다른 오류)이 동일한 트랩 명령을 실행하기 때문에 이러한 goto 구현은 확실히 신뢰할 수 없습니다. 특히, 갈 레이블을 선택할 수 없습니다.


기본적으로 실제 시나리오에서는 goto 문이 필요하지 않으며 다른 장소에 대한 임의의 호출로 코드를 이해하기 어렵 기 때문에 중복됩니다.

코드가 여러 번 호출되면 루프를 사용 continue하고 워크 플로를 사용하여 및를 사용하는 것이 좋습니다 break.

코드가 스스로 반복되면 함수를 작성하고 원하는만큼 호출하십시오.

코드가 변수 값을 기반으로 특정 섹션으로 이동해야하는 경우 case문 사용을 고려하십시오 .

긴 코드를 작은 조각으로 분리 할 수 ​​있으면 별도의 파일로 옮기고 부모 스크립트에서 호출하는 것을 고려하십시오.


이 형식과 일반 함수의 차이점은 무엇입니까?
yurenchen 2016 년

2
@yurenchen- 코드 흐름의 변화를 달성하기 위해 trap사용 exception handling하는 것으로 생각하십시오 . 이 경우 트랩은 스크립트를 유발하는 모든 것을 포착 EXIT하고 있으며, 존재하지 않는 명령을 호출하여 트리거됩니다 goto. BTW :이 인수 는 EXIT의 원인 이므로 스크립트가 종료되고 있다는 오류 메시지를 숨기 므로 인수 goto trap가 무엇이든 될 수 있습니다 . goto ignoredgoto2>/dev/null
Jesse Chisholm

2

이것은 Hubbbitus가 작성한 Judy Schmidt 스크립트를 약간 수정 한 것입니다.

이스케이프되지 않은 레이블을 스크립트에 넣으면 시스템에서 문제가 발생하여 충돌했습니다. 레이블을 이스케이프 처리하기 위해 #을 추가하면 쉽게 해결할 수 있습니다. Alexej Magura와 그들의 제안에 대해 access_granted에게 감사드립니다.

#!/bin/bash
# include this boilerplate
function goto {  
label=$1
cmd=$(sed -n "/$#label#:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}

start=${1:-"start"}

goto $start

#start#
echo "start"
goto bing

#boom#
echo boom
goto eof

#bang#
echo bang
goto boom

#bing#
echo bing
goto bang

#eof#
echo "the end mother-hugger..."

이 복사 붙여 넣기가 작동하지 않습니다. => 여전히 이동합니다.
Alexis Paques

그게 무슨 뜻이야? 작동하지 않는 것은 무엇입니까? 좀 더 구체적으로 말씀해 주시겠습니까?
thebunnyrules

물론 코드를 시도했습니다! 나는 모든 성공을 게시하기 전에 5 또는 6 가지 조건에서 테스트했습니다. 코드가 어떻게 실패 했습니까? 어떤 오류 메시지 나 코드입니까?
thebunnyrules

뭐든간에 나는 당신을 돕고 싶지만 당신은 정말로 모호하고 의사 소통을하고 있습니다 ( "당신은 그것을 테스트 했습니까?"라는 질문을 모욕하는 것은 말할 것도 없습니다). "올바르게 붙여 넣지 않았다"는 것은 무엇입니까? "/ $ # label # : / {: a; n; p; ba};"를 참조하는 경우 부분적으로 다르다는 것을 잘 알고 있습니다. 새로운 레이블 형식으로 수정했습니다 (일부 경우 스크립트를 충돌시키는 다른 대답에서는 # label # vs : label). 내 스크립트에 오류가 있거나 구문이 잘못된 구문과 같은 올바른 대답이 있습니까? 아니면 "잘라 내고 붙여 넣는 방법을 모르겠습니다"라고 생각하여 답을 -1 했습니까?
thebunnyrules

1
@ thebunnyrules 나는 당신과 같은 문제에 부딪 쳤지 만 해결책에 대해 다르게 +1을
Fabby

2

디버깅 할 때 코드 블록 주석 처리를위한 간단한 검색 가능한 goto.

GOTO=false
if ${GOTO}; then
    echo "GOTO failed"
    ...
fi # End of GOTO
echo "GOTO done"

결과는-> GOTO 완료


1

함수를 사용 하여이 작업을 수행하는 방법을 찾았습니다.

말은, 예를 들어, 3 개 개의 선택이있다 : A, B,와 C. A그리고 B명령을 실행하지만, C당신에게 더 많은 정보를 제공하고 원래의 메시지를 다시 이동합니다. 기능을 사용하여 수행 할 수 있습니다.

라인 containsg function demoFunction가 함수를 설정하기 demoFunction때문에 함수를 실제로 실행하려면 해당 스크립트 다음 에 호출해야 합니다.

다른 여러 함수를 작성하고 GOTO쉘 스크립트에서 다른 위치 를 " " 해야하는 경우이를 호출하여이를 쉽게 조정할 수 있습니다 .

function demoFunction {
        read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand

        case $runCommand in
            a|A) printf "\n\tpwd being executed...\n" && pwd;;
            b|B) printf "\n\tls being executed...\n" && ls;;
            c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;;
        esac
}

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