it-swarm-vi.com

Nhận trạng thái thoát của quá trình được chuyển sang một quy trình khác

Tôi có hai quy trình foobar, được kết nối với một đường ống:

$ foo | bar

bar luôn thoát 0; Tôi quan tâm đến mã thoát của foo. Có cách nào để có được nó?

314
Michael Mrozek

Nếu bạn đang sử dụng bash, bạn có thể sử dụng biến mảng PIPESTATUS để lấy trạng thái thoát của từng thành phần của đường ống.

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

Nếu bạn đang sử dụng zsh, thì mảng đó được gọi là pipestatus (trường hợp quan trọng!) Và các chỉ số mảng bắt đầu tại một:

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

Để kết hợp chúng trong một hàm theo cách không làm mất các giá trị:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

Chạy phần trên trong bash hoặc zsh và bạn sẽ nhận được kết quả tương tự; chỉ một trong retval_bashretval_zsh Sẽ được thiết lập. Cái khác sẽ để trống. Điều này sẽ cho phép một hàm kết thúc bằng return $retval_bash $retval_zsh (lưu ý thiếu dấu ngoặc kép!).

277
camh

Có 3 cách phổ biến để làm điều này:

Đường ống

Cách đầu tiên là đặt tùy chọn pipefail (ksh, zsh hoặc bash). Đây là cách đơn giản nhất và những gì nó làm về cơ bản là đặt trạng thái thoát $? đến mã thoát của chương trình cuối cùng để thoát khác không (hoặc bằng 0 nếu tất cả đã thoát thành công).

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

Bash cũng có một biến mảng gọi là $PIPESTATUS ($pipestatus in zsh) chứa trạng thái thoát của tất cả các chương trình trong đường ống cuối cùng.

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

Bạn có thể sử dụng ví dụ lệnh thứ 3 để lấy giá trị cụ thể trong đường ống mà bạn cần.

Thi hành riêng

Đây là khó sử dụng nhất của các giải pháp. Chạy từng lệnh riêng biệt và nắm bắt trạng thái

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1
253
Patrick

Giải pháp này hoạt động mà không sử dụng các tính năng cụ thể của bash hoặc các tệp tạm thời. Phần thưởng: cuối cùng, trạng thái thoát thực sự là trạng thái thoát và không phải là một chuỗi trong tệp.

Tình hình:

someprog | filter

bạn muốn trạng thái thoát từ someprog và đầu ra từ filter.

Đây là giải pháp của tôi:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

kết quả của cấu trúc này là thiết bị xuất chuẩn từ filter là thiết bị xuất chuẩn của trạng thái xây dựng và thoát khỏi someprog là trạng thái thoát của cấu trúc.


cấu trúc này cũng hoạt động với nhóm lệnh đơn giản {...} thay vì subshells (...). subshells có một số hàm ý, trong số những người khác một chi phí hiệu suất, mà chúng ta không cần ở đây. đọc hướng dẫn sử dụng bash tốt để biết thêm chi tiết: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

Thật không may, ngữ pháp bash yêu cầu không gian và dấu chấm phẩy cho dấu ngoặc nhọn để cấu trúc trở nên rộng rãi hơn nhiều.

Đối với phần còn lại của văn bản này, tôi sẽ sử dụng biến thể subshell.


Ví dụ someprogfilter:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

Ví dụ đầu ra:

filtered line1
filtered line2
filtered line3
42

Lưu ý: quá trình con kế thừa các mô tả tệp mở từ cha mẹ. Điều đó có nghĩa là someprog sẽ kế thừa bộ mô tả tệp mở 3 và 4. Nếu someprog ghi vào bộ mô tả tệp 3 thì điều đó sẽ trở thành trạng thái thoát. Trạng thái thoát thực sẽ bị bỏ qua vì read chỉ đọc một lần.

Nếu bạn lo lắng rằng someprog của bạn có thể ghi vào bộ mô tả tệp 3 hoặc 4 thì tốt nhất là đóng bộ mô tả tệp trước khi gọi someprog.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

Các exec 3>&- 4>&- trước someprog đóng bộ mô tả tệp trước khi thực hiện someprog vì vậy đối với someprog những mô tả tệp đó đơn giản là không tồn tại.

Nó cũng có thể được viết như thế này: someprog 3>&- 4>&-


Từng bước giải thích về cấu trúc:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

Từ dưới lên trên:

  1. Một subshell được tạo với bộ mô tả tập tin 4 được chuyển hướng đến thiết bị xuất chuẩn. Điều này có nghĩa là bất cứ điều gì được in ra để mô tả tập tin 4 trong lớp con sẽ kết thúc như là thiết bị xuất chuẩn của toàn bộ cấu trúc.
  2. Một đường ống được tạo và các lệnh ở bên trái (#part3) và phải (#part2) được thực thi. exit $xs cũng là lệnh cuối cùng của đường ống và điều đó có nghĩa là chuỗi từ stdin sẽ là trạng thái thoát của toàn bộ cấu trúc.
  3. Một subshell được tạo với bộ mô tả tập tin 3 được chuyển hướng đến thiết bị xuất chuẩn. Điều này có nghĩa là bất cứ điều gì được in ra mô tả tập tin 3 trong phần con này sẽ kết thúc bằng #part2 và lần lượt sẽ là trạng thái thoát của toàn bộ cấu trúc.
  4. Một đường ống được tạo và các lệnh ở bên trái (#part5#part6) và phải (filter >&4) được thực thi. Đầu ra của filter được chuyển hướng đến bộ mô tả tệp 4. Trong #part1 bộ mô tả tập tin 4 đã được chuyển hướng đến thiết bị xuất chuẩn. Điều này có nghĩa là đầu ra của filter là thiết bị xuất chuẩn của toàn bộ cấu trúc.
  5. Thoát trạng thái khỏi #part6 được in ra mô tả tập tin 3. Trong #part3 mô tả tập tin 3 đã được chuyển hướng đến #part2. Điều này có nghĩa là trạng thái thoát khỏi #part6 sẽ là trạng thái thoát cuối cùng cho toàn bộ cấu trúc.
  6. someprog được thực thi. Trạng thái thoát được thực hiện trong #part5. Thiết bị xuất chuẩn được lấy bởi đường ống trong #part4 và được chuyển tiếp tới filter. Đầu ra từ filter sẽ lần lượt đạt đến thiết bị xuất chuẩn như được giải thích trong #part4
58
lesmana

Mặc dù không chính xác những gì bạn yêu cầu, bạn có thể sử dụng

#!/bin/bash -o pipefail

để đường ống của bạn trả lại kết quả khác không.

có thể là một chút ít mã hóa

Chỉnh sửa: Ví dụ

[[email protected] ~]# false | true
[[email protected] ~]# echo $?
0
[[email protected] ~]# set -o pipefail
[[email protected] ~]# false | true
[[email protected] ~]# echo $?
1
37
Chris

Những gì tôi làm khi có thể là đưa mã thoát từ foo vào bar. Ví dụ: nếu tôi biết rằng foo không bao giờ tạo ra một dòng chỉ có các chữ số, thì tôi chỉ có thể giải quyết mã thoát:

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

Hoặc nếu tôi biết rằng đầu ra từ foo không bao giờ chứa một dòng chỉ có .:

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

Điều này luôn có thể được thực hiện nếu có một số cách để bar hoạt động trên tất cả trừ dòng cuối cùng và chuyển dòng cuối cùng làm mã thoát.

Nếu bar là một đường ống phức tạp có đầu ra mà bạn không cần, bạn có thể bỏ qua một phần của nó bằng cách in mã thoát trên một bộ mô tả tệp khác.

exit_codes=$({ { foo; echo foo:"$?" >&3; } |
               { bar >/dev/null; echo bar:"$?" >&3; }
             } 3>&1)

Sau đây $exit_codes thường là foo:X bar:Y, nhưng nó có thể là bar:Y foo:X if bar thoát trước khi đọc tất cả đầu vào của nó hoặc nếu bạn không may mắn. Tôi nghĩ rằng việc ghi vào các ống lên tới 512 byte là nguyên tử trên tất cả các đơn vị, vì vậy foo:$?bar:$? các phần sẽ không được trộn lẫn miễn là các chuỗi thẻ dưới 507 byte.

Nếu bạn cần nắm bắt đầu ra từ bar, điều đó trở nên khó khăn. Bạn có thể kết hợp các kỹ thuật ở trên bằng cách sắp xếp đầu ra của bar không bao giờ chứa một dòng trông giống như một dấu hiệu mã thoát, nhưng nó thực sự khó hiểu.

output=$(echo;
         { { foo; echo foo:"$?" >&3; } |
           { bar | sed 's/^/^/'; echo bar:"$?" >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

Và, tất nhiên, có tùy chọn đơn giản là sử dụng tệp tạm thời để lưu trữ trạng thái. Đơn giản, nhưng không đơn giản trong sản xuất:

  • Nếu có nhiều tập lệnh chạy đồng thời hoặc nếu cùng một tập lệnh sử dụng phương thức này ở một số nơi, bạn cần đảm bảo rằng chúng sử dụng các tên tệp tạm thời khác nhau.
  • Tạo một tập tin tạm thời một cách an toàn trong một thư mục chia sẻ là khó khăn. Thường, /tmp là nơi duy nhất mà tập lệnh chắc chắn có thể ghi tệp. Sử dụng mktemp , không phải là POSIX nhưng hiện có sẵn trên tất cả các thông báo nghiêm túc.
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

Bắt đầu từ đường ống:

foo | bar | baz

Đây là một giải pháp chung chỉ sử dụng POSIX Shell và không có tệp tạm thời:

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$error_statuses chứa mã trạng thái của bất kỳ quy trình thất bại nào, theo thứ tự ngẫu nhiên, với các chỉ mục để cho biết lệnh nào được phát ra mỗi trạng thái.

# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null

Lưu ý các trích dẫn xung quanh $error_statuses trong các bài kiểm tra của tôi; không có chúng grep không thể phân biệt được vì các dòng mới bị ép buộc vào khoảng trắng.

17
Jander

Vì vậy, tôi muốn đóng góp một câu trả lời như của lesmana, nhưng tôi nghĩ rằng có lẽ tôi là một giải pháp Bourne-Shell thuần túy đơn giản hơn một chút và có lợi hơn một chút:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

Tôi nghĩ rằng điều này được giải thích tốt nhất từ ​​trong ra ngoài - lệnh1 sẽ thực thi và in đầu ra thông thường của nó trên thiết bị xuất chuẩn (mô tả tệp 1), sau đó khi hoàn tất, printf sẽ thực thi và in mã thoát của lệnh1 trên thiết bị xuất chuẩn của nó, nhưng thiết bị xuất chuẩn đó được chuyển hướng đến mô tả tập tin 3.

Trong khi lệnh1 đang chạy, thiết bị xuất chuẩn của nó đang được chuyển sang lệnh2 (đầu ra của printf không bao giờ chuyển sang lệnh2 vì chúng tôi gửi nó tới tệp mô tả 3 thay vì 1, đó là những gì đường ống đọc). Sau đó, chúng tôi chuyển hướng đầu ra của lệnh2 sang bộ mô tả tệp 4, do đó, nó cũng nằm ngoài bộ mô tả tệp 1 - vì chúng tôi muốn mô tả tệp 1 miễn phí một lát sau, vì chúng tôi sẽ đưa đầu ra printf trên bộ mô tả tệp 3 trở lại vào bộ mô tả tệp 1 - bởi vì đó là những gì thay thế lệnh (backticks), sẽ nắm bắt và đó là những gì sẽ được đặt vào biến.

Ma thuật cuối cùng là lần đầu tiên exec 4>&1 chúng tôi đã thực hiện như một lệnh riêng - nó mở mô tả tệp 4 dưới dạng bản sao của thiết bị xuất chuẩn của Shell bên ngoài. Thay thế lệnh sẽ nắm bắt bất cứ điều gì được viết trên tiêu chuẩn từ phối cảnh của các lệnh bên trong nó - nhưng, vì đầu ra của lệnh2 sẽ chuyển sang mô tả tệp 4 khi có liên quan đến việc thay thế lệnh, tuy nhiên, thay thế lệnh không nắm bắt được - tuy nhiên, một khi nó được "ra" thay thế lệnh, nó vẫn thực sự đi đến phần mô tả tập tin tổng thể của tập lệnh 1.

(Các exec 4>&1 phải là một lệnh riêng biệt vì nhiều shell thông thường không thích nó khi bạn cố ghi vào một bộ mô tả tệp bên trong một lệnh thay thế, được mở trong lệnh "bên ngoài" đang sử dụng thay thế. Vì vậy, đây là cách di động đơn giản nhất để làm điều đó.)

Bạn có thể xem xét nó theo một cách ít kỹ thuật và vui tươi hơn, vì nếu các đầu ra của các lệnh đang nhảy vọt vào nhau: Command1 pipe sang lệnh2, thì đầu ra của printf nhảy qua lệnh 2 để lệnh2 không bắt được nó, và sau đó Đầu ra của lệnh 2 nhảy qua và thay thế lệnh giống như printf vừa kịp lúc bị bắt bởi sự thay thế để nó kết thúc trong biến và đầu ra của lệnh2 tiếp tục được ghi vào đầu ra tiêu chuẩn, giống như trong một đường ống bình thường.

Ngoài ra, theo tôi hiểu, $? vẫn sẽ chứa mã trả về của lệnh thứ hai trong đường ống, bởi vì các phép gán biến, thay thế lệnh và các lệnh ghép đều hoàn toàn trong suốt đối với mã trả về của lệnh bên trong chúng, do đó trạng thái trả về của lệnh2 sẽ được truyền đi - điều này, và không phải xác định một chức năng bổ sung, là lý do tại sao tôi nghĩ rằng đây có thể là một giải pháp tốt hơn so với giải pháp được đề xuất bởi lesmana.

Theo đề xuất của lesmana, có thể lệnh1 sẽ kết thúc bằng cách sử dụng mô tả tệp 3 hoặc 4, vì vậy để mạnh mẽ hơn, bạn sẽ làm:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

Lưu ý rằng tôi sử dụng các lệnh ghép trong ví dụ của mình, nhưng các chuỗi con (sử dụng ( ) thay vì { } cũng sẽ hoạt động, mặc dù có lẽ kém hiệu quả hơn.)

Các lệnh kế thừa bộ mô tả tệp từ quá trình khởi chạy chúng, vì vậy toàn bộ dòng thứ hai sẽ kế thừa bộ mô tả tệp bốn và lệnh ghép tiếp theo là 3>&1 sẽ kế thừa bộ mô tả tập tin ba. Nên 4>&- đảm bảo rằng lệnh ghép bên trong sẽ không kế thừa bộ mô tả tệp bốn và 3>&- sẽ không kế thừa bộ mô tả tệp ba, vì vậy lệnh1 có được 'môi trường sạch hơn', chuẩn hơn. Bạn cũng có thể di chuyển bên trong 4>&- bên cạnh các 3>&-, nhưng tôi hiểu tại sao không chỉ giới hạn phạm vi của nó càng nhiều càng tốt.

Tôi không chắc chắn tần suất mọi thứ sử dụng bộ mô tả tệp ba và bốn trực tiếp - Tôi nghĩ rằng hầu hết các chương trình sử dụng các tòa nhà chọc trời trả lại các mô tả tệp không được sử dụng tại thời điểm đó, nhưng đôi khi mã được ghi trực tiếp vào bộ mô tả tệp 3, tôi đoán (tôi có thể tưởng tượng một chương trình kiểm tra một bộ mô tả tệp để xem nó có mở không và sử dụng nó nếu có, hoặc hành xử khác đi nếu không). Vì vậy, cái sau có lẽ là tốt nhất để ghi nhớ và sử dụng cho các trường hợp mục đích chung.

12
mtraceur

Nếu bạn đã cài đặt gói moreutils , bạn có thể sử dụng tiện ích mispipe thực hiện chính xác những gì bạn đã hỏi.

11
Emanuele Aina

giải pháp của lesmana ở trên cũng có thể được thực hiện mà không cần chi phí bắt đầu các quy trình con lồng nhau bằng cách sử dụng { .. } thay vào đó (hãy nhớ rằng hình thức các lệnh được nhóm này luôn phải kết thúc bằng dấu chấm phẩy). Một cái gì đó như thế này:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

Tôi đã kiểm tra cấu trúc này với phiên bản dash 0,5.5 và phiên bản bash 3.2.25 và 4.2.42, vì vậy ngay cả khi một số shell không hỗ trợ { .. } nhóm, nó vẫn tuân thủ POSIX.

7
pkeller

Sau đây có nghĩa là một addon cho câu trả lời của @Patrik, trong trường hợp bạn không thể sử dụng một trong những giải pháp phổ biến.

Câu trả lời này giả định như sau:

  • Bạn có Shell mà không biết $PIPESTATUS cũng không set -o pipefail
  • Bạn muốn sử dụng một đường ống để thực hiện song song, vì vậy không có tệp tạm thời.
  • Bạn không muốn có thêm sự lộn xộn xung quanh nếu bạn làm gián đoạn tập lệnh, có thể là do mất điện đột ngột.
  • Giải pháp này nên tương đối dễ theo dõi và sạch sẽ để đọc.
  • Bạn không muốn giới thiệu các subshells bổ sung.
  • Bạn không thể sử dụng các mô tả tệp hiện có, vì vậy không được chạm vào stdin/out/err (tuy nhiên bạn có thể tạm thời giới thiệu một số tệp mới)

Giả định bổ sung. Bạn có thể thoát khỏi tất cả, nhưng điều này làm tắc nghẽn công thức quá nhiều, vì vậy nó không được đề cập ở đây:

  • Tất cả những gì bạn muốn biết là tất cả các lệnh trong PIPE đều có mã thoát 0.
  • Bạn không cần thêm thông tin ban nhạc bên.
  • Shell của bạn không đợi tất cả các lệnh ống trở lại.

Trước: foo | bar | baz, tuy nhiên, điều này chỉ trả về mã thoát của lệnh cuối cùng (baz)

Muốn: $? không được 0 (đúng), nếu bất kỳ lệnh nào trong đường ống không thành công

Sau:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

# $? now is 0 only if all commands had exit code 0

Giải thích:

  • Một tempfile được tạo bằng mktemp. Điều này thường ngay lập tức tạo một tệp trong /tmp
  • Tempfile này sau đó được chuyển hướng đến FD 9 để ghi và FD 8 để đọc
  • Sau đó tempfile ngay lập tức bị xóa. Nó vẫn mở, mặc dù, cho đến khi cả hai FD biến mất.
  • Bây giờ đường ống được bắt đầu. Mỗi bước chỉ thêm vào FD 9, nếu có lỗi.
  • wait là cần thiết cho ksh, vì ksh khác không đợi tất cả các lệnh ống kết thúc. Tuy nhiên, xin lưu ý rằng có những tác dụng phụ không mong muốn nếu có một số tác vụ nền, vì vậy tôi đã nhận xét nó theo mặc định. Nếu chờ đợi không đau, bạn có thể bình luận.
  • Sau đó, nội dung của tập tin được đọc. Nếu nó trống (vì tất cả đã hoạt động) read trả về false, vì vậy true chỉ ra lỗi

Điều này có thể được sử dụng như là một thay thế plugin cho một lệnh duy nhất và chỉ cần sau:

  • FD không sử dụng 9 và 8
  • Một biến môi trường duy nhất để giữ tên của tempfile
  • Và công thức này có thể được điều chỉnh phù hợp với mọi Shell ngoài đó cho phép IO chuyển hướng
  • Ngoài ra, nó là nền tảng bất khả tri và không cần những thứ như /proc/fd/N

BUGs:

Kịch bản này có lỗi trong trường hợp /tmp hết dung lượng. Nếu bạn cũng cần bảo vệ chống lại trường hợp nhân tạo này, bạn có thể làm như sau, tuy nhiên điều này có nhược điểm là số lượng 0 trong 000 phụ thuộc vào số lượng lệnh trong đường ống, do đó phức tạp hơn một chút:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

Ghi chú tính di động:

  • ksh và các shell tương tự chỉ chờ lệnh ống cuối cùng cần wait không bị lỗi

  • Ví dụ cuối sử dụng printf "%1s" "$?" thay vì echo -n "$?" bởi vì điều này là di động hơn. Không phải mọi nền tảng diễn giải -n chính xác.

  • printf "$?" cũng sẽ làm điều đó, tuy nhiên printf "%1s" bắt một số trường hợp góc trong trường hợp bạn chạy tập lệnh trên một số nền tảng thực sự bị hỏng. (Đọc: nếu bạn tình cờ lập trình trong paranoia_mode=extreme.)

  • FD 8 và FD 9 có thể cao hơn trên các nền tảng hỗ trợ nhiều chữ số. AFAIR Shell tuân thủ POSIX chỉ cần hỗ trợ các chữ số đơn.

  • Đã được thử nghiệm với Debian 8.2 sh, bash, ksh, ash, sash và thậm chí csh

5
Tino

Đây là tính di động, tức là hoạt động với mọi Shell tuân thủ POSIX, không yêu cầu thư mục hiện tại có thể ghi và cho phép nhiều tập lệnh sử dụng cùng một thủ thuật để chạy đồng thời.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

Chỉnh sửa: đây là phiên bản mạnh hơn theo nhận xét của Gilles:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2: và đây là một biến thể nhẹ hơn sau bình luận dubiousjim:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
4
jlliagre

Với một chút đề phòng, điều này sẽ hoạt động:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
3
alex

Khối 'if' sau sẽ chỉ chạy nếu 'lệnh' thành công:

if command; then
   # ...
fi

Nói một cách cụ thể, bạn có thể chạy một cái gì đó như thế này:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

Cái nào sẽ chạy haconf -makerw và lưu trữ thiết bị xuất chuẩn và thiết bị xuất chuẩn của nó thành "$ haconf thừng". Nếu giá trị được trả về từ haconf là đúng, thì khối 'if' sẽ được thực thi và grep sẽ đọc "$ haconf thừng", cố gắng khớp với "Cụm đã có thể ghi".

Lưu ý rằng các đường ống tự động tự làm sạch; với việc chuyển hướng, bạn sẽ phải cẩn thận để xóa "$ haconfợi" khi hoàn tất.

Không thanh lịch như pipefail, nhưng là một sự thay thế hợp pháp nếu chức năng này không nằm trong tầm tay.

2
Rany Albeg Wein
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$
1
C.G.

(Ít nhất là bash) kết hợp với set -e người ta có thể sử dụng lớp con để mô phỏng rõ ràng đường ống và thoát khỏi lỗi đường ống

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

Vì vậy, nếu foo không thành công vì một số lý do - phần còn lại của chương trình sẽ không được thực thi và tập lệnh thoát với mã lỗi tương ứng. (Điều này giả định rằng foo in lỗi của chính nó, đủ để hiểu lý do thất bại)

0
noonex