Penggunaan praktis untuk memindahkan deskriptor file

16

Menurut halaman bash man:

Operator pengalihan

   [n]<&digit-

memindahkan deskriptor file digitke deskriptor file n, atau input standar (file deskriptor 0) jika ntidak ditentukan. digitditutup setelah diduplikasi ke n.

Apa artinya "memindahkan" deskriptor file ke file lain? Apa situasi khas untuk latihan seperti itu?

Quentin
sumber

Jawaban:

14

3>&4-adalah ekstensi ksh93 juga didukung oleh bash dan itu adalah kependekan 3>&4 4>&-, yaitu 3 sekarang menunjuk ke tempat 4 dulu, dan 4 sekarang ditutup, jadi apa yang ditunjukkan oleh 4 sekarang telah pindah ke 3.

Penggunaan umum adalah dalam kasus di mana Anda telah menggandakan stdinatau stdoutmenyimpan salinannya dan ingin mengembalikannya, seperti di:

Misalkan Anda ingin menangkap stderr dari sebuah perintah (dan hanya stderr) sambil meninggalkan stdout sendirian dalam sebuah variabel.

Substitusi perintah var=$(cmd), membuat pipa. Ujung penulisan pipa menjadi cmdstdout (file descriptor 1) dan ujung lainnya dibaca oleh shell untuk mengisi variabel.

Sekarang, jika Anda ingin stderrpergi ke variabel, Anda bisa melakukan: var=$(cmd 2>&1). Sekarang keduanya fd 1 (stdout) dan 2 (stderr) pergi ke pipa (dan akhirnya ke variabel), yang hanya setengah dari yang kita inginkan.

Jika kita lakukan var=$(cmd 2>&1-)(kependekan var=$(cmd 2>&1 >&-), sekarang hanya cmdstderr yang masuk ke pipa, tetapi fd 1 ditutup. Jika cmdmencoba menulis output apa pun, itu akan kembali dengan EBADFkesalahan, jika membuka file, itu akan mendapatkan fd gratis pertama dan file terbuka akan ditugaskan untuk itu stdoutkecuali perintah menjaga itu! Bukan juga yang kita inginkan.

Jika kita ingin stdout cmddibiarkan sendiri, yaitu menunjuk ke sumber daya yang sama dengan yang menunjuk ke luar substitusi perintah, maka kita perlu entah bagaimana membawa sumber daya itu ke dalam substitusi perintah. Untuk itu kita bisa melakukan salinan substitusi perintah di stdout luar untuk membawanya masuk.

{
  var=$(cmd)
} 3>&1

Yang merupakan cara yang lebih bersih untuk menulis:

exec 3>&1
var=$(cmd)
exec 3>&-

(yang juga memiliki manfaat mengembalikan fd 3 daripada menutupnya pada akhirnya).

Kemudian pada {(atau exec 3>&1) dan ke atas }, fd 1 dan 3 menunjuk ke sumber daya yang sama yang ditunjukkan fd 1 pada awalnya. fd 3 juga akan menunjuk ke sumber daya itu di dalam substitusi perintah (substitusi perintah hanya mengalihkan fd 1, stdout). Jadi di atas, untuk cmd, kita punya untuk fds 1, 2, 3:

  1. pipa ke var
  2. tidak tersentuh
  3. sama seperti apa yang 1 poin di luar substitusi perintah

Jika kami mengubahnya menjadi:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

Maka itu menjadi:

  1. sama seperti apa yang 1 poin di luar substitusi perintah
  2. pipa ke var
  3. sama seperti apa yang 1 poin di luar substitusi perintah

Sekarang, kita sudah mendapatkan apa yang kita inginkan: stderr pergi ke pipa dan stdout tidak tersentuh. Namun, kami membocorkan bahwa fd 3 ke cmd.

Sementara perintah (berdasarkan konvensi) menganggap fds 0 hingga 2 terbuka dan menjadi input, output dan kesalahan standar, mereka tidak menganggap fds lainnya. Kemungkinan besar mereka akan membiarkan fd 3 itu tidak tersentuh. Jika mereka membutuhkan deskriptor file lain, mereka hanya akan melakukan open()/dup()/socket()...yang akan mengembalikan deskriptor file pertama yang tersedia. Jika (seperti skrip shell yang melakukan exec 3>&1) mereka perlu menggunakannya fdsecara khusus, mereka pertama-tama akan menugaskannya untuk sesuatu (dan dalam proses itu, sumber daya yang dimiliki oleh fd 3 kami akan dirilis oleh proses itu).

Adalah praktik yang baik untuk menutup fd 3 karena cmdtidak menggunakannya, tetapi bukan masalah besar jika kita membiarkannya ditugaskan sebelum kita menelepon cmd. Masalahnya mungkin: bahwa cmd(dan berpotensi proses lain yang ditimbulkannya) memiliki satu fd lebih sedikit tersedia untuk itu. Masalah yang berpotensi lebih serius adalah jika sumber daya yang ditunjuk oleh fd mungkin berakhir dipegang oleh proses yang cmddilatarbelakangi oleh itu . Ini bisa menjadi masalah jika sumber daya itu adalah pipa atau saluran komunikasi antar-proses lainnya (seperti ketika skrip Anda dijalankan sebagai script_output=$(your-script)), karena itu berarti proses membaca dari ujung yang lain tidak akan pernah melihat akhir file sampai saat itu proses latar belakang berakhir.

Jadi di sini, lebih baik menulis:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

Yang, dengan bashdapat disingkat menjadi:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

Untuk meringkas alasan mengapa itu jarang digunakan:

  1. itu gula non-standar dan hanya sintaksis. Anda harus menyeimbangkan penghematan beberapa penekanan tombol dengan membuat skrip Anda lebih portabel dan kurang jelas bagi orang-orang yang tidak terbiasa dengan fitur yang tidak biasa itu.
  2. Kebutuhan untuk menutup fd asli setelah menduplikatnya sering diabaikan karena sebagian besar waktu, kita tidak menderita akibatnya, jadi kita lakukan saja >&3alih - alih >&3-atau >&3 3>&-.

Bukti bahwa itu jarang digunakan, karena Anda tahu itu palsu di bash . Dalam bash compound-command 3>&4-atau any-builtin 3>&4-daun fd 4 ditutup bahkan setelah compound-commandatau any-builtintelah kembali. Sebuah tambalan untuk memperbaiki masalah sekarang tersedia (2013-02-19).

Stéphane Chazelas
sumber
Terima kasih, sekarang saya tahu apa yang bergerak adalah fd. Saya memiliki 4 pertanyaan dasar mengenai cuplikan ke-2 (dan fds secara umum): 1) Di cmd1, Anda membuat 2 (stderr) menjadi salinan 3, bagaimana jika perintah secara internal akan menggunakan 3 fd ini? 2) Mengapa 3> & 1 dan 4> & 1 bekerja? Duplikasi 3 dan 4 berlaku hanya dalam dua cmds itu, apakah shell saat ini juga terpengaruh? 3) Mengapa Anda menutup 4 in cmd1 dan 3 in cmd2? Perintah-perintah itu tidak menggunakan fds yang disebutkan, bukan? 4) Dalam cuplikan terakhir, apa yang terjadi jika fd diduplikasi ke yang tidak ada (dalam cmd1 dan cmd2), maksud saya masing-masing 3 dan 4?
Quentin
@ Quentin, saya telah menggunakan contoh yang lebih sederhana dan menjelaskan sedikit lebih berharap sekarang menimbulkan lebih sedikit pertanyaan daripada jawaban. Jika Anda masih memiliki pertanyaan yang tidak terkait langsung dengan sintaks bergerak fd, saya sarankan Anda mengajukan pertanyaan terpisah
Stéphane Chazelas
{ var=$(cmd 2>&1 >&3) ; } 3>&1- Bukankah itu salah ketik di penutupan 1?
Quentin
@ Quentin, Ini salah ketik yang menurut saya tidak dimaksudkan untuk memasukkannya, namun tidak ada bedanya karena tidak ada di dalam kawat gigi menggunakan yang fd 1 (itu adalah seluruh titik duplikasi ke fd 3: karena asli 1 jika tidak, tidak akan dapat diakses di dalam $(...)).
Stéphane Chazelas
1
@ Quentin Saat masuk {...}, fd 3 poin ke apa fd 1 digunakan untuk menunjuk dan fd 1 ditutup, kemudian setelah masuk $(...), fd 1 diatur ke pipa yang mengumpankan $var, kemudian untuk cmd2 ke yang juga, dan kemudian 1 ke apa 3 poin untuk, itu adalah bagian luar 1. Fakta bahwa 1 tetap ditutup setelahnya adalah bug di bash, saya akan melaporkannya. ksh93 dari mana fitur itu berasal tidak memiliki bug itu.
Stéphane Chazelas
4

Ini berarti untuk mengarahkannya ke tempat yang sama dengan deskriptor file lainnya. Anda perlu melakukan hal ini sangat jarang, selain penanganan terpisah jelas dari kesalahan descriptor standar ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Ini bisa berguna dalam beberapa kasus kompleks.

Panduan Skrip Bash Lanjutan memiliki contoh level log yang lebih panjang ini dan cuplikan ini:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

Dalam Sorcery Sumber Mage kita misalnya menggunakannya untuk membedakan output yang berbeda dari blok kode yang sama:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

Ini memiliki substitusi proses tambahan ditambahkan untuk alasan logging (VOYEUR memutuskan apakah data harus ditampilkan di layar atau hanya log), tetapi beberapa pesan harus selalu disajikan. Untuk mencapai itu, kami mencetaknya ke file descriptor 3 dan kemudian menanganinya secara khusus.

lynxlynxlynx
sumber
0

Di Unix, file ditangani oleh deskriptor file (bilangan bulat kecil, misalnya input standar adalah 0, output standar adalah 1, kesalahan standar adalah 2; saat Anda membuka file lain, mereka biasanya ditugaskan deskriptor terkecil yang tidak digunakan). Jadi, jika Anda mengetahui inards dari program ini, dan Anda ingin mengirim output yang masuk ke file deskriptor 5 ke output standar, Anda akan memindahkan deskriptor 5 ke 1. Di situlah 2> errorsberasal, dan konstruksi ingin 2>&1menduplikasi kesalahan ke dalam aliran output.

Jadi, hampir tidak pernah digunakan (saya samar-samar ingat menggunakannya sekali atau dua kali dalam kemarahan dalam 25 + tahun penggunaan Unix yang hampir eksklusif), tetapi ketika dibutuhkan sangat penting.

vonbrand
sumber
Tetapi mengapa tidak menduplikasi file deskriptor 1 dengan cara berikut: 5> & 1? Saya tidak mengerti apa gunanya memindahkan FD karena kernel akan segera menutupnya setelah ...
Quentin
Itu tidak menggandakan 5 menjadi 1, itu mengirim 5 ke tempat 1 pergi. Dan sebelum Anda bertanya; ya, ada beberapa notasi yang berbeda, diambil dari berbagai cangkang prekursor.
vonbrand
Masih belum mengerti. Jika 5>&1mengirim 5 ke tempat 1 pergi, lalu apa yang sebenarnya 1>&5-dilakukan, selain menutup 5?
Quentin