Trong tập lệnh Bash, làm cách nào tôi có thể thoát toàn bộ tập lệnh nếu một điều kiện nhất định xảy ra?


717

Tôi đang viết một kịch bản bằng Bash để kiểm tra một số mã. Tuy nhiên, có vẻ ngớ ngẩn khi chạy thử nghiệm nếu biên dịch mã bị lỗi ngay từ đầu, trong trường hợp đó tôi sẽ hủy bỏ các thử nghiệm.

Có cách nào tôi có thể làm điều này mà không cần bọc toàn bộ tập lệnh bên trong vòng lặp while và sử dụng dấu ngắt không? Một cái gì đó giống như một dun dun dun goto?

Câu trả lời:


810

Hãy thử tuyên bố này:

exit 1

Thay thế 1bằng mã lỗi thích hợp. Xem thêm Mã thoát với ý nghĩa đặc biệt .


4
@CMCDragonkai, thường thì mọi mã khác không sẽ hoạt động. Nếu bạn không cần bất cứ điều gì đặc biệt, bạn có thể sử dụng 1một cách nhất quán. Nếu tập lệnh được chạy bởi một tập lệnh khác, bạn có thể muốn xác định bộ mã trạng thái của riêng bạn với ý nghĩa cụ thể. Ví dụ: 1== kiểm tra thất bại, 2== biên dịch thất bại. Nếu tập lệnh là một phần của cái gì đó khác, bạn có thể cần điều chỉnh mã để phù hợp với thực tiễn được sử dụng ở đó. Ví dụ: khi một phần của bộ kiểm thử chạy tự động, mã 77được sử dụng để đánh dấu một bài kiểm tra bị bỏ qua.
Michał Górny

21
không, điều này cũng đóng cửa sổ, không chỉ thoát khỏi kịch bản
Toni Leigh

7
@ToniLeigh bash không có khái niệm về "cửa sổ", có lẽ bạn đang bối rối về việc thiết lập cụ thể của bạn - ví dụ như trình giả lập thiết bị đầu cuối - làm gì.
Michael Foukarakis

3
@ToniLeigh Nếu nó đóng "cửa sổ" thì có khả năng bạn đang đặt exit #lệnh bên trong một hàm chứ không phải tập lệnh. (Trong trường hợp sử dụng return #thay thế.)
Jamie

1
@Sevenearths 0 có nghĩa là nó đã thành công, do đó exit 0thoát khỏi tập lệnh và trả về 0 (nói với các tập lệnh khác có thể sử dụng kết quả của tập lệnh này, nó đã thành công)
VictorGalisson

689

Sử dụng tập -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Tập lệnh sẽ chấm dứt sau khi dòng đầu tiên không thành công (trả về mã thoát không khác). Trong trường hợp này, lệnh-that-fail2 sẽ không chạy.

Nếu bạn đã kiểm tra trạng thái trả về của mỗi lệnh đơn lẻ, tập lệnh của bạn sẽ trông như thế này:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Với set -e, nó sẽ trông như:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Bất kỳ lệnh nào không thành công sẽ khiến toàn bộ tập lệnh bị lỗi và trả về trạng thái thoát mà bạn có thể kiểm tra bằng $? . Nếu tập lệnh của bạn quá dài hoặc bạn đang xây dựng nhiều thứ, nó sẽ trở nên khá xấu xí nếu bạn thêm kiểm tra trạng thái trả lại ở mọi nơi.


10
Với set -eBạn vẫn có thể thực hiện một số lệnh thoát với lỗi mà không dừng tập lệnh : command 2>&1 || echo $?.
Adobe

6
set -esẽ hủy bỏ tập lệnh nếu một đường ống hoặc cấu trúc lệnh trả về giá trị khác không. Ví dụ foo || barsẽ chỉ thất bại nếu cả hai foobartrả về giá trị khác không. Thông thường một tập lệnh bash được viết tốt sẽ hoạt động nếu bạn thêm set -evào lúc bắt đầu và phần bổ sung hoạt động như một kiểm tra vệ sinh tự động: hủy bỏ tập lệnh nếu có bất cứ điều gì sai.
Mikko Rantalainen

6
Nếu bạn kết hợp các lệnh với nhau, bạn cũng có thể thất bại nếu bất kỳ lệnh nào bị lỗi bằng cách đặt set -o pipefailtùy chọn.
Jake Biesinger

18
Trên thực tế, mã thành ngữ mà không set -emake || exit $?.
tripleee

4
Ngoài ra bạn có set -u. Hãy xem chế độ nghiêm ngặt bash không chính thức : set -euo pipefail.
Pablo A

236

Một anh chàng SysOps đã từng dạy tôi kỹ thuật Móng ba ngón:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Các chức năng này là * NIX OS và shell shell - mạnh mẽ. Đặt chúng ở đầu tập lệnh của bạn (bash hoặc cách khác), try()tuyên bố và mã của bạn trên.

Giải trình

(dựa trên bình luận cừu bay ).

  • yell: in tên tập lệnh và tất cả các đối số thành stderr:
    • $0 là đường dẫn đến kịch bản;
    • $* là tất cả các đối số.
    • >&2có nghĩa là >chuyển hướng stdout đến & pipe2 . ống1 sẽ là stdoutchính nó.
  • diethực hiện tương tự như yell, nhưng thoát với trạng thái thoát không 0 , có nghĩa là không thành công.
  • trysử dụng ||(boolean OR), chỉ đánh giá bên phải nếu bên trái thất bại.
    • $@là tất cả các đối số một lần nữa, nhưng khác nhau .

3
Tôi còn khá mới với unix scripting. Bạn có thể giải thích làm thế nào các chức năng trên được thực hiện? Tôi đang thấy một số cú pháp mới mà tôi không quen thuộc. Cảm ơn.
kaizenCoder 17/05/2015

15
la lên : $0là đường dẫn đến kịch bản. $*là tất cả các đối số. >&2có nghĩa là >chuyển hướng stdout sang &đường ống 2. ống 1 sẽ là thiết bị xuất chuẩn. vì vậy hãy hét tiền tố tất cả các đối số với tên tập lệnh và in ra stderr. die làm tương tự như la hét , nhưng thoát ra với trạng thái thoát 0, điều đó có nghĩa là thất bại. hãy thử sử dụng boolean hoặc ||, chỉ đánh giá bên phải nếu bên trái không thất bại. $@là tất cả các đối số một lần nữa, nhưng khác nhau . hy vọng điều đó giải thích mọi thứ
cừu bay

1
Tôi đã sửa đổi điều này để die() { yell "$1"; exit $2; }bạn có thể chuyển một tin nhắn và thoát mã die "divide by zero" 115.
Đánh dấu Lakata

3
Tôi có thể thấy cách tôi sử dụng yelldie. Tuy nhiên, trykhông quá nhiều. Bạn có thể cung cấp một ví dụ về bạn sử dụng nó?
kshenoy

2
Hmm, nhưng làm thế nào để bạn sử dụng chúng trong một kịch bản? Tôi không hiểu ý của bạn là gì khi "thử () câu lệnh và mã của bạn trên".
TheJavaGuy-Ivan Milosavljević

33

Nếu bạn sẽ gọi tập lệnh với source, bạn có thể sử dụng return <x>ở đâu<x> sẽ là tình trạng kịch bản exit (sử dụng một khác không xứng với lỗi hoặc sai). Nhưng nếu bạn gọi một tập lệnh thực thi (nghĩa là trực tiếp với tên tệp của nó), câu lệnh return sẽ dẫn đến một khiếu nại (thông báo lỗi "return: chỉ có thể 'return' từ một hàm hoặc tập lệnh có nguồn gốc").

Nếu exit <x>được sử dụng thay thế, khi tập lệnh được gọi với source, nó sẽ dẫn đến việc thoát khỏi trình bao khởi động tập lệnh, nhưng tập lệnh thực thi sẽ chỉ chấm dứt, như mong đợi.

Để xử lý một trong hai trường hợp trong cùng một tập lệnh, bạn có thể sử dụng

return <x> 2> /dev/null || exit <x>

Điều này sẽ xử lý bất kỳ lời mời nào có thể phù hợp. Đó là giả sử bạn sẽ sử dụng tuyên bố này ở cấp cao nhất của tập lệnh. Tôi sẽ khuyên không nên trực tiếp thoát khỏi tập lệnh từ trong một hàm.

Lưu ý: <x>được cho là chỉ là một con số.


Không hoạt động đối với tôi trong tập lệnh có trả về / thoát bên trong hàm, nghĩa là có thể thoát bên trong hàm mà không cần trình bao, nhưng không làm cho trình gọi hàm quan tâm kiểm tra chính xác mã trả về của hàm ?
Tháng Một

@jan Những gì người gọi thực hiện (hoặc không làm) với các giá trị trả về, hoàn toàn trực giao (nghĩa là độc lập) với cách bạn trả về từ hàm (... w / o thoát khỏi trình bao, bất kể lệnh gọi nào). Nó chủ yếu phụ thuộc vào mã người gọi, không phải là một phần của Q & A này. Bạn thậm chí có thể điều chỉnh giá trị trả về của hàm theo nhu cầu của người gọi, nhưng câu trả lời này không giới hạn giá trị trả về này có thể là ...
kavadias

11

Tôi thường bao gồm một hàm gọi là run () để xử lý lỗi. Mọi cuộc gọi tôi muốn thực hiện đều được chuyển đến chức năng này để toàn bộ tập lệnh thoát khi gặp lỗi. Ưu điểm của giải pháp này so với giải pháp set -e là tập lệnh không thoát ra một cách im lặng khi một dòng bị lỗi và có thể cho bạn biết vấn đề là gì. Trong ví dụ sau, dòng thứ 3 không được thực thi do tập lệnh thoát khỏi lệnh gọi thành false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

1
Man, vì một số lý do, tôi thực sự thích câu trả lời này. Tôi nhận ra nó phức tạp hơn một chút, nhưng nó có vẻ rất hữu ích. Và cho rằng tôi không phải là chuyên gia bash, điều đó khiến tôi tin rằng logic của tôi bị lỗi, và có điều gì đó không ổn với phương pháp này, nếu không, tôi cảm thấy những người khác sẽ khen ngợi nó nhiều hơn. Vì vậy, vấn đề với chức năng này là gì? Có bất cứ điều gì tôi nên tìm kiếm ở đây?

Tôi không nhớ lý do của mình khi sử dụng eval, chức năng hoạt động tốt với cmdDefput = $ ($ 1)
Joseph Sheedy

Tôi chỉ thực hiện điều này như là một phần của quy trình triển khai phức tạp và nó hoạt động rất tuyệt vời. Cảm ơn bạn và đây là một bình luận và một upvote.
nhóm mờ

Công việc thực sự tuyệt vời! Đây là giải pháp đơn giản và sạch nhất hoạt động tốt. Đối với tôi, tôi đã thêm điều này trước một lệnh trong vòng lặp FOR vì các vòng lặp FOR sẽ không nhận set -e option. Sau đó, lệnh, vì nó là với các đối số, tôi đã sử dụng các dấu ngoặc đơn để tránh các vấn đề bash như vậy : runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Lưu ý tôi đã thay đổi tên của hàm thành runTry.
Tony-Caffe

1
evalcó khả năng nguy hiểm nếu bạn chấp nhận đầu vào tùy ý, nhưng nếu không thì điều này có vẻ khá tốt.
dragon788

5

Thay vì ifxây dựng, bạn có thể tận dụng đánh giá ngắn mạch :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Lưu ý cặp dấu ngoặc đơn cần thiết vì mức độ ưu tiên của toán tử thay thế. $?là một biến đặc biệt được thiết lập để thoát mã của lệnh được gọi gần đây nhất.


1
Nếu tôi làm command -that --fails || exit $?nó hoạt động mà không có dấu ngoặc đơn, thì điều gì echo $[4/0]khiến chúng ta cần chúng?
Anentropic

2
@Anentropic @skalee Các dấu ngoặc đơn không liên quan gì đến quyền ưu tiên, nhưng xử lý ngoại lệ. Một phép chia cho 0 sẽ tạo ra một lối thoát ngay lập tức khỏi trình bao với mã 1. Nếu không có dấu ngoặc đơn (nghĩa là một bash đơn giản echo $[4/0] || exit $?) sẽ không bao giờ thực thi lệnh , hãy để một mình tuân theo . echo||
bobbogo

1

Tôi có cùng một câu hỏi nhưng không thể hỏi nó vì nó sẽ là một bản sao.

Câu trả lời được chấp nhận, sử dụng exit, không hoạt động khi tập lệnh phức tạp hơn một chút. Nếu bạn sử dụng quy trình nền để kiểm tra điều kiện, thoát chỉ thoát khỏi quy trình đó, vì nó chạy trong vỏ phụ. Để giết tập lệnh, bạn phải giết nó một cách rõ ràng (ít nhất đó là cách duy nhất tôi biết).

Đây là một kịch bản nhỏ về cách làm điều đó:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Đây là một câu trả lời tốt hơn nhưng vẫn chưa hoàn chỉnh. Tôi thực sự không biết làm thế nào để thoát khỏi phần bùng nổ .

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.