Có một tuyên bố goto khác trong bash?


204

Có một tuyên bố "goto" trong bash? Tôi biết Nó được coi là thực hành xấu, nhưng tôi cần đặc biệt là "goto".


4
Không, không có gotobash (ít nhất là nó nói command not foundvới tôi). Tại sao? Có thể có một cách tốt hơn để làm điều đó.
Niklas B.

153
Anh ta có thể có lý do của mình. Tôi tìm thấy câu hỏi này vì tôi muốn một gotocâu lệnh bỏ qua rất nhiều mã để gỡ lỗi một tập lệnh lớn mà không phải chờ một giờ để các tác vụ không liên quan khác nhau hoàn thành. Tôi chắc chắn không sử dụng gotomã sản xuất, nhưng để gỡ lỗi mã của tôi, nó sẽ giúp cuộc sống của tôi dễ dàng hơn rất nhiều và sẽ dễ dàng hơn khi phát hiện ra nó.
Karl Nicoll

30
@delnan Nhưng không có goto có thể làm cho một số điều phức tạp hơn. Thực sự có trường hợp sử dụng.
glglgl

70
Tôi phát ngán với huyền thoại goto này! Không có gì sai với goto! Tất cả mọi thứ bạn viết cuối cùng trở thành goto. Trong trình biên dịch, chỉ có goto. Một lý do chính đáng để sử dụng goto trong các ngôn ngữ lập trình cao hơn là ví dụ nhảy ra khỏi các vòng lặp lồng nhau một cách sạch sẽ và dễ đọc.
Alexander Czutro

25
"Tránh goto" là một quy tắc tuyệt vời. Giống như bất kỳ quy tắc nào, nó nên được học theo ba giai đoạn. Thứ nhất: tuân theo quy tắc cho đến khi đó là bản chất thứ hai. Thứ hai, học cách hiểu các lý do cho quy tắc. Thứ ba, tìm hiểu các ngoại lệ tốt cho quy tắc, dựa trên sự hiểu biết toàn diện về cách tuân theo quy tắc và lý do của quy tắc. Tránh bỏ qua các bước ở trên, điều này sẽ giống như sử dụng "goto". ;-)
Jeff Learman

Câu trả lời:


75

Không có; thấy §3.2.4 "Commands Compound" trong Bash Reference Manual để biết thông tin về các cấu trúc điều khiển mà làm tồn tại. Cụ thể, lưu ý đề cập đến breakcontinue, không linh hoạt như goto, nhưng linh hoạt hơn ở Bash so với một số ngôn ngữ và có thể giúp bạn đạt được những gì bạn muốn. (Dù bạn muốn gì ..)


9
Bạn có thể mở rộng về "linh hoạt hơn trong Bash so với một số ngôn ngữ" không?
user239558

21
@ user239558: Một số ngôn ngữ chỉ cho phép bạn breakhoặc continuetừ vòng lặp trong cùng, trong khi Bash cho phép bạn chỉ định có bao nhiêu cấp độ vòng lặp để nhảy. (Và ngay cả các ngôn ngữ cho phép bạn breakhoặc continuetừ vòng tùy ý, hầu hết các yêu cầu để được bày tỏ tĩnh - ví dụ, break foo;sẽ thoát ra khỏi vòng lặp dán nhãn foo- trong khi ở Bash nó bày tỏ động - ví dụ, break "$foo"sẽ thoát ra khỏi $foovòng lặp. )
ruakh

Tôi khuyên bạn nên xác định các hàm với các tính năng cần thiết và tính toán chúng từ các phần khác của mã.
blmayer

123

Nếu bạn đang sử dụng nó để bỏ qua một phần của tập lệnh lớn để gỡ lỗi (xem bình luận của Karl Nicoll), thì nếu false có thể là một lựa chọn tốt (không chắc là "false" luôn có sẵn, đối với tôi, nó nằm trong / bin / false) :

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

if false; then

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

fi

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

Khó khăn đến khi đến lúc lấy ra mã gỡ lỗi của bạn. Cấu trúc "nếu sai" khá đơn giản và dễ nhớ, nhưng làm thế nào để bạn tìm thấy fi phù hợp? Nếu trình chỉnh sửa của bạn cho phép bạn chặn thụt lề, bạn có thể thụt lề bị bỏ qua (sau đó bạn sẽ muốn đặt lại khi bạn hoàn thành). Hoặc một bình luận trên dòng fi, nhưng nó sẽ phải là một cái gì đó bạn sẽ nhớ, mà tôi nghi ngờ sẽ phụ thuộc rất nhiều vào lập trình viên.


6
falseluôn luôn có sẵn. Nhưng nếu bạn có một khối mã bạn không muốn thực thi, chỉ cần nhận xét nó. Hoặc xóa nó (và tìm trong hệ thống kiểm soát nguồn của bạn nếu bạn cần khôi phục nó sau).
Keith Thompson

1
Nếu khối mã quá dài để bình luận tẻ nhạt từng dòng một, hãy xem các thủ thuật này. stackoverflow.com/questions/947897/ Lời Tuy nhiên, những điều này không giúp trình soạn thảo văn bản khớp từ đầu đến cuối, vì vậy chúng không có nhiều cải tiến.
Camille Goudeseune

4
"Nếu sai" thường tốt hơn nhiều so với nhận xét mã, bởi vì nó đảm bảo rằng mã kèm theo tiếp tục là mã hợp pháp. Lý do tốt nhất để bình luận mã là khi nó thực sự cần phải xóa, nhưng có một cái gì đó cần được ghi nhớ - vì vậy nó chỉ là một nhận xét, và không còn là "mã".
Jeff Learman

1
Tôi sử dụng nhận xét '#if sai;'. Bằng cách đó tôi chỉ có thể tìm kiếm nó và tìm thấy cả phần đầu và phần cuối của phần gỡ lỗi.
Jon

Câu trả lời này không nên được nêu lên vì nó không trả lời câu hỏi của OP theo bất kỳ cách nào.
Viktor Joras

54

Nó thực sự có thể hữu ích cho một số nhu cầu gỡ lỗi hoặc trình diễn.

Tôi thấy rằng giải pháp 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

kết quả trong:

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

35
"Nhiệm vụ của tôi để làm cho bash trông giống như ngôn ngữ lắp ráp sắp hoàn thành." - Chà. Chỉ là, wow.
Brian Agnew

5
điều duy nhất tôi thay đổi là làm cho nó bắt đầu giống như vậy : start:để chúng không bị lỗi cú pháp.
Alexej Magura

5
Sẽ tốt hơn nếu bạn đã làm điều đó: cmd = $ (sed -n "/ # $ nhãn: / {: a; n; p; ba};" $ 0 | grep -v ': $') với các nhãn bắt đầu là: # bắt đầu: => điều này sẽ ngăn các lỗi tập lệnh
access_granted

2
Hừm, thực sự rất có thể là lỗi của tôi. Tôi đã chỉnh sửa bài viết. Cảm ơn bạn.
Hubbitus

2
set -xgiúp hiểu những gì đang diễn ra
John Lin

30

Bạn có thể sử dụng casetrong bash để mô phỏng một goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

sản xuất:

bar
star

4
Lưu ý rằng điều này đòi hỏi bash v4.0+. Tuy nhiên, đó không phải là một mục đích chung gotomà là một lựa chọn thông qua cho casetuyên bố.
mkuity0

3
Tôi nghĩ rằng đây sẽ là câu trả lời. tôi có một nhu cầu thực sự để đi đến để hỗ trợ tiếp tục thực thi tập lệnh, từ một hướng dẫn cụ thể. theo mọi cách, ngoại trừ ngữ nghĩa, goto, ngữ nghĩa và cú pháp đều dễ thương, nhưng không thực sự cần thiết. giải pháp tuyệt vời, IMO.
nathan g

1
@nathang, cho dù đó là những câu trả lời phụ thuộc vào việc trường hợp của bạn xảy ra để ăn khớp với các tập hợp con của các trường hợp chung OP được hỏi về. Thật không may, câu hỏi hỏi về trường hợp chung, làm cho câu trả lời này quá hẹp để có thể chính xác. (Cho dù câu hỏi đó có nên được đóng lại vì quá rộng vì lý do đó là một cuộc thảo luận khác nhau).
Charles Duffy

1
goto là nhiều hơn lựa chọn. Tính năng goto có nghĩa là có thể nhảy đến các địa điểm theo một số điều kiện, thậm chí tạo ra một vòng lặp như dòng chảy ...
Sergio Abreu

19

Nếu bạn đang kiểm tra / gỡ lỗi một tập lệnh bash và chỉ muốn bỏ qua trước một hoặc nhiều phần mã, thì đây là một cách rất đơn giản để thực hiện nó cũng rất dễ tìm và xóa sau này (không giống như hầu hết các phương thức miêu tả trên).

#!/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"

Để đưa tập lệnh của bạn trở lại bình thường, chỉ cần xóa bất kỳ dòng nào với GOTO.

Chúng ta cũng có thể làm đẹp giải pháp này bằng cách thêm một gotolệnh dưới dạng bí danh:

#!/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"

Các bí danh thường không hoạt động trong các tập lệnh bash, vì vậy chúng ta cần shoptlệnh để sửa nó.

Nếu bạn muốn có thể bật / tắt tính năng của mình goto, chúng tôi cần thêm một chút:

#!/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"

Sau đó, bạn có thể làm export DEBUG=TRUEtrước khi chạy tập lệnh.

Các nhãn là nhận xét, vì vậy sẽ không gây ra lỗi cú pháp nếu vô hiệu hóa goto(bằng cách đặt gotothành ' :' no-op), nhưng điều này có nghĩa là chúng ta cần trích dẫn chúng trong các gototuyên bố của mình.

Bất cứ khi nào sử dụng bất kỳ loại gotogiải pháp nào, bạn cần cẩn thận rằng mã bạn nhảy qua không đặt bất kỳ biến nào mà bạn dựa vào sau này - bạn có thể cần di chuyển các định nghĩa đó lên đầu tập lệnh của mình hoặc chỉ ở trên một tập lệnh gotobáo cáo của bạn .


Không đi sâu vào những điều tốt / xấu của goto, giải pháp của Laurence chỉ tuyệt vời. Bạn đã nhận được phiếu bầu của tôi.
Mogens TrasherDK

1
Điều đó giống như một sự bỏ qua, if(false)dường như có ý nghĩa hơn (với tôi).
vesperto

2
Trích dẫn "nhãn" goto cũng có nghĩa là tài liệu ở đây không được mở rộng / đánh giá giúp bạn tránh khỏi một số lỗi khó tìm (không được trích dẫn, nó sẽ phải chịu: mở rộng tham số, thay thế lệnh và mở rộng số học, tất cả đều có thể có phản ứng phụ). Bạn cũng có thể tinh chỉnh điều này một chút với alias goto=":<<"và phân phối cathoàn toàn.
mr.spuratic

vâng, vesperto, bạn có thể sử dụng câu lệnh 'if' và tôi đã thực hiện điều đó trong nhiều tập lệnh, nhưng nó rất lộn xộn và khó kiểm soát và theo dõi hơn, đặc biệt là nếu bạn muốn thay đổi điểm nhảy thường xuyên.
Laurence Renshaw

12

Mặc dù những người khác đã làm rõ rằng không có gotobash tương đương trực tiếp (và cung cấp các lựa chọn thay thế gần nhất như hàm, vòng lặp và ngắt), tôi muốn minh họa cách sử dụng vòng lặp cộng breakcó thể mô phỏng một loại câu lệnh goto cụ thể.

Tình huống mà tôi thấy điều này hữu ích nhất là khi tôi cần quay lại phần đầu của một đoạn mã nếu một số điều kiện không được đáp ứng. Trong ví dụ dưới đây, vòng lặp while sẽ chạy mãi cho đến khi ping dừng thả các gói xuống IP thử nghiệm.

#!/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

Giải pháp này có các vấn đề sau:

  • Loại bỏ bừa bãi tất cả các dòng mã kết thúc bằng một :
  • Coi label:bất cứ nơi nào trên một dòng như một nhãn

Đây là shell-checkphiên bản cố định ( sạch):


#!/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

Có thêm một khả năng để đạt được kết quả mong muốn: lệnh trap. Nó có thể được sử dụng để làm sạch mục đích chẳng hạn.


4

Không có gototrong bash.

Đây là một số cách giải quyết bẩn sử dụng trapmà chỉ nhảy ngược lại :)

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

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

Đầu ra:

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

Điều này không nên được sử dụng theo cách đó, nhưng chỉ cho mục đích giáo dục. Đây là lý do tại sao điều này hoạt động:

trapđang sử dụng xử lý ngoại lệ để đạt được sự thay đổi trong dòng mã. Trong trường hợp trapnày, việc bắt bất cứ điều gì khiến tập lệnh EXIT. Lệnh gotokhông tồn tại và do đó đưa ra một lỗi, thông thường sẽ thoát khỏi tập lệnh. Lỗi này đang bị bắt trap2>/dev/nullẩn thông báo lỗi thường được hiển thị.

Việc thực hiện goto này rõ ràng là không đáng tin cậy, vì bất kỳ lệnh không tồn tại nào (hoặc bất kỳ lỗi nào khác, theo cách đó), sẽ thực hiện cùng một lệnh bẫy. Cụ thể, bạn không thể chọn nhãn nào sẽ sử dụng.


Về cơ bản trong kịch bản thực tế, bạn không cần bất kỳ câu lệnh goto nào, chúng không cần thiết vì các cuộc gọi ngẫu nhiên đến các địa điểm khác nhau chỉ khiến mã của bạn trở nên khó hiểu.

Nếu mã của bạn được gọi nhiều lần, thì hãy xem xét sử dụng vòng lặp và thay đổi quy trình làm việc của nó để sử dụng continuebreak.

Nếu mã của bạn tự lặp lại, hãy xem xét việc viết hàm và gọi nó nhiều lần bạn muốn.

Nếu mã của bạn cần nhảy vào phần cụ thể dựa trên giá trị biến, sau đó xem xét sử dụng casecâu lệnh.

Nếu bạn có thể tách mã dài của mình thành các phần nhỏ hơn, hãy xem xét chuyển mã thành các tệp riêng biệt và gọi chúng từ tập lệnh gốc.


sự khác biệt giữa hình thức này và chức năng bình thường là gì?
yurenchen

2
@yurenchen - Hãy nghĩ về trapviệc sử dụng exception handlingđể đạt được sự thay đổi trong dòng mã. Trong trường hợp này, cái bẫy đang bắt bất cứ thứ gì gây ra tập lệnh EXIT, được kích hoạt bằng cách gọi lệnh không tồn tại goto. BTW: Đối số goto trapcó thể là bất cứ điều gì, goto ignoredbởi vì đó là gotonguyên nhân gây ra EXIT và 2>/dev/nullẩn thông báo lỗi cho biết tập lệnh của bạn đang thoát.
Jesse Chisholm

2

Đây là một chỉnh sửa nhỏ của kịch bản Judy Schmidt do Hubbbitus đưa ra.

Việc đặt các nhãn không thoát trong tập lệnh là vấn đề trên máy và khiến nó bị sập. Điều này đủ dễ để giải quyết bằng cách thêm # để thoát nhãn. Cảm ơn Alexej Magura và access_granted vì những gợi ý của họ.

#!/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..."

Bản sao dán này không hoạt động => vẫn còn jumpto.
Alexis Paques

Điều đó nghĩa là gì? Cái gì không hiệu quả? Bạn có thể đặc sắc hơn không?
thebunnyrules

Tất nhiên tôi đã thử mã! Tôi đã thử nghiệm dưới 5 hoặc 6 điều kiện khác nhau trước khi đăng tất cả thành công. Làm thế nào mã không thành công cho bạn, thông báo lỗi hoặc mã gì?
thebunnyrules

Dù gì đi nữa. Tôi muốn giúp bạn nhưng bạn thực sự mơ hồ và không truyền thông (không đề cập đến xúc phạm với câu hỏi "bạn đã kiểm tra nó" chưa). "Bạn đã không dán chính xác có nghĩa là gì"? Nếu bạn đang đề cập đến nhãn "/ $ # #: / {: a; n; p; ba};" một phần, tôi rất ý thức rằng nó khác nhau. Tôi đã sửa đổi nó cho định dạng nhãn mới (còn gọi là # nhãn # vs: nhãn trong câu trả lời khác đã bị lỗi script trong một số trường hợp). Bạn có gặp một số vấn đề hợp lệ với câu trả lời của tôi (như lỗi script hay cú pháp sai) hay bạn chỉ đơn giản là -1 câu trả lời của tôi vì bạn nghĩ "Tôi không biết cách cắt và dán"?
thebunnyrules

1
@thebunnyrules Tôi gặp vấn đề tương tự như bạn nhưng đã giải quyết nó +1
Fabby

2

Một goto có thể tìm kiếm đơn giản để sử dụng nhận xét các khối mã khi gỡ lỗi.

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

Kết quả là-> GOTO đã hoàn thành


1

Tôi tìm ra một cách để làm điều này bằng cách sử dụng các chức năng.

Say, ví dụ, bạn có 3 lựa chọn: A, B, và C. ABthực thi một lệnh, nhưng Ccung cấp cho bạn thêm thông tin và đưa bạn trở lại dấu nhắc ban đầu. Điều này có thể được thực hiện bằng cách sử dụng các chức năng.

Lưu ý rằng vì containg dòng function demoFunctionchỉ là thiết lập chức năng, bạn cần gọi demoFunctionsau tập lệnh đó để chức năng sẽ thực sự chạy.

Bạn có thể dễ dàng điều chỉnh điều này bằng cách viết nhiều hàm khác và gọi chúng nếu bạn cần " GOTO" một vị trí khác trong tập lệnh shell của mình.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.