Làm cách nào để tập lệnh này thoát lỗi dựa trên kết quả của vòng lặp for?


13

Tôi có một tập lệnh bash sử dụng set -o errexitđể có lỗi khi toàn bộ tập lệnh thoát ra tại điểm bị lỗi.
Tập lệnh chạy một curllệnh đôi khi không thể truy xuất tệp dự định - tuy nhiên khi điều này xảy ra, tập lệnh không thoát lỗi.

Tôi đã thêm một forvòng lặp để

  1. Tạm dừng vài giây rồi thử lại curllệnh
  2. sử dụng falseở dưới cùng của vòng lặp for để xác định trạng thái thoát không bằng 0 mặc định - nếu lệnh curl thành công - vòng lặp bị phá vỡ và trạng thái thoát của lệnh cuối cùng sẽ bằng không.
#! /bin/bash

set -o errexit

# ...

for (( i=1; i<5; i++ ))
do
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    if [ -f ~/.vim/autoload/pathogen.vim ]
    then
        echo "file has been retrieved by curl, so breaking now..."
        break;
    fi

    echo "curl'ed file doesn't yet exist, so now will wait 5 seconds and retry"
    sleep 5
    # exit with non-zero status so main script will errexit
    false

done

# rest of script .....

Vấn đề là khi curllệnh thất bại, vòng lặp thử lại lệnh năm lần - nếu tất cả các lần thử đều không thành công, vòng lặp for kết thúc và tập lệnh chính sẽ tiếp tục - thay vì kích hoạt lệnh errexit.
Làm cách nào tôi có thể lấy toàn bộ tập lệnh để thoát nếu curlcâu lệnh này không thành công?

Câu trả lời:


18

Thay thế:

done

với:

done || exit 1

Điều này sẽ khiến mã thoát ra nếu forvòng lặp thoát với mã thoát khác không.

Như là một điểm đố, sự 1trong exit 1là không cần thiết. Một exitlệnh đơn giản sẽ thoát với trạng thái thoát của lệnh được thực thi cuối cùng sẽ là false(code = 1) nếu tải xuống thất bại. Nếu quá trình tải xuống thành công, mã thoát của vòng lặp là mã thoát của echolệnh. echothường thoát với mã = ​​0, thành công về mặt pháp lý. Trong trường hợp đó, lệnh ||không kích hoạt và exitlệnh không được thực thi.

Cuối cùng, lưu ý rằng set -o errexitcó thể đầy bất ngờ. Để thảo luận về ưu và nhược điểm của nó, hãy xem Câu hỏi thường gặp của Greg # 105 .

Tài liệu

Từ man bash:

cho ((expr1; expr2; expr3)); làm danh sách; thực hiện
Đầu tiên, biểu thức số học expr1 được đánh giá theo các quy tắc được mô tả dưới đây theo ĐÁNH GIÁ ARITHMETIC. Biểu thức số học expr2 sau đó được đánh giá lặp đi lặp lại cho đến khi nó ước lượng bằng không. Mỗi lần expr2 ước tính giá trị khác không, danh sách được thực thi và biểu thức số học expr3 được ước tính. Nếu bất kỳ biểu thức nào bị bỏ qua, nó sẽ hoạt động như thể nó ước tính là 1. Giá trị trả về là trạng thái thoát của lệnh cuối cùng trong danh sách được thực thi hoặc sai nếu bất kỳ biểu thức nào không hợp lệ. [Nhấn mạnh thêm]


Bạn có nghĩ rằng sẽ là một ý tưởng tốt để đặt truetrước câu lệnh break rõ ràng và đảm bảo giá trị thoát của vòng lặp không?
RobertL

1
Tôi nghĩ rằng rõ ràng là tốt hơn ngầm . Đó là lý do tại sao tôi viết exit 1khi chỉ đơn giản là exitsẽ làm việc. Tuy nhiên, đó là một câu hỏi về phong cách và những người khác có thể có ý kiến ​​riêng của họ.
John1024

1
hoạt động độc đáo! cảm ơn :) cá nhân tôi sẽ đọc exitnhư một lối thoát đơn giản - điều đó chấm dứt kịch bản theo đúng nghĩa của nó. exit 1 sẽ đọc cho tôi như là một "tín hiệu" cho một quá trình khác (tức là errexit) - rằng nó sẽ chấm dứt tập lệnh dựa trên "kết quả" của exit 1. - vì vậy tôi đã đi cùng exitnhưng cảm ơn vì đã giải thích
the_velour_fog 6/11/2015

1
Nếu tập lệnh của bạn đang thoát do một điều kiện lỗi, bạn nên gọi exit 1. Điều đó hoàn toàn không ảnh hưởng errexit. Nó chỉ đơn thuần nói với chương trình gọi rằng có gì đó không ổn. Các falselệnh chứa một tuyên bố: exit(1). 99,9% các lệnh Unix trả về 0 khi thành công và không phải là không có lỗi. Bạn cũng nên như vậy.
RobertL

2

Nếu bạn đã errexitthiết lập, thì falsecâu lệnh sẽ khiến tập lệnh thoát ngay lập tức. Điều tương tự nếu curllệnh thất bại.

Kịch bản ví dụ của bạn, như được viết, sẽ thoát sau curllỗi lệnh đầu tiên trong lần gọi đầu tiên falsenếu errexit được đặt.

Để xem nó hoạt động như thế nào (tôi sử dụng tốc ký -eđể đặt errexit:

$ ( set -e;  false; echo still here )
$

$ ( set +e;  false; echo still here )
still here
$

Vì vậy, nếu curllệnh thực thi nhiều lần, tập lệnh này không được errexitđặt.


1
set -ecòn tinh tế hơn thế. Nó sẽ không thoát sau lệnh thất bại đầu tiên trong một vòng lặp. Bạn có thể chứng minh điều đó cho chính mình bằng cách chạy (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )và lưu ý rằng mã chạy falsebốn lần. Để biết thêm về set -e, xem Câu hỏi thường gặp của Greg # 105 .
John1024

@ John1024 Cảm ơn. Cái này đi xuống và ra ngoài.
RobertL

@ John1024 Nhưng tôi đoán bằng chứng vẫn chưa errexitđược đặt ra. Vui lòng áp dụng logic cho kịch bản trong câu hỏi. Chạy cái này: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Có kiểm tra các giá trị trả về với if while || &&vv không kích hoạt errexit. Kịch bản gốc không phải ||là vòng lặp for.
RobertL

Tôi chỉ nhận thấy rằng tôi đã không hiển thị set -o errexitlệnh trong mã ví dụ của mình, đã thêm nó ngay bây giờ - và đối với tôi nó không bị lỗi như mong đợi. Tôi cần phải giữ falselệnh cuối cùng trong vòng lặp for, sau đó đóng vòng lặp với done || exit [1]- sau đó nó hoạt động tốt!
the_velour_fog 6/11/2015

@RobertL Tôi thấy quan điểm của bạn.
John1024

1

set -o errexit có thể là khó khăn trong các vòng lặp và subshells, bởi vì bạn phải vượt qua khỏi quá trình.

Phá vỡ một vòng lặp (ngay cả trong hoạt động bình thường) được coi là thực hành xấu. Bạn có thể gọi tôi là trường học cũ để thích vòng lặp while hơn là vòng lặp for cho hai điều kiện, nhưng tôi thấy tốt hơn khi đọc:

i=1
RET=-1
while [ $i -le 5 ] && [ $RET -ne 0 ]; do
    [ $i -eq 1 ] || sleep 5
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    RET=$?
    i=$((i+1))
done
exit $RET

0

Nếu errexitđược đặt và curllệnh không thành công, tập lệnh kết thúc ngay sau khi lệnh curl không thành công. Trong hướng dẫn bash, không có gợi ý nào set -ebỏ qua bất kỳ trạng thái trả về thất bại nào của một lệnh trong lệnh ghép. Đây chỉ là trường hợp nếu lệnh ghép được thực thi trong ngữ cảnh set -ebị bỏ qua.
https://www.gnu.org/software/bash/manual/bash.html#The-set-Builtin

Hãy thử một ví dụ thích nghi một chút được đăng bởi RobertL. Điều này dừng ở lần lặp đầu tiên ngay sau lệnh sai:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )

0

Bạn có thể chỉ cần thêm tùy chọn --fail vào lệnh curl, điều này sẽ giải quyết vấn đề của bạn, tập lệnh sẽ thất bại và thoát lỗi nếu lệnh curl thất bại, nếu cũng rất hữu ích khi sử dụng curl trong đường ống jenkins:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
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.