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 stderr
một số command
vào var
bạ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 bash
chứ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 -n
bí 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>&1
chuyển hướng stderr
đến việc bắt đầu ra$(..)
1>&3
chuyển hướng stdout
từ đầ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 stderr
vẫ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 command
khô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 command
sẽ 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ư
lvm
phàn nàn về mô tả tập tin bất ngờ. Và lvm
phà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>&1
là đối số 9
theo 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 || return
chỉ 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
bash
cũ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
eval
s đượ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)