Chuyển hướng đầu ra lỗi tiêu chuẩn sang biến bash


7

Đây là đoạn mã C sẽ gây ra segfault:

// segfault.c

#include <string.h>

int main()
{
    memset((char *)0x0, 1, 100);
    return 1;
}

Biên dịch nó với:

gcc segfault.c -o segfault

Nếu được thực hiện từ bash:

$ ./segfault
Segmentation fault (core dumped)

Bây giờ tôi gói cuộc gọi bên trong một tập lệnh bash. Có ba lần thử liên tiếp. Tôi muốn nhận được đầu ra lỗi bên trong biến retvà hiển thị nó.

#!/bin/bash

# segfault.sh

ret=`./segfault 2>&1`
echo "1) " $ret
ret=`eval ./segfault 2>&1`
echo "2) " $ret
ret=`eval ./segfault 2>&1 | cat`
echo "3) " $ret

Nếu tôi thực thi tập lệnh từ bash:

1) 
2) 
3)  ./segfault.sh: line 7: 28814 Segmentation fault (core dumped) ./segfault

Rõ ràng, chỉ có hình thức thứ ba của công việc gọi. Câu hỏi của tôi là, tại sao hai hình thức đầu tiên không thể bắt được đầu ra lỗi?

Câu trả lời:


7

Nó hoạt động với tôi với tập lệnh bash đơn giản (chỉ stderr):

$ cat seg.sh 
#!/bin/bash
echo "Segfault" 1>&2
$ test=`./seg.sh`; echo "x$test"
Segfault
x
$ test=`./seg.sh 2>&1`; echo "x$test"
xSegfault
$ test=`eval ./seg.sh 2>&1`; echo "x$test"
xSegfault

Vấn đề trong trường hợp của bạn là do thực tế Segmentation fault (core dumped)không phải do chương trình của bạn viết ra (vì nó bị giết bởi kernel), mà là do quá trình cha mẹ, người có được thông tin về cái chết của con mình. Hiệu ứng này được ẩn đi bằng cách đưa nó vào một quy trình và đường ống khác cattrong ví dụ cuối cùng của bạn. Bạn nên dựa vào mã thoát, sau đó vào stderr:

$ ./segfault; echo $?
Segmentation fault (core dumped)
139

2

Lỗi phân đoạn tin nhắn (bị đổ lõi), được phát ra bởi bash, không phải do chương trình bị sập (khi tin nhắn được phát ra, chương trình đã chết!). Việc chuyển hướng chỉ áp dụng cho chính chương trình.

Để chuyển hướng các thông điệp từ chính shell về chương trình, hãy chạy chương trình bên trong cấu trúc nhóm shell và chuyển hướng đầu ra của cả nhóm. Cấu trúc nhóm vỏ cơ bản nhất, không có gì ngoài nhóm, là niềng răng.

ret=`{ ./segfault; } 2>&1`

Biểu mẫu ret=`eval ./segfault 2>&1`áp dụng chuyển hướng cho toàn bộ đánh giá evallệnh, vì vậy về nguyên tắc, nó sẽ hoạt động và trên thực tế nó hoạt động trên máy của tôi với bash 4.3.30 và các phiên bản cũ hơn. Điều có thể xảy ra (và tôi có thể sao chép nó bằng ksh) là phiên bản bash của bạn thực hiện một số tối ưu hóa để tránh bỏ các chương trình con khi chúng là lệnh cuối cùng trong một lớp con. Cách danh nghĩa để thực thi lệnh ret=`eval ./segfault`là:

  • Tạo một đường ống.
  • Fork, tức là tạo một quy trình con shell. Trong quy trình con (quy trình 1):
    • Chuyển hướng đầu ra vào đường ống.
    • Thi công evaldựng sẵn.
    • Cái nĩa. Trong quy trình con (quy trình 2):
      • Thực thi tệp ./segfault, tức là thay thế chương trình shell hiện đang chạy trong quy trình này segfault.
    • (Trong quy trình 1) Đợi quá trình 2 kết thúc.
    • Quy trình 1 lối thoát.
  • (Trong quy trình vỏ ban đầu) Đọc từ đường ống và tích lũy dữ liệu trong retbiến.
  • Khi đường ống được đóng lại, tiếp tục thực hiện.

Như bạn có thể thấy, quy trình 1 tạo ra một quy trình khác, sau đó đợi cho nó kết thúc và thoát ngay lập tức. Sẽ hiệu quả hơn cho quy trình 1 để tự tái chế. Một số shell (và phiên bản shell) tốt hơn những cái khác trong việc nhận ra các tình huống như vậy và thực hiện tối ưu hóa cuộc gọi đuôi . Tuy nhiên, trong trường hợp ret=`{ ./segfault; } 2>&1`, quy trình 2 có lỗi tiêu chuẩn được chuyển hướng đến mô tả tệp 1, nhưng quy trình 1 thì không. Trong phiên bản shell mà bạn đã thử, trình tối ưu hóa không nhận ra tình huống này (nó có thể đã thực hiện một cuộc gọi đuôi, nhưng nó nên thiết lập chuyển hướng khác nhau).

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.