Vì lợi ích của người đọc, công thức này ở đây
- có thể được sử dụng lại như oneliner để bắt stderr vào một biến
- vẫn cấp quyền truy cập vào mã trả về của lệnh
- Hy sinh một mô tả tập tin tạm thời 3 (tất nhiên bạn có thể thay đổi)
- Và không để bộ mô tả tập tin tạm thời này cho lệnh bên trong
Nếu bạn muốn bắt stderrmột số commandvào varbạn có thể làm
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Sau đó, bạn có tất cả:
echo "command gives $? and stderr '$var'";
Nếu commandđơn giản (không phải như thế a | b), bạn có thể bỏ nội tâm {}:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Được gói gọn trong một bashchức năng dễ sử dụng lại (có thể cần phiên bản 3 trở lên local -n):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Giải thích:
local -nbí danh "$ 1" (là biến cho catch-stderr)
3>&1 sử dụng mô tả tập tin 3 để lưu các điểm xuất sắc
{ command; } (hoặc "$ @") sau đó thực thi lệnh trong quá trình bắt đầu ra $(..)
- Xin lưu ý rằng thứ tự chính xác rất quan trọng ở đây (thực hiện sai cách xáo trộn các mô tả tệp sai):
2>&1chuyển hướng stderrđến việc bắt đầu ra$(..)
1>&3chuyển hướng stdouttừ đầu ra bắt $(..)trở lại "bên ngoài" stdoutđã được lưu trong bộ mô tả tệp 3. Lưu ý rằng stderrvẫn đề cập đến nơi FD 1 đã chỉ trước: Để bắt đầu ra$(..)
3>&-sau đó đóng bộ mô tả tệp 3 vì nó không còn cần thiết nữa, như vậy commandkhông đột nhiên có một số mô tả tệp mở không xác định hiển thị. Lưu ý rằng lớp vỏ bên ngoài vẫn có FD 3 mở, nhưng commandsẽ không nhìn thấy nó.
- Điều thứ hai là quan trọng, bởi vì một số chương trình như
lvmphàn nàn về mô tả tập tin bất ngờ. Và lvmphàn nàn với stderr- chỉ những gì chúng ta sẽ nắm bắt!
Bạn có thể bắt bất kỳ mô tả tập tin nào khác với công thức này, nếu bạn thích ứng. Tất nhiên ngoại trừ mô tả tệp 1 (ở đây logic chuyển hướng sẽ sai, nhưng đối với mô tả tệp 1, bạn chỉ có thể sử dụng var=$(command)như bình thường).
Lưu ý rằng điều này hy sinh bộ mô tả tệp 3. Nếu bạn tình cờ cần bộ mô tả tệp đó, vui lòng thay đổi số. Nhưng hãy lưu ý rằng một số shell (từ những năm 1980) có thể hiểu 99>&1là đối số 9theo sau 9>&1(điều này không có vấn đề gì bash).
Cũng lưu ý rằng không dễ để tạo cấu hình FD 3 này thông qua một biến. Điều này làm cho mọi thứ rất khó đọc:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Lưu ý bảo mật:catch-var-from-fd-by-fd Không được lấy 3 đối số đầu tiên từ bên thứ 3. Luôn cung cấp cho họ một cách rõ ràng theo kiểu "tĩnh".
Vì vậy, không-không-không catch-var-from-fd-by-fd $var $fda $fdb $command, không bao giờ làm điều này!
Nếu bạn tình cờ chuyển vào một tên biến khác nhau, ít nhất hãy làm như sau:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Điều này vẫn không bảo vệ bạn trước mọi khai thác, nhưng ít nhất là giúp phát hiện và tránh các lỗi script phổ biến.
Ghi chú:
catch-var-from-fd-by-fd var 2 3 cmd.. giống như catch-stderr var cmd..
shift || returnchỉ là một số cách để ngăn chặn các lỗi xấu trong trường hợp bạn quên đưa ra số lượng đối số chính xác. Có lẽ chấm dứt shell sẽ là một cách khác (nhưng điều này làm cho nó khó kiểm tra từ dòng lệnh).
- Các thói quen đã được viết như vậy, nó là dễ hiểu hơn. Người ta có thể viết lại chức năng mà nó không cần
exec, nhưng sau đó nó trở nên thực sự xấu xí.
- Thói quen này có thể được viết lại cho không phải
bashcũng như vậy mà không cần local -n. Tuy nhiên, sau đó bạn không thể sử dụng các biến cục bộ và nó trở nên cực kỳ xấu xí!
- Cũng lưu ý rằng các
evals được sử dụng một cách an toàn. Thường evalđược coi là nguy hiểm. Tuy nhiên trong trường hợp này, nó không tệ hơn việc sử dụng "$@"(để thực thi các lệnh tùy ý). Tuy nhiên, hãy chắc chắn sử dụng trích dẫn chính xác và chính xác như được hiển thị ở đây (nếu không nó sẽ trở nên rất nguy hiểm ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)