Làm cách nào để nắm bắt mã thoát / xử lý lỗi chính xác khi sử dụng thay thế quy trình?


13

Tôi có một đoạn mã phân tích tên tệp thành một mảng bằng phương thức sau được lấy từ một câu hỏi và trả lời trên SO :

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

Điều này hoạt động tuyệt vời và xử lý tất cả các loại biến thể tên tệp hoàn hảo. Tuy nhiên, đôi khi, tôi sẽ chuyển một tệp không tồn tại vào tập lệnh, ví dụ:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

Trong trường hợp bình thường, tôi sẽ có tập lệnh bắt mã thoát với nội dung tương tự RET=$?và sử dụng nó để quyết định cách tiến hành. Điều này dường như không làm việc với quá trình thay thế ở trên.

Thủ tục chính xác trong trường hợp như thế này là gì? Làm thế nào tôi có thể chụp mã trả lại? Có cách nào khác phù hợp hơn để xác định nếu có sự cố xảy ra trong quy trình thay thế không?

Câu trả lời:


5

Bạn có thể dễ dàng nhận được tiền lãi từ bất kỳ quy trình phụ nào bằng cách lặp lại lợi nhuận của nó trên thiết bị xuất chuẩn của nó. Điều tương tự cũng đúng với quá trình thay thế:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

Nếu tôi chạy nó thì dòng cuối cùng - (hoặc \0phần được phân tách như trường hợp có thể) sẽ là findtrạng thái trả về. readsẽ trả về 1 khi nhận được EOF - vì vậy thời gian duy nhất $returnđược đặt $FILElà dành cho bit thông tin cuối cùng được đọc.

Tôi sử dụng printfđể tránh thêm \newline - điều này rất quan trọng vì ngay cả một readhoạt động thường xuyên - một trong đó bạn không phân định trên \0NUL - sẽ trả về 0 trong trường hợp khi dữ liệu vừa đọc không kết thúc một \newline. Vì vậy, nếu dòng cuối cùng của bạn không kết thúc bằng một \newline, giá trị cuối cùng trong biến đọc của bạn sẽ là lợi tức của bạn.

Chạy lệnh trên và sau đó:

echo "$return"

ĐẦU RA

0

Và nếu tôi thay đổi phần thay thế quá trình ...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

ĐẦU RA

1

Một minh chứng đơn giản hơn:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

ĐẦU RA

pipe

Và trên thực tế, miễn là lợi nhuận bạn muốn là điều cuối cùng bạn viết cho thiết bị xuất chuẩn từ bên trong thay thế quy trình - hoặc bất kỳ quy trình được ghi lại nào mà bạn đọc theo cách này - thì $FILEluôn luôn là trạng thái trả về bạn muốn khi nó đã qua. Và do đó, || ! return=...phần này không thực sự cần thiết - nó chỉ được sử dụng để thể hiện khái niệm này.


5

Các quy trình thay thế quy trình là không đồng bộ: shell khởi chạy chúng và sau đó không đưa ra bất kỳ cách nào để phát hiện khi chúng chết. Vì vậy, bạn sẽ không thể có được trạng thái thoát.

Bạn có thể viết trạng thái thoát vào một tệp, nhưng điều này nói chung là vụng về vì bạn không thể biết khi nào tệp được viết. Ở đây, tệp được viết ngay sau khi kết thúc vòng lặp, vì vậy thật hợp lý khi chờ đợi nó.

 < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

Một cách tiếp cận khác là sử dụng một đường ống có tên và một quá trình nền (mà bạn có thể waitlàm).

mkfifo find_pipe
find  >find_pipe &
find_pid=$!
 <find_pipe
wait $find_pid
find_status=$?

Nếu không có cách tiếp cận nào phù hợp, tôi nghĩ bạn sẽ cần hướng đến một ngôn ngữ có khả năng hơn, như Perl, Python hoặc Ruby.


Cảm ơn bạn cho câu trả lời này. Các phương pháp bạn mô tả hoạt động tốt nhưng tôi phải thừa nhận rằng chúng phức tạp hơn một chút so với tôi dự đoán. Trong trường hợp của tôi, tôi đã giải quyết một vòng lặp trước vòng lặp được hiển thị trong câu hỏi lặp qua tất cả các đối số và in lỗi nếu một trong số chúng không phải là tệp hoặc thư mục. Mặc dù điều này không xử lý các loại lỗi khác có thể xảy ra trong quy trình thay thế, nhưng nó đủ tốt cho trường hợp cụ thể này. Nếu tôi cần một phương pháp xử lý lỗi phức tạp hơn trong những tình huống như thế này chắc chắn tôi sẽ quay lại câu trả lời của bạn.
Glutimate

2

Sử dụng một coprocess . Sử dụng coprocnội dung dựng sẵn, bạn có thể bắt đầu một quy trình con, đọc đầu ra của nó và kiểm tra trạng thái thoát của nó:

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

Nếu thư mục không tồn tại, waitsẽ thoát với mã trạng thái khác không.

Hiện tại cần phải sao chép PID sang một biến khác vì $LS_PIDsẽ không được đặt trước khi waitđược gọi. Xem Bash unets * _PID biến trước khi tôi có thể đợi trên coproc để biết chi tiết.


1
Tôi tò mò khi nào người ta sẽ sử dụng <& "$ LS" so với đọc -u $ LS? - cảm ơn
Brian Chrisman

1
@BrianChrisman Trong trường hợp này, có lẽ không bao giờ. read -unên làm việc tốt Ví dụ này có nghĩa là chung chung và cho thấy đầu ra của bộ đồng xử lý có thể được chuyển sang một lệnh khác như thế nào.
Feuermurmel

1

Một cách tiếp cận là:

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

Ý tưởng là lặp lại trạng thái thoát cùng với mã thông báo ngẫu nhiên sau khi lệnh hoàn thành, sau đó sử dụng các biểu thức chính quy bash để tìm và trích xuất trạng thái thoát. Mã thông báo được sử dụng để tạo một chuỗi duy nhất để tìm kiếm trong đầu ra.

Đây có thể không phải là cách tốt nhất để làm điều đó theo nghĩa lập trình chung, nhưng nó có thể là cách ít đau đớn nhất để xử lý nó trong bash.

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.