Hủy bỏ tập lệnh shell nếu bất kỳ lệnh nào trả về giá trị khác không?


437

Tôi có một kịch bản shell Bash gọi một số lệnh. Tôi muốn có kịch bản shell tự động thoát với giá trị trả về là 1 nếu bất kỳ lệnh nào trả về giá trị khác không.

Đây có phải là có thể mà không cần kiểm tra rõ ràng kết quả của mỗi lệnh?

ví dụ

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi

8
Ngoài ra set -e, cũng làm set -u(hoặc set -eu). -uchấm dứt hành vi che giấu lỗi ngu ngốc mà bạn có thể truy cập vào bất kỳ biến không tồn tại nào và có một giá trị trống được tạo ra mà không có chẩn đoán.
Kaz

Câu trả lời:


742

Thêm phần này vào phần đầu của tập lệnh:

set -e

Điều này sẽ khiến shell thoát ngay lập tức nếu một lệnh đơn giản thoát ra với giá trị thoát khác. Lệnh đơn giản là bất kỳ lệnh nào không phải là một phần của if, while hoặc cho đến khi kiểm tra hoặc một phần của && hoặc || danh sách.

Xem trang man bash (1) trên lệnh nội bộ "set" để biết thêm chi tiết.

Cá nhân tôi bắt đầu gần như tất cả các tập lệnh shell với "set -e". Thật khó chịu khi có một kịch bản ngoan cố tiếp tục khi một cái gì đó thất bại ở giữa và phá vỡ các giả định cho phần còn lại của kịch bản.


36
Điều đó sẽ hoạt động, nhưng tôi thích sử dụng "#! / Usr / bin / env bash" vì tôi thường xuyên chạy bash từ một nơi khác ngoài / bin. Và "#! / Usr / bin / env bash -e" không hoạt động. Ngoài ra, thật tuyệt khi có một nơi để sửa đổi để đọc "set -xe" khi tôi muốn bật theo dõi để gỡ lỗi.
Ville Laurikari

48
Ngoài ra, các cờ trên dòng shebang bị bỏ qua nếu tập lệnh được chạy dưới dạng bash script.sh.
Tom Anderson

27
Chỉ cần một lưu ý: Nếu bạn khai báo các hàm bên trong tập lệnh bash, các hàm sẽ cần phải được thiết lập lại -e bên trong thân hàm nếu bạn muốn mở rộng chức năng này.
Jin Kim

8
Ngoài ra, nếu bạn lấy tập lệnh của mình, dòng shebang sẽ không liên quan.

4
@JinKim Điều đó dường như không xảy ra trong bash 3.2.48. Hãy thử như sau trong một kịch bản : set -e; tf() { false; }; tf; echo 'still here'. Ngay cả khi không có set -ebên trong cơ thể tf(), hành quyết bị hủy bỏ. Có lẽ bạn muốn nói rằng điều đó set -ekhông được kế thừa bởi các subshells , đó là sự thật.
mkuity0

202

Để thêm vào câu trả lời được chấp nhận:

Hãy nhớ rằng set -eđôi khi không đủ, đặc biệt nếu bạn có đường ống.

Ví dụ: giả sử bạn có tập lệnh này

#!/bin/bash
set -e 
./configure  > configure.log
make

... Hoạt động như mong đợi: một lỗi trong việc configurehủy bỏ việc thực thi.

Ngày mai bạn thực hiện một thay đổi có vẻ tầm thường:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... và bây giờ nó không hoạt động. Điều này được giải thích ở đây và một cách giải quyết (chỉ Bash) được cung cấp:

#! / bin / bash
đặt -e 
thiết lập đường ống

./mình | tee configure.log
làm

1
Cảm ơn bạn đã giải thích tầm quan trọng của việc pipefailphải đi cùng set -o!
Malcolm

83

Các câu lệnh if trong ví dụ của bạn là không cần thiết. Chỉ cần làm như thế này:

dosomething1 || exit 1

Nếu bạn sử dụng lời khuyên của Ville Laurikari và sử dụng set -ethì đối với một số lệnh bạn có thể cần sử dụng lệnh này:

dosomething || true

Các || truesẽ làm cho đường ống lệnh có truegiá trị trả về ngay cả khi lệnh bị lỗi nên các -etùy chọn sẽ không giết kịch bản.


1
Tôi thích điều này. Đặc biệt bởi vì câu trả lời hàng đầu là bash-centric (hoàn toàn không rõ ràng đối với tôi cho dù / đến mức độ nào nó áp dụng cho kịch bản zsh). Và tôi có thể tra cứu nó, nhưng bạn thì rõ ràng hơn, bởi vì logic.
g33kz0r

set -ekhông phải là bash-centric - nó được hỗ trợ ngay cả trên Bourne Shell ban đầu.
Marcos Vives Del Sol

27

Nếu bạn đã dọn dẹp, bạn cần thực hiện khi thoát, bạn cũng có thể sử dụng 'bẫy' với ERR tín hiệu giả. Điều này hoạt động tương tự như bẫy INT hoặc bất kỳ tín hiệu nào khác; bash ném ERR nếu bất kỳ lệnh nào thoát với giá trị khác không:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Hoặc, đặc biệt nếu bạn đang sử dụng "set -e", bạn có thể bẫy EXIT; Bẫy của bạn sau đó sẽ được thực thi khi tập lệnh thoát vì bất kỳ lý do gì, bao gồm cả kết thúc bình thường, ngắt, thoát do tùy chọn -e, v.v.


12

Các $?biến hiếm khi cần thiết. Thành ngữ giả command; if [ $? -eq 0 ]; then X; finên luôn luôn được viết là if command; then X; fi.

Các trường hợp $?được yêu cầu là khi cần kiểm tra đối với nhiều giá trị:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

hoặc khi $?cần được tái sử dụng hoặc thao tác khác:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi

3
Tại sao "nên luôn luôn được viết là"? Ý tôi là, tại sao " nên " nó lại như vậy? Khi một lệnh dài (nghĩ rằng gọi GCC với hàng tá tùy chọn), thì việc chạy lệnh trước khi kiểm tra trạng thái trả về sẽ dễ đọc hơn nhiều.
ysap

Nếu một lệnh quá dài, bạn có thể phá vỡ nó bằng cách đặt tên cho nó (xác định hàm shell).
Mark Edgar

12

Chạy nó với -ehoặc set -eở trên cùng.

Cũng nhìn vào set -u.


34
Để có khả năng cứu người khác, bạn cần phải đọc qua help set: -ucoi các tham chiếu đến các biến không đặt là lỗi.
mkuity0

1
Vì vậy, đó là set -uhoặc set -e, không phải cả hai? @lumpynose
ericn

1
@eric Tôi đã nghỉ hưu vài năm trước. Mặc dù tôi yêu công việc của mình nhưng bộ não già nua của tôi đã quên mọi thứ. Tự tay tôi đoán rằng bạn có thể sử dụng cả hai cùng nhau; từ ngữ xấu về phía tôi; Tôi nên nói "và / hoặc".
lumpynose

3

Một biểu hiện như

dosomething1 && dosomething2 && dosomething3

sẽ dừng xử lý khi một trong các lệnh trả về với giá trị khác không. Ví dụ: lệnh sau sẽ không bao giờ in "xong":

cat nosuchfile && echo "done"
echo $?
1


-2

chỉ cần đưa vào một câu hỏi khác để tham khảo vì có thêm một câu hỏi cho đầu vào của Mark Edgars và đây là một ví dụ bổ sung và chạm vào chủ đề tổng thể:

[[ `cmd` ]] && echo success_else_silence

giống cmd || exit errcodenhư ai đó đã chỉ ra

ví dụ. Tôi muốn đảm bảo rằng một phân vùng không được đếm nếu được gắn kết:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 

5
Không, [[ cmd`]] `không giống nhau. Sẽ sai nếu đầu ra của lệnh trống và đúng, bất kể trạng thái thoát của lệnh.
Gilles 'SO- ngừng trở nên xấu xa'
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.