Làm thế nào để thoát nếu một lệnh thất bại?


222

Tôi là một người mới trong kịch bản shell. Tôi muốn in một tin nhắn và thoát khỏi tập lệnh của mình nếu một lệnh thất bại. Tôi đã thử:

my_command && (echo 'my_command failed; exit)

Nhưng nó không hoạt động. Nó tiếp tục thực hiện các hướng dẫn theo dòng này trong kịch bản. Tôi đang sử dụng Ubuntu và bash.


5
Bạn có dự định trích dẫn không được tiết lộ là một lỗi cú pháp sẽ gây ra lỗi / thoát không? nếu không, bạn nên đóng trích dẫn trong ví dụ của bạn.
hobs

Câu trả lời:


406

Thử:

my_command || { echo 'my_command failed' ; exit 1; }

Bốn thay đổi:

  • Thay đổi &&thành||
  • Sử dụng { }thay thế( )
  • Giới thiệu ;sau exit
  • không gian sau {và trước}

Vì bạn muốn in thông báo và chỉ thoát khi lệnh thất bại (thoát với giá trị khác không), bạn cần ||không phải là một &&.

cmd1 && cmd2

sẽ chạy cmd2khi cmd1thành công (giá trị thoát 0). Trong khi

cmd1 || cmd2

sẽ chạy cmd2khi cmd1không thành công (giá trị thoát khác không).

Việc sử dụng ( )làm cho lệnh bên trong chúng chạy trong lớp vỏ phụ và gọi exittừ đó khiến bạn thoát khỏi lớp vỏ phụ chứ không phải lớp vỏ ban đầu của bạn, do đó việc thực thi tiếp tục trong lớp vỏ ban đầu của bạn.

Để khắc phục việc sử dụng này { }

Hai thay đổi cuối cùng được yêu cầu bởi bash.


4
Nó dường như được "đảo ngược". Nếu một hàm "thành công", nó trả về 0 và nếu "thất bại" thì nó trả về giá trị khác không, do đó && có thể được dự kiến ​​sẽ đánh giá khi nửa đầu tiên trả về khác không. Điều đó không có nghĩa là câu trả lời ở trên là không chính xác - không có nó là chính xác. && và || trong các kịch bản hoạt động dựa trên thành công không dựa trên giá trị trả về.
CashCow

7
Nó dường như đảo ngược, nhưng đọc nó ra và nó có ý nghĩa: "thực hiện lệnh này (thành công)" HOẶC "in lỗi này và thoát"
đơn giản

2
Logic đằng sau nó là ngôn ngữ sử dụng đánh giá ngắn mạch (SCE). Với SCE, f biểu thức có dạng "p OR q" và p được đánh giá là đúng, sau đó không có lý do gì để nhìn vào q. Nếu biểu thức có dạng "p VÀ q" và p được đánh giá là sai, không có lý do để xem xét q. Lý do cho việc này là hai lần: 1) nhanh hơn, vì lý do rõ ràng và 2) nó tránh được một số loại lỗi nhất định (ví dụ: "if x! = 0 VÀ 10 / x> 5" sẽ bị sập nếu không có SCE ). Khả năng sử dụng nó trong dòng lệnh như thế này là một hiệu ứng phụ hạnh phúc.
dùng2635263

2
Tôi muốn giới thiệu tiếng vang cho STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop

127

Các câu trả lời khác đã bao gồm tốt câu hỏi trực tiếp, nhưng bạn cũng có thể quan tâm đến việc sử dụng set -e. Cùng với đó, bất kỳ lệnh nào không thành công (bên ngoài các bối cảnh cụ thể như ifcác bài kiểm tra) sẽ khiến tập lệnh bị hủy bỏ. Đối với các tập lệnh nhất định, nó rất hữu ích.


3
Thật không may, nó cũng rất nguy hiểm - set -ecó một bộ quy tắc dài và phức tạp về thời điểm nó hoạt động và khi nào nó không hoạt động. (Nếu bất cứ ai chạy if yourfunction; then ..., thì set -esẽ không bao giờ bắn vào bên trong yourfunctionhoặc bất cứ thứ gì nó gọi, bởi vì mã đó được coi là "đã kiểm tra"). Xem phần bài tập của BashFAQ # 105 , thảo luận về điều này và các cạm bẫy khác (một số trong đó chỉ áp dụng cho các phiên bản shell cụ thể, vì vậy bạn cần chắc chắn kiểm tra mọi bản phát hành có thể được sử dụng để chạy tập lệnh của mình).
Charles Duffy

67

Cũng lưu ý, trạng thái thoát của mỗi lệnh được lưu trữ trong biến shell $?, Mà bạn có thể kiểm tra ngay sau khi chạy lệnh. Trạng thái khác không chỉ ra thất bại:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi

10
Điều này chỉ có thể được thay thế bằng nếu my_command, không cần sử dụng lệnh kiểm tra ở đây.
Bart Sas

5
+1 vì tôi nghĩ rằng bạn không nên bị trừng phạt vì liệt kê thay thế này - nó phải ở trên bàn - mặc dù nó hơi xấu và theo kinh nghiệm của tôi quá dễ dàng để thực hiện một lệnh khác ở giữa mà không nhận thấy bản chất của bài kiểm tra (có thể tôi m chỉ là ngu ngốc).
Tony Delroy

1
@BartSas Nếu lệnh dài, tốt hơn là đặt nó trên dòng riêng của nó. Nó làm cho kịch bản dễ đọc hơn.
Michael

@Michael, không phải nếu nó tạo ra sự phụ thuộc giữa các dòng, nơi bạn cần biết những gì đã xảy ra trên dòng A trước khi bạn có thể hiểu dòng B (hoặc tệ hơn, cần biết những gì sẽ xảy ra trong dòng B trước khi bạn biết rằng nó an toàn để thêm một cái gì đó mới sau khi kết thúc dòng A). Tôi đã thấy quá nhiều lần ai đó thêm vào echo "Just finished step A", không biết rằng echoghi đè lên cái $?sẽ được kiểm tra sau đó.
Charles Duffy

67

Nếu bạn muốn hành vi đó cho tất cả các lệnh trong tập lệnh của mình, chỉ cần thêm

  set -e 
  set -o pipefail

ở đầu kịch bản. Cặp tùy chọn này báo cho trình thông dịch bash thoát bất cứ khi nào lệnh trả về với mã thoát khác không.

Điều này không cho phép bạn in một thông báo thoát, mặc dù.


8
Bạn có thể chạy các lệnh khi thoát bằng lệnh trapbash tích hợp.
Gavin Smith

Đây là câu trả lời cho câu hỏi XY.
jwg

5
Có thể thú vị để giải thích những gì các lệnh làm độc lập hơn là cặp. Đặc biệt là vì set -o pipefailcó thể không phải là hành vi mong muốn. Vẫn cảm ơn vì đã chỉ ra nó!
Antoine Pinsard

3
set -ehoàn toàn không đáng tin cậy, nhất quán hoặc có thể dự đoán được! Để thuyết phục bản thân về điều đó, hãy xem lại phần bài tập của BashFAQ # 105 hoặc bảng so sánh các hành vi của các loại đạn khác nhau tại in-ulm.de/~mascheck/various/set-e
Charles Duffy

14

Tôi đã hack thành ngữ sau:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Đặt trước mỗi lệnh với một tiếng vang thông tin và theo sau mỗi lệnh với cùng một
if [ $? -ne 0 ];...dòng. (Tất nhiên, bạn có thể chỉnh sửa thông báo lỗi đó nếu bạn muốn.)


Điều này có thể được định nghĩa là một chức năng, do đó sử dụng lại nó.
Eric Wang

1
'nếu [$? -Một 0]; thì ... fi 'là một cách phức tạp để viết' || '
kevin cline

if ! javac *.java; then ...- tại sao phải bận tâm với $?tất cả?
Charles Duffy

Charles - bởi vì tôi là một người viết kịch bản bash tầm thường nhất :)
Grant Birchmeier

10

Được cung cấp my_commandđược thiết kế theo quy tắc, tức là trả về 0 khi thành công, sau đó &&hoàn toàn ngược lại với những gì bạn muốn. Bạn muốn ||.

Cũng lưu ý rằng điều (đó dường như không đúng với tôi trong bash, nhưng tôi không thể thử từ nơi mình đang ở. Nói với tôi.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}

IIRC, bạn cần có dấu chấm phẩy sau mỗi dòng bên trong {}
Alex Howansky

9
@Alex: không nếu chúng nằm trên một dòng riêng biệt.
Tạm dừng cho đến khi có thông báo mới.

5

Bạn cũng có thể sử dụng, nếu bạn muốn duy trì trạng thái lỗi thoát và có một tệp có thể đọc được với một lệnh trên mỗi dòng:

my_command1 || exit $?
my_command2 || exit $?

Điều này, tuy nhiên sẽ không in bất kỳ thông báo lỗi bổ sung. Nhưng trong một số trường hợp, lỗi sẽ được in bằng lệnh thất bại.


1
Không có lý do cho nó là exit $?; chỉ exitsử dụng $?như giá trị mặc định của nó.
Charles Duffy

@CharlesDuffy không biết. Tuyệt vời, vì vậy nó thậm chí còn đơn giản hơn!
alexpirine

3

Các trapBUILTIN vỏ cho phép các tín hiệu bắt, và điều kiện hữu ích khác, bao gồm thực hiện thất bại lệnh (ví dụ, một tình trạng lợi nhuận khác không). Vì vậy, nếu bạn không muốn kiểm tra rõ ràng trạng thái trả về của mỗi lệnh đơn lẻ bạn có thể nói trap "your shell code" ERRvà mã shell sẽ được thực thi bất cứ khi nào lệnh trả về trạng thái khác không. Ví dụ:

trap "echo script failed; exit 1" ERR

Lưu ý rằng cũng như các trường hợp khác bắt các lệnh không thành công, các đường ống cần được xử lý đặc biệt; ở trên sẽ không bắt được false | true.

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.