Menguji apakah deskriptor file valid

12

Saya ingin membuat bash script menghasilkan informasi tambahan ke file descriptors (FDs) lebih besar dari atau sama dengan 3, ketika mereka terbuka. Untuk menguji apakah FD terbuka, saya menyusun trik berikut:

if (printf '' 1>&3) 2>&-; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

Ini cukup untuk kebutuhan saya, tapi saya ingin tahu apakah ada cara yang lebih idiomatis untuk menguji apakah FD itu valid. Saya terutama tertarik tentang apakah ada pemetaan fcntl(1)syscall ke perintah shell, yang akan memungkinkan pengambilan bendera FD ( O_WRONLYdan O_RDWRuntuk menguji apakah FD dapat ditulisi, dan O_RDONLYdan O_RDWRuntuk menguji apakah FD dapat dibaca).

Witiko
sumber

Jawaban:

12

Dalam ksh(baik varian AT&T dan pdksh) atau zsh, Anda dapat melakukan:

if print -nu3; then
  echo fd 3 is writeable
fi

Mereka tidak akan menulis apa pun di fd itu, tetapi masih memeriksa apakah fd dapat ditulis (menggunakan fcntl(3, F_GETFL)) dan melaporkan kesalahan sebaliknya:

$ ksh -c 'print -nu3' 3< /dev/null
ksh: print: -u: 3: fd not open for writing

(yang dapat Anda redirect ke /dev/null).

Dengan bash, saya pikir satu-satunya pilihan Anda adalah untuk memeriksa apakah dup()berhasil seperti dalam pendekatan Anda, meskipun itu tidak akan menjamin bahwa fd dapat ditulisi (atau memanggil utilitas eksternal ( zsh/ perl...) untuk melakukan fcntl()).

Perhatikan bahwa dalam bash(seperti kebanyakan shell), jika Anda menggunakan (...)alih-alih {...;}, itu akan melakukan proses tambahan. Kamu bisa menggunakan:

if { true >&3; } 2<> /dev/null

alih-alih menghindari garpu (kecuali di shell Bourne di mana pengarahan ulang perintah majemuk selalu menyebabkan subkulit). Jangan gunakan :bukan truekarena itu builtin khusus , jadi akan menyebabkan shell keluar ketika bash dalam mode kepatuhan POSIX.

Namun Anda dapat mempersingkat menjadi:

if { >&3; } 2<> /dev/null
Stéphane Chazelas
sumber
@ mikeserve, re: suntingan Anda, ada apa dengan itu <>? Shell tidak akan membaca dari stderrnya, mengapa Anda ingin membukanya di read + write? Apa maksud Anda dengan apa yang terjadi pada intrinsik? ?
Stéphane Chazelas
7

Dalam deskripsi Penggunaan Aplikasi POSIX Anda akan menemukan yang berikut:command

Ada beberapa keuntungan untuk menekan karakteristik khusus built-in khusus. Sebagai contoh:

command exec > unwritable-file

tidak menyebabkan skrip non-interaktif dibatalkan, sehingga status output dapat diperiksa oleh skrip.

Inilah sebabnya mengapa Anda bisa melakukan:

if    command >&3
then  echo 3 is open >&3
else  ! echo 3 is not open
fi    2<>/dev/null

Atau...

{ command >&3
  printf %s\\n%.0d  string "0$(($??8:0))" >&"$(($??1:3))"
} 2<>/dev/null

Yang akan menulis string diikuti oleh \newline baik ke stdout atau 3 dan masih meneruskan status keluar non-nol ketika 3 tidak terbuka karena matematika dilakukan pada $?gagal gagal mengubah oktal 08 ke % desimal tetapi memotong apa-apa sama sekali oktal 00 .

Atau...

command exec >&3 || handle_it

Tetapi jika Anda menggunakan ksh93, Anda bisa melakukan:

fds

Untuk daftar deskriptor file terbuka. Tambahkan -luntuk melihat ke mana mereka pergi.

mikeserv
sumber
3

Deskriptor file terbuka dapat ditemukan di /proc/<pid>/fd. Untuk membuat daftar, misalnya, deskriptor file terbuka dari shell saat ini Anda dapat mengeluarkan ls -l /proc/$$/fdyang seharusnya memberi Anda sesuatu seperti:

total 0
lrwx------ 1 testuser testuser 64 jun  1 09:11 0 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 1 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 2 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:39 255 -> /dev/pts/3

Saat Anda membuka file menggunakan:

touch /tmp/myfile
exec 7</tmp/myfile

Itu harus terdaftar oleh yang baru ls -l /proc/$$/fd:

lr-x------ 1 testuser testuser 64 jun  1 09:11 7 -> /tmp/myfile

Jika Anda menutup file deskriptor lagi menggunakannya exec 7>&-juga tidak terdaftar /proc/$$/fdlagi.

Lambert
sumber
2
Semua ini cukup spesifik untuk Linux. FWIW.
lcd047
1
Mengujinya di Linux maupun di Solaris (10 dan 11). Perbedaannya adalah bahwa Anda perlu menggunakan pfiles <pid>untuk melihat deskriptor file mana yang terhubung ke file mana saat ls -lmenampilkan koneksi di Linux.
Lambert
Saya suka kekompakannya [ -e /proc/$$/fd/3 ], tapi saya lebih suka untuk tidak bergantung pada procfs, karena sudah usang di FreeBSD dan mungkin juga un * ces lainnya.
Witiko
1
Membawa saya ke alternatif menggunakan pfiles <pid>atau lsof -p <pid>untuk melihat file deskriptor mana yang terbuka.
Lambert
1
/procsama sekali tidak ada di OpenBSD. Pada FreeBSD dan NetBSD harus mount-ed secara eksplisit, dan /proc/<PID>tidak memiliki subdirektori fd.
lcd047
3

Trik Anda terlihat lucu; tetapi untuk cara idiomatis saya bertanya-tanya mengapa Anda tidak menggunakan:

if ( exec 1>&3 ) 2>&-
Janis
sumber
Ini memang cara yang lebih bersih.
Witiko
5
Itu menciptakan subkulit meskipun yang paling shell berarti proses forking. Itu tidak menjamin fd dapat ditulisi. Anda dapat menggunakan { true >&3; } 2> /dev/nulluntuk menghindari garpu. Atau { command exec >&3; } 2> /dev/nulljika Anda ingin mengarahkan stdout ke sana.
Stéphane Chazelas
@Sthane; Trik subkulit yang diciptakan @Witiko adalah untuk tidak memengaruhi deskriptor file dari lingkungan saat ini saat menggunakan pengalihan untuk mendapatkan pengalihan. - Bisakah Anda menguraikan "tulisan yang dapat ditulis" yang Anda sebutkan?
Janis
2
{ true >&3; } 2> /dev/nulltidak akan memengaruhi lingkungan saat ini juga dan tidak akan bercabang (kecuali di shell Bourne). Maksud saya, (exec 1>&3) 2>&-akan mengembalikan true untuk membuka fd dalam mode read-only.
Stéphane Chazelas
1
execmenjadi builtin khusus akan keluar dari shell jika gagal (untuk bash, hanya ketika dalam mode kepatuhan POSIX). command execmencegah itu. truebukan builtin khusus. Catat itu execdan command execmempengaruhi lingkungan saat ini (itu sebabnya saya katakan jika Anda ingin mengarahkan stdout ke sana ).
Stéphane Chazelas
-1

Jika Anda tertarik pada solusi forking rendah sehingga menggunakannya berulang kali, saya akan menyarankan fungsi ini:

checkfd () {
    exec 2> / dev / null
    jika exec> & 3; kemudian
        exec 1> / dev / tty
        echo "fd3 OK"
    lain
        gema "fd3 KO"
    fi
    exec 2> / dev / tty
}

Dan inilah yang ia hasilkan dengan zsh:

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3 OKE
$
dan
sumber
Dalam kebanyakan shell exec >&3akan membunuh shell ketika 3 tidak terbuka.
mikeserv
Setidaknya itu berfungsi zshdan bash. Bisakah Anda memberikan shell yang execmenyebabkan kegagalan exit?
dan
Ya. Di bashlakukan set -o posixdan coba lagi. Dalam zsh... saya pikir ini masalah pengaturan env var POSIX_BUILTINSke nilai tidak-nol - tetapi saya lupa begitu saja. Dalam kasus apa pun, zshini bukan shell yang berupaya untuk kepatuhan POSIX, dan karenanya merupakan standar non-standar. Kedua cangkang itu menghindari kompatibilitas untuk apa yang beberapa orang yakini kenyamanan.
mikeserv
Ini juga bekerja pada shell Bourne polos.
dan
Di bash, dengan set -o posixmencoba berhasil.
dan
-1

Ini tampaknya sangat mudah (lihat komentar):

[ -r /proc/$$/fd/$FD ] && echo "File descriptor $FD is readable"
[ -w /proc/$$/fd/$FD ] && echo "File descriptor $FD is writable"

Sebagai tambahan ... Tes [-r file] tidak menunjukkan apakah ada data yang benar-benar menunggu untuk dibaca (/ dev / null melewati tes ini (lihat komentar)).

[ -r /proc/$$/fd/4 ] \
  && [ read -t 0.0001 -N 0 <&4 ] \
  && echo "Data is waiting to be read from file descriptor 4"

Sejumlah kecil untuk argumen batas waktu (baca -t) diperlukan atau data yang perlu perhitungan mungkin terlewatkan. Tes yang dapat dibaca ([-r file]) diperlukan atau perintah baca akan gagal jika file tidak dapat dibaca. Ini tidak akan benar-benar membaca data apa pun karena jumlah byte adalah nol (baca -N 0).

Paul
sumber
jika Anda akan menggunakan sistem Linux, Anda mungkin perlu melihat /proc/<pid>/fdinfo/<fd>, yang mencantumkan semua mode file terbuka di bawah flags:- lihat di sini . Untuk alasan mengapa bagian kedua Anda (bahkan setelah memperbaiki kesalahan mencolok): read -t .1 -N0 <&4tidak akan memberi tahu apakah ada data untuk dibaca di fd 4: coba saja 4</dev/null.
Mosvy
Dan tentu saja, [ -r /proc/$$/fd/$FD ]tidak memberi tahu Anda apakah deskriptor file $FDdapat dibaca, tetapi jika file itu dibuka dapat dibuka lagi , dengan deskriptor file lain, untuk membaca:exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy
-1

Pertanyaannya sudah cukup lama - tetapi bagaimanapun juga - mengapa tidak menggunakan builtin?

for i in {0..5} ; do if [ -t $i ]; then echo "$i is a valid FD"; else echo "$i is INVALID FD"; fi; done

Keluaran:

0 is a valid FD
1 is a valid FD
2 is a valid FD
3 is INVALID FD
4 is INVALID FD
5 is INVALID FD

Jadi, untuk menjawab pertanyaan - akan menyarankan:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi
Dimas
sumber
-ttidak menguji apakah deskriptor file valid, tetapi jika itu terhubung ke tty. Tambahkan echo yup |ke skrip Anda, dan akan mengatakan bahwa 0 is INVALID FD, meskipun sebenarnya itu sangat valid, sebuah pipa.
Mosvy