Lỗi bẫy trong thay thế lệnh bằng cách sử dụng lỗi -o errtrace '(tức là đặt -E)


14

Theo hướng dẫn tham khảo này :

-E (cũng -o lỗi)

Nếu được đặt, bất kỳ bẫy nào trên ERR đều được kế thừa bởi các hàm shell, thay thế lệnh và các lệnh được thực thi trong môi trường lớp con. Bẫy ERR thường không được kế thừa trong những trường hợp như vậy.

Tuy nhiên, tôi phải hiểu sai, vì những điều sau đây không hoạt động:

#!/usr/bin/env bash
# -*- bash -*-

set -e -o pipefail -o errtrace -o functrace

function boom {
  echo "err status: $?"
  exit $?
}
trap boom ERR


echo $( made up name )
echo "  ! should not be reached ! "

Tôi đã biết chuyển nhượng đơn giản my_var=$(made_up_name), sẽ thoát khỏi tập lệnh với set -e(ví dụ errexit).

Được -E/-o errtracecho là làm việc như mã trên? Hoặc, rất có thể, tôi đọc sai nó?


2
Đây là một câu hỏi hay. Thay thế echo $( made up name )bằng $( made up name )sản xuất các hành vi mong muốn. Tôi không có một lời giải thích mặc dù.
iruvar

Tôi không biết về bash's -E nhưng tôi biết rằng -e chỉ ảnh hưởng đến việc thoát shell nếu lỗi xảy ra từ lệnh cuối cùng trong một đường ống. Vì vậy var=$( pipe ), $( pipe )ví dụ của bạn và cả hai sẽ đại diện cho các điểm cuối đường ống trong khi pipe > echothì không. Trang người đàn ông của tôi nói: "1. Sự thất bại của bất kỳ lệnh riêng lẻ nào trong đường ống đa lệnh sẽ không khiến vỏ thoát ra. Chỉ xem xét sự thất bại của đường ống."
mikeerv

Bạn có thể làm cho nó thất bại mặc dù: echo $ ($ {madeupname?}). Nhưng đó là thiết lập -e. Một lần nữa, -E nằm ngoài kinh nghiệm của riêng tôi.
mikeerv

@mikeerv @ 1_CR Hướng dẫn bash @ echo chỉ ra rằng echoluôn trả về 0. Điều này phải được thực hiện trong phân tích ...

Câu trả lời:


5

Lưu ý: zshsẽ phàn nàn về "mẫu xấu" nếu bạn không định cấu hình nó để chấp nhận "nhận xét nội tuyến" cho hầu hết các ví dụ ở đây và không chạy chúng qua trình bao proxy như tôi đã làm với sh <<-\CMD.

Ok, như tôi đã nêu trong các ý kiến ​​trên, tôi không biết cụ thể về bashset -E , nhưng tôi biết rằng các vỏ tương thích POSIX cung cấp một phương tiện đơn giản để kiểm tra giá trị nếu bạn muốn:

    sh -evx <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
        echo "echo still works" 
    }
    _test && echo "_test doesnt fail"
    # END
    CMD
sh: line 1: empty: error string
+ echo

+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail

Ở trên bạn sẽ thấy rằng mặc dù tôi đã từng parameter expansionkiểm tra ${empty?} _test()vẫn return một lượt - như đã được chứng minh trong lần trước echoĐiều này xảy ra bởi vì giá trị không thành công sẽ giết chết lớp $( command substitution )vỏ con chứa nó, nhưng vỏ mẹ của nó - _testtại thời điểm này - vẫn tiếp tục vận chuyển. Và echokhông quan tâm - đó là rất nhiều hạnh phúc chỉ để phục vụ một \newline; echokhông kiểm tra.

Nhưng hãy xem xét điều này:

    sh -evx <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    _test ||\
            echo "this doesnt even print"
    # END
    CMD
_test+ sh: line 1: empty: function doesnt run

Bởi vì tôi đã cung cấp _test()'sđầu vào với một tham số được đánh giá trước trong INIT here-documentlúc này, _test()hàm thậm chí không cố chạy. Những gì nhiều hơn shvỏ rõ ràng từ bỏ hoàn toàn con ma và echo "this doesnt even print" thậm chí không in.

Có lẽ đó không phải là điều bạn muốn.

Điều này xảy ra bởi vì ${var?}mở rộng tham số kiểu được thiết kế để thoát khỏishell trường hợp thiếu tham số, nó hoạt động như sau :

${parameter:?[word]}

Lỗi chỉ ra nếu Nullhoặc Unset.Nếu tham số không được đặt hoặc null, expansion of word(hoặc một thông báo cho biết nó không được đặt nếu từ bị bỏ qua) sẽ là written to standard errorshell exits with a non-zero exit status. Nếu không, giá trị của parameter shall be substituted. Một vỏ tương tác không cần phải thoát.

Tôi sẽ không sao chép / dán toàn bộ tài liệu, nhưng nếu bạn muốn thất bại với set but nullgiá trị, bạn sử dụng biểu mẫu:

${var :? error message }

Với những điều :colonnhư trên. Nếu bạn muốn một nullgiá trị thành công, chỉ cần bỏ qua dấu hai chấm. Bạn cũng có thể phủ nhận nó và chỉ thất bại đối với các giá trị được đặt, như tôi sẽ hiển thị trong giây lát.

Một hoạt động khác của _test():

    sh <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    echo "this runs" |\
        ( _test ; echo "this doesnt" ) ||\
            echo "now it prints"
    # END
    CMD
this runs
sh: line 1: empty: function doesnt run
now it prints

Điều này hoạt động với tất cả các loại thử nghiệm nhanh, nhưng ở trên bạn sẽ thấy rằng _test(), chạy từ giữa pipelinethất bại, và trên thực tế, nó chứa phần tử con của nó command listhoàn toàn thất bại, vì không có lệnh nào trong hàm chạy hay echochạy sau , mặc dù nó cũng được hiển thị rằng nó có thể dễ dàng được kiểm tra bởi vì echo "now it prints" bây giờ in.

Ma quỷ là trong các chi tiết, tôi đoán. Trong trường hợp trên, lớp vỏ thoát ra không phải là tập lệnh _main | logic | pipelinemà là ( subshell in which we ${test?} ) ||một hộp cát nhỏ được yêu cầu.

Và nó có thể không rõ ràng, nhưng nếu bạn muốn chỉ vượt qua cho trường hợp ngược lại, hoặc chỉ set=các giá trị, nó cũng khá đơn giản:

    sh <<-\CMD
    N= #N is NULL
    _test=$N #_test is also NULL and
    v="something you would rather do without"    
    ( #this subshell dies
        echo "v is ${v+set}: and its value is ${v:+not NULL}"
        echo "So this ${_test:-"\$_test:="} will equal ${_test:="$v"}"
        ${_test:+${N:?so you test for it with a little nesting}}
        echo "sure wish we could do some other things"
    )
    ( #this subshell does some other things 
        unset v #to ensure it is definitely unset
        echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
        echo "So this ${_test:-"\$_test:="} will equal NULL ${_test:="$v"}"
        ${_test:+${N:?is never substituted}}
        echo "so now we can do some other things" 
    )
    #and even though we set _test and unset v in the subshell
    echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
    # END
    CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without

Ví dụ trên tận dụng lợi thế của cả 4 hình thức thay thế tham số POSIX và các thử nghiệm :colon nullhoặc khác nhau của chúng not null. Có nhiều thông tin hơn trong liên kết ở trên, và đây là một lần nữa .

Và tôi đoán chúng ta cũng nên thể hiện _testchức năng của mình, phải không? Chúng tôi chỉ khai báo empty=somethingnhư một tham số cho chức năng của chúng tôi (hoặc bất kỳ lúc nào trước đó):

    sh <<-\CMD
    _test() { echo $( echo ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?tested as a pass before function runs}
    INIT
    echo "this runs" >&2 |\
        ( empty=not_empty _test ; echo "yay! I print now!" ) ||\
            echo "suspiciously quiet"
    # END
    CMD
this runs
not_empty
echo still works
yay! I print now!

Cần lưu ý rằng đánh giá này là độc lập - nó không yêu cầu thử nghiệm bổ sung để thất bại. Một vài ví dụ nữa:

    sh <<-\CMD
    empty= 
    ${empty?null, no colon, no failure}
    unset empty
    echo "${empty?this is stderr} this is not"
    # END
    CMD
sh: line 3: empty: this is stderr

    sh <<-\CMD
    _input_fn() { set -- "$@" #redundant
            echo ${*?WHERES MY DATA?}
            #echo is not necessary though
            shift #sure hope we have more than $1 parameter
            : ${*?WHERES MY DATA?} #: do nothing, gracefully
    }
    _input_fn heres some stuff
    _input_fn one #here
    # shell dies - third try doesnt run
    _input_fn you there?
    # END
    CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?

Và cuối cùng chúng ta quay trở lại câu hỏi ban đầu: làm thế nào để xử lý lỗi trong một mạng con $(command substitution)? Sự thật là - có hai cách, nhưng không phải là trực tiếp. Cốt lõi của vấn đề là quá trình đánh giá của shell - việc mở rộng shell (bao gồm $(command substitution)) xảy ra sớm hơn trong quy trình đánh giá của shell so với thực thi lệnh shell hiện tại - đó là khi lỗi của bạn có thể bị bắt và bị mắc kẹt.

Vấn đề mà các trải nghiệm op là vào thời điểm shell hiện tại đánh giá lỗi, $(command substitution)lớp con đã được thay thế - không còn lỗi.

Vậy hai cách là gì? Hoặc bạn thực hiện nó một cách rõ ràng trong phạm vi con $(command substitution)với các thử nghiệm mà bạn không có nó hoặc bạn hấp thụ kết quả của nó vào một biến shell hiện tại và kiểm tra giá trị của nó.

Cách 1:

    echo "$(madeup && echo \: || echo '${fail:?die}')" |\
          . /dev/stdin

sh: command not found: madeup
/dev/stdin:1: fail: die

    echo $?

126

Cách 2:

    var="$(madeup)" ; echo "${var:?die} still not stderr"

sh: command not found: madeup
sh: var: die

    echo $?

1

Điều này sẽ thất bại bất kể số lượng biến được khai báo trên mỗi dòng:

   v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"

sh: command not found: madeup
sh: v1: parameter not set

Và giá trị trả lại của chúng tôi không đổi:

    echo $?
1

BÂY GIỜ

    trap 'printf %s\\n trap resurrects shell!' ERR
    v1="$(madeup)" v2="$(printf %s\\n shown after trap)"
    echo "${v1:?#1 - still stderr}" "${v2:?invisible}"

sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap

    echo $?
0

Ví dụ, thực tế đặc điểm kỹ thuật chính xác của anh ấy: echo $ {v = $ (tên được tạo)}; tiếng vang "$ {v :? bẫy1st}! không đạt được!"
mikeerv

1
Tóm lại: Các mở rộng tham số này là một cách tuyệt vời để kiểm soát nếu các biến được đặt, v.v ... Đối với trạng thái thoát của tiếng vang, tôi nhận thấy rằng echo "abc" "${v1:?}"dường như không thực thi (abc không bao giờ được in). Và shell trả về 1. Điều này đúng với hoặc không có lệnh chẵn ( "${v1:?}"trực tiếp trên cli). Nhưng đối với tập lệnh của OP, tất cả những gì được yêu cầu để kích hoạt bẫy là đặt phép gán biến của anh ta chứa một sự thay thế cho một lệnh không tồn tại một mình trên một dòng. Mặt khác, hành vi của echo là trả về 0 luôn, trừ khi bị gián đoạn như với các bài kiểm tra bạn đã giải thích.

Chỉ cần v=$( madeup ). Tôi không thấy những gì không an toàn với điều đó. Đó chỉ là một nhiệm vụ, người đã viết sai lệnh chẳng hạn v="$(lss)". Nó lỗi. Có, bạn có thể xác minh với trạng thái lỗi của lệnh cuối $ không? - bởi vì đó là lệnh trên dòng (một bài tập không có tên lệnh) và không có gì khác - không phải là một đối số để lặp lại. Ngoài ra, ở đây, nó bị kẹt bởi hàm là! = 0, do đó bạn nhận được phản hồi hai lần. Mặt khác, chắc chắn như bạn giải thích, có một cách tốt hơn để thực hiện điều này một cách có trật tự trong một khung nhưng OP có 1 dòng duy nhất: một tiếng vang cộng với sự thay thế thất bại của anh ta. Anh đang thắc mắc về tiếng vang.

Có, bài tập đơn giản được đề cập trong câu hỏi cũng như được cho phép, và trong khi tôi không thể đưa ra lời khuyên cụ thể về cách sử dụng -E, tôi đã cố gắng hiển thị kết quả tương tự với vấn đề đã được chứng minh là có thể hơn mà không cần dùng đến bashism. Trong mọi trường hợp, cụ thể là các vấn đề bạn đề cập - như chỉ định một dòng trên mỗi dòng - làm cho các giải pháp đó khó xử lý trong một đường ống, mà tôi cũng đã trình bày cách xử lý. Mặc dù đó là sự thật những gì bạn nói - không có gì không an toàn khi chỉ cần gán nó.
mikeerv

1
Điểm tốt - có lẽ nhiều nỗ lực hơn được kêu gọi. Tôi nghĩ về nó
mikeerv

1

Nếu được đặt, bất kỳ bẫy nào trên ERR đều được kế thừa bởi các hàm shell, thay thế lệnh và các lệnh được thực thi trong môi trường lớp con

Trong kịch bản của bạn, nó thực thi lệnh ( echo $( made up name )). Trong các lệnh bash được phân định bằng một trong hai ; hoặc với dòng mới . Trong lệnh

echo $( made up name )

$( made up name )được coi là một phần của lệnh. Ngay cả khi phần này không thành công và trả về có lỗi, toàn bộ lệnh sẽ thực thi thành công vì echokhông biết về nó. Khi lệnh trả về với 0, không có bẫy nào được kích hoạt.

Bạn cần đặt nó trong hai lệnh, gán và echo

var=$(made_up_name)
echo $var

echo $varkhông thất bại - $varđược mở rộng để trống trước khi echothực sự nhìn vào nó.
mikeerv 17/03/2016

0

Điều này là do một lỗi trong bash. Trong bước thay thế chỉ huy

echo $( made up name )

madechạy (hoặc không tìm thấy) trong một lớp con, nhưng lớp con được "tối ưu hóa" theo cách mà nó không sử dụng một số bẫy từ vỏ cha. Điều này đã được cố định trong phiên bản 4.4.5 :

Trong một số trường hợp nhất định, một lệnh đơn giản được tối ưu hóa để loại bỏ một ngã ba, dẫn đến bẫy EXIT không được thực thi.

Với bash 4.4.5 trở lên, bạn sẽ thấy đầu ra sau:

error.sh: line 13: made: command not found
err status: 127
  ! should not be reached !

Trình xử lý bẫy đã được gọi như mong đợi, sau đó thoát ra khỏi khung con. ( set -echỉ làm cho lớp con thoát ra, chứ không phải cha mẹ, do đó, thông báo "không nên đạt được", trên thực tế, phải đạt được.)

Một cách giải quyết cho các phiên bản cũ hơn là buộc tạo ra một khung con đầy đủ, không được tối ưu hóa:

echo $( ( made up name ) )

Các không gian bổ sung được yêu cầu để phân biệt với Mở rộng số học.

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.