Untuk kepentingan pembaca, resep ini ada di sini
- dapat digunakan kembali sebagai oneliner untuk menangkap stderr menjadi variabel
- masih memberikan akses ke kode kembali perintah
- Mengorbankan deskriptor file sementara 3 (yang tentu saja dapat diubah oleh Anda)
- Dan tidak mengekspos deskriptor file sementara ini ke perintah dalam
Jika Anda ingin menangkap stderr
beberapa command
ke dalam var
Anda dapat melakukannya
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Setelah itu Anda memiliki semuanya:
echo "command gives $? and stderr '$var'";
Jika command
sederhana (bukan sesuatu seperti a | b
) Anda dapat meninggalkan batin {}
:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Dibungkus menjadi bash
fungsi -mudah yang dapat digunakan kembali (mungkin membutuhkan versi 3 dan di atas untuk local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Dijelaskan:
local -n
alias "$ 1" (yang merupakan variabel untuk catch-stderr
)
3>&1
menggunakan file descriptor 3 untuk menyimpan poin stdout di sana
{ command; }
(atau "$ @") kemudian mengeksekusi perintah dalam output capturing $(..)
- Harap perhatikan bahwa urutan pastinya penting di sini (melakukannya dengan cara yang salah mengacak deskriptor file dengan salah):
2>&1
pengalihan stderr
ke penangkapan output$(..)
1>&3
redirect stdout
dari output menangkap $(..)
kembali ke "luar" stdout
yang disimpan dalam file descriptor 3. Perhatikan bahwa stderr
masih merujuk ke tempat FD 1 menunjuk sebelumnya: Ke output menangkap$(..)
3>&-
kemudian menutup file deskriptor 3 karena tidak diperlukan lagi, sehingga command
tidak tiba-tiba muncul beberapa deskriptor file terbuka yang tidak dikenal. Perhatikan bahwa cangkang luar masih memiliki FD 3 terbuka, tetapi command
tidak akan melihatnya.
- Yang terakhir ini penting, karena beberapa program suka
lvm
mengeluh tentang deskriptor file yang tidak terduga. Dan lvm
mengeluh stderr
- hanya apa yang akan kita tangkap!
Anda dapat menangkap deskriptor file lain dengan resep ini, jika Anda menyesuaikannya. Kecuali deskriptor file 1 tentu saja (di sini logika redirection akan salah, tetapi untuk deskriptor file 1 Anda dapat menggunakan var=$(command)
seperti biasa).
Perhatikan bahwa ini deskriptor file pengorbanan 3. Jika Anda membutuhkan deskriptor file itu, jangan ragu untuk mengubah nomornya. Namun perlu diperhatikan, bahwa beberapa kerang (dari tahun 1980-an) dapat dipahami 99>&1
sebagai argumen yang 9
diikuti 9>&1
(ini bukan masalah bagi bash
).
Perhatikan juga bahwa tidak mudah untuk membuat FD 3 ini dapat dikonfigurasi melalui variabel. Ini membuat banyak hal menjadi tidak terbaca:
: 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>&-";
}
Catatan keamanan: 3 argumen pertama yang catch-var-from-fd-by-fd
tidak boleh diambil dari pihak ke-3. Selalu berikan mereka secara eksplisit dalam mode "statis".
Jadi tidak-tidak-tidak catch-var-from-fd-by-fd $var $fda $fdb $command
, jangan pernah lakukan ini!
Jika Anda memasukkan nama variabel variabel, setidaknya lakukan sebagai berikut:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Ini masih tidak akan melindungi Anda dari setiap eksploitasi, tetapi setidaknya membantu untuk mendeteksi dan menghindari kesalahan penulisan skrip yang umum.
Catatan:
catch-var-from-fd-by-fd var 2 3 cmd..
sama dengan catch-stderr var cmd..
shift || return
hanyalah beberapa cara untuk mencegah kesalahan jelek jika Anda lupa untuk memberikan jumlah argumen yang benar. Mungkin menghentikan shell akan menjadi cara lain (tetapi ini membuatnya sulit untuk menguji dari commandline).
- Rutinitas ditulis sedemikian rupa, sehingga lebih mudah dipahami. Seseorang dapat menulis ulang fungsi sedemikian rupa sehingga tidak perlu
exec
, tetapi kemudian menjadi sangat jelek.
- Rutin ini dapat ditulis ulang untuk non
bash
-juga sehingga tidak perlu local -n
. Namun kemudian Anda tidak dapat menggunakan variabel lokal dan itu menjadi sangat jelek!
- Perhatikan juga bahwa
eval
s digunakan dengan cara yang aman. Biasanya eval
dianggap berbahaya. Namun dalam hal ini tidak lebih jahat daripada menggunakan "$@"
(untuk menjalankan perintah sewenang-wenang). Namun tolong pastikan untuk menggunakan kutipan yang tepat dan benar seperti yang ditunjukkan di sini (kalau tidak menjadi sangat berbahaya ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)