Kapan Anda akan menggunakan deskriptor file tambahan?

74

Saya tahu Anda dapat membuat file descriptor dan mengarahkan output ke sana. misalnya

exec 3<> /tmp/foo # open fd 3.
echo a >&3 # write to it
exec 3>&- # close fd 3.

Tetapi Anda dapat melakukan hal yang sama tanpa deskriptor file:

FILE=/tmp/foo
echo a > "$FILE"

Saya mencari contoh yang baik kapan Anda harus menggunakan deskriptor file tambahan.

dogbane
sumber

Jawaban:

50

Sebagian besar perintah memiliki saluran input tunggal (input standar, deskriptor file 0) dan saluran output tunggal (output standar, deskriptor file 1) atau beroperasi pada beberapa file yang dibuka sendiri (sehingga Anda memberikan mereka nama file). (Itu selain dari kesalahan standar (fd 2), yang biasanya memfilter semua jalan ke pengguna.) Namun kadang-kadang nyaman untuk memiliki perintah yang bertindak sebagai filter dari beberapa sumber atau ke beberapa target. Misalnya, inilah skrip sederhana yang memisahkan baris bernomor ganjil dalam file dari yang genap bernomor

while IFS= read -r line; do
  printf '%s\n' "$line"
  if IFS= read -r line; then printf '%s\n' "$line" >&3; fi
done >odd.txt 3>even.txt

Sekarang anggaplah Anda ingin menerapkan filter yang berbeda untuk garis bilangan ganjil dan ke garis genap (tetapi tidak menyatukannya kembali, itu akan menjadi masalah yang berbeda, tidak layak dari shell pada umumnya). Dalam shell, Anda hanya bisa menyalurkan output standar perintah ke perintah lain; untuk mem-pipe deskriptor file lain, Anda harus mengarahkannya ke fd 1 terlebih dahulu.

{ while  done | odd-filter >filtered-odd.txt; } 3>&1 | even-filter >filtered-even.txt

Kasus penggunaan lain yang lebih sederhana adalah memfilter output kesalahan dari suatu perintah .

exec M>&Nmengarahkan kembali deskriptor file ke yang lain untuk sisa skrip (atau sampai perintah lain mengubah deskriptor file lagi). Ada beberapa fungsi yang tumpang tindih antara exec M>&Ndan somecommand M>&N. The execbentuk lebih kuat dalam hal itu tidak harus bersarang:

exec 8<&0 9>&1
exec >output12
command1
exec <input23
command2
exec >&9
command3
exec <&8

Contoh lain yang mungkin menarik:

Dan untuk lebih banyak contoh:

NB Ini adalah pertanyaan mengejutkan yang berasal dari penulis pos paling terunggah di situs yang menggunakan pengalihan melalui fd 3 !

Gilles
sumber
Saya lebih suka mengatakan bahwa "sebagian besar perintah memiliki saluran keluaran tunggal atau ganda - stdout (fd 1) dan sangat sering stderr (fd 2)".
rozcietrzewiacz
Juga, bisakah Anda menjelaskan mengapa Anda menggunakan while IFS= read -r line;? Cara saya melihatnya, IFS tidak berpengaruh di sini karena Anda menetapkan nilai hanya satu variabel ( baris ). Lihat pertanyaan ini.
rozcietrzewiacz
@rozcietrzewiacz Saya telah menyebutkan stderr, dan melihat bagian pertama dari jawaban saya untuk alasan mengapa IFSmembuat perbedaan bahkan jika Anda membaca variabel tunggal (itu untuk mempertahankan spasi putih terkemuka).
Gilles
Tidak bisakah Anda melakukan hal yang sama dengannya sed -ne 'w odd.txt' -e 'n;w even.txt'?
Wildcard
1
@ Kartu Memori Anda dapat melakukan hal yang sama dengan alat lain, tentu saja. Tetapi tujuan dari jawaban ini adalah untuk menggambarkan pengalihan dalam shell.
Gilles
13

Berikut ini contoh menggunakan FD tambahan sebagai kontrol obrolan script bash:

#!/bin/bash

log() {
    echo $* >&3
}
info() {
    echo $* >&4
}
err() {
    echo $* >&2
}
debug() {
    echo $* >&5
}

VERBOSE=1

while [[ $# -gt 0 ]]; do
    ARG=$1
    shift
    case $ARG in
        "-vv")
            VERBOSE=3
        ;;
        "-v")
            VERBOSE=2
        ;;
        "-q")
            VERBOSE=0
        ;;
        # More flags
        *)
        echo -n
        # Linear args
        ;;
    esac
done

for i in 1 2 3; do
    fd=$(expr 2 + $i)
    if [[ $VERBOSE -ge $i ]]; then
        eval "exec $fd>&1"
    else
        eval "exec $fd> /dev/null"
    fi
done

err "This will _always_ show up."
log "This is normally displayed, but can be prevented with -q"
info "This will only show up if -v is passed"
debug "This will show up for -vv"
Fordi
sumber
8

Dalam konteks pipa bernama (fifos) penggunaan deskriptor file tambahan dapat mengaktifkan perilaku perpipaan yang tidak memblokir.

(
rm -f fifo
mkfifo fifo
exec 3<fifo   # open fifo for reading
trap "exit" 1 2 3 15
exec cat fifo | nl
) &
bpid=$!

(
exec 3>fifo  # open fifo for writing
trap "exit" 1 2 3 15
while true;
do
    echo "blah" > fifo
done
)
#kill -TERM $bpid

Lihat: Named Pipe ditutup sebelum waktunya dalam skrip?

chad
sumber
1
Anda menggali salah satu pertanyaan lama saya :) chad benar, Anda akan mengalami kondisi balapan.
n0pe
6

Deskriptor file tambahan baik untuk ketika Anda ingin menangkap stdout dalam variabel namun masih ingin menulis ke layar, misalnya dalam antarmuka pengguna skrip bash

arg1 string to echo 
arg2 flag 0,1 print or not print to 3rd fd stdout descriptor   
function ecko3 {  
if [ "$2" -eq 1 ]; then 
    exec 3>$(tty) 
    echo -en "$1" | tee >(cat - >&3)
    exec 3>&- 
else 
    echo -en "$1"  
fi 
}
Adam Michael Danischewski
sumber
2
Saya tahu ini bukan jawaban baru, tetapi saya harus menatap ini sebentar untuk melihat apa yang dilakukannya dan berpikir akan sangat membantu jika seseorang menambahkan contoh fungsi ini sedang digunakan. Yang satu ini menggema dan menangkap seluruh output dari a perintah - df, dalam hal ini. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe
3

Berikut ini skenario lain ketika menggunakan deskriptor file tambahan yang tampaknya sesuai (dalam Bash):

Keamanan kata sandi shell script parameter baris perintah

env -i bash --norc   # clean up environment
set +o history
read -s -p "Enter your password: " passwd
exec 3<<<"$passwd"
mycommand <&3  # cat /dev/stdin in mycommand
bernard
sumber
1

Contoh: menggunakan kawanan untuk memaksa skrip dijalankan secara seri dengan kunci file

Salah satu contoh adalah dengan menggunakan penguncian file untuk memaksa skrip untuk menjalankan sistem seri lebar. Ini berguna jika Anda tidak ingin dua skrip dari jenis yang sama beroperasi pada file yang sama. Jika tidak, kedua skrip akan saling mengganggu dan kemungkinan data korup.

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#open file descriptor 3 for writing
exec 3> /tmp/file.lock

#create an exclusive lock on the file using file descriptor 3
#exit if lock could not be obtained
flock -n 3

#execute serial code

#remove the file while the lock is still obtained
rm -f /tmp/file.lock

#close the open file handle which releases the file lock and disk space
exec 3>&-

Gunakan kawanan secara fungsional dengan mendefinisikan kunci dan membuka kunci

Anda juga dapat menggunakan logika penguncian / penguncian ini menjadi fungsi yang dapat digunakan kembali. trapShell bawaan berikut akan secara otomatis melepaskan kunci file ketika skrip keluar (kesalahan atau berhasil). trapmembantu membersihkan kunci file Anda. Path /tmp/file.lockharus berupa jalur kode keras sehingga banyak skrip dapat mencoba untuk menguncinya.

# obtain a file lock and automatically unlock it when the script exits
function lock() {
  exec 3> /tmp/file.lock
  flock -n 3 && trap unlock EXIT
}

# release the file lock so another program can obtain the lock
function unlock() {
  # only delete if the file descriptor 3 is open
  if { >&3 ; } &> /dev/null; then
    rm -f /tmp/file.lock
  fi
  #close the file handle which releases the file lock
  exec 3>&-
}

The unlocklogika di atas adalah untuk menghapus file sebelum mengunci dilepaskan. Dengan cara ini membersihkan file kunci. Karena file itu dihapus, contoh lain dari program ini dapat memperoleh kunci file.

Penggunaan fungsi kunci dan buka kunci dalam skrip

Anda dapat menggunakannya dalam skrip Anda seperti contoh berikut.

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#try to lock (else exit because of non-zero exit code)
lock

#system-wide serial locked code

unlock

#non-serial code

Jika Anda ingin kode Anda menunggu hingga dapat mengunci, Anda dapat menyesuaikan skrip seperti:

set -e

#wait for lock to be successfully obtained
while ! lock 2> /dev/null; do
  sleep .1
done

#system-wide serial locked code

unlock

#non-serial code
Sam Gleske
sumber
0

Sebagai contoh konkret, saya hanya menulis sebuah skrip yang memerlukan informasi waktu dari sebuah sub-perintah. Menggunakan deskriptor file tambahan memungkinkan saya untuk menangkap timestderr perintah tanpa mengganggu stdout atau stderr subcommand.

(time ls -9 2>&3) 3>&2 2> time.txt

Apa yang dilakukan adalah mengarahkan lsstderr ke fd 3, mengarahkan fd 3 ke stderr skrip, dan mengarahkan timestderr ke sebuah file. Ketika skrip dijalankan, stdout dan stderr-nya sama dengan sub-perintah, yang dapat dialihkan seperti biasa. Hanya timekeluaran yang diarahkan ke file.

$ echo '(time ls my-example-script.sh missing-file 2>&3) 3>&2 2> time.txt' > my-example-script.sh
$ chmod +x my-example-script.sh 
$ ./my-example-script.sh 
ls: missing-file: No such file or directory
my-example-script.sh
$ ./my-example-script.sh > /dev/null
ls: missing-file: No such file or directory
$ ./my-example-script.sh 2> /dev/null
my-example-script.sh
$ cat time.txt

real    0m0.002s
user    0m0.001s
sys 0m0.001s
Ben Blank
sumber