ทดสอบว่า file descriptor ถูกต้องหรือไม่


12

ฉันต้องการสร้างสคริปต์ทุบตีข้อมูลเพิ่มเติมให้กับ file descriptors (FDs) มากกว่าหรือเท่ากับ 3 เมื่อเปิด เพื่อทดสอบว่า FD เปิดอยู่หรือไม่ฉันได้วางแผนเคล็ดลับต่อไปนี้:

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

นี่เพียงพอสำหรับความต้องการของฉัน แต่ฉันอยากรู้ว่ามีวิธีการทดสอบที่เป็นไปตามสำนวนมากขึ้นหรือไม่ถ้า FD นั้นถูกต้อง ฉันสนใจเป็นพิเศษเกี่ยวกับว่ามีการแม็พfcntl(1)syscall กับคำสั่งเชลล์หรือไม่ซึ่งจะอนุญาตให้เรียกใช้แฟล็ก FD ( O_WRONLYและ O_RDWRเพื่อทดสอบว่า FD สามารถเขียนได้หรือไม่O_RDONLYและ O_RDWRเพื่อทดสอบว่า FD อ่านได้หรือไม่)

คำตอบ:


12

ในksh(ทั้งรุ่น AT&T และ pdksh) หรือzshคุณสามารถทำได้:

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

พวกเขาจะไม่เขียนอะไรบน fd นั้น แต่ก็ยังตรวจสอบว่า fd นั้นเขียนได้ (โดยใช้fcntl(3, F_GETFL)) และรายงานข้อผิดพลาดเป็นอย่างอื่น:

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

(ซึ่งคุณสามารถเปลี่ยนเส้นทางไป/dev/null)

ด้วยbashฉันคิดว่าตัวเลือกเดียวของคุณคือการตรวจสอบว่าdup()ประสบความสำเร็จในแนวทางของคุณหรือไม่แม้ว่าจะไม่รับประกันว่า fd นั้นสามารถเขียนได้ (หรือเรียกใช้ยูทิลิตี้ภายนอก ( zsh/ perl... ) เพื่อทำสิ่งนั้นfcntl())

โปรดทราบว่าในbash(เช่นเชลล์ส่วนใหญ่) หากคุณใช้(...)แทน{...;}นั่นจะแยกเป็นกระบวนการพิเศษ คุณสามารถใช้ได้:

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

แทนเพื่อหลีกเลี่ยงการแยก (ยกเว้นในเชลล์เป้าหมายซึ่งคำสั่งการเปลี่ยนเส้นทางแบบผสมมักทำให้เกิด subshell เสมอ) อย่าใช้:แทนtrueเพราะมันเป็นbuiltin พิเศษดังนั้นจะทำให้เชลล์ออกเมื่อ bash อยู่ในโหมดการปฏิบัติตาม POSIX

อย่างไรก็ตามคุณสามารถย่อให้สั้นลงเป็น:

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

@mikeserve อีกครั้ง: การแก้ไขของคุณคือ<>อะไร เชลล์ไม่อ่านจาก stderr ของมันทำไมคุณถึงต้องการเปิดมันใน read + write? คุณหมายถึงอะไรกับสิ่งที่เกิดขึ้นกับเนื้อแท้? ?
Stéphane Chazelas

7

ในคำอธิบายการใช้งาน POSIX แอปพลิเคชันคุณจะพบสิ่งต่อไปนี้:command

มีข้อดีบางประการในการระงับคุณลักษณะพิเศษของบิวด์อินพิเศษในบางโอกาส ตัวอย่างเช่น:

command exec > unwritable-file

ไม่ทำให้สคริปต์ไม่โต้ตอบยกเลิกเพื่อให้สถานะเอาต์พุตสามารถตรวจสอบได้โดยสคริปต์

นี่คือเหตุผลที่คุณสามารถทำได้:

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

หรือ...

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

ซึ่งจะเขียนสตริงตามด้วย\newline อย่างใดอย่างหนึ่งเพื่อ stdout หรือ 3 และยังคงผ่านสถานะทางออกที่ไม่ใช่ศูนย์เมื่อ 3 ไม่ได้เปิดเพราะคณิตศาสตร์ทำเมื่อ$?ลมขึ้นล้มเหลวในการแปลงเลขฐานแปด08ถึง%แต่ตัดให้ไม่มีอะไรเลย ฐานแปด00

หรือ...

command exec >&3 || handle_it

แต่ถ้าคุณใช้ksh93คุณสามารถทำได้:

fds

สำหรับรายการตัวอธิบายไฟล์ที่เปิด เพิ่ม-lเพื่อดูว่าพวกเขาไปที่ไหน


3

/proc/<pid>/fdเปิดอธิบายไฟล์สามารถพบได้ใน ยกตัวอย่างเช่นในรายการตัวอธิบายไฟล์ที่เปิดของเชลล์ปัจจุบันคุณสามารถออกls -l /proc/$$/fdซึ่งควรให้สิ่งที่คล้ายกับคุณ:

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

เมื่อคุณเปิดไฟล์โดยใช้:

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

มันควรจะถูกระบุไว้โดยใหม่ls -l /proc/$$/fd:

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

หากคุณปิดตัวอธิบายไฟล์อีกครั้งโดยใช้exec 7>&-มันจะไม่ปรากฏในรายการ/proc/$$/fdอีกต่อไป


2
ทั้งหมดนี้ค่อนข้างเฉพาะกับ Linux FWIW
lcd047

1
ทดสอบบน Linux เช่นเดียวกับบน Solaris (10 และ 11) ความแตกต่างคือคุณต้องใช้pfiles <pid>เพื่อดูไฟล์ descriptor เชื่อมต่อกับไฟล์ใดในขณะที่ls -lแสดงการเชื่อมต่อบน Linux
Lambert

ฉันชอบความกะทัดรัดของ[ -e /proc/$$/fd/3 ]แต่ฉันไม่ชอบที่จะพึ่งพา procfs เพราะมันเลิกใช้ใน FreeBSD และอาจจะเป็น un * ces อื่น ๆ เช่นกัน
Witiko

1
นำฉันไปสู่ทางเลือกของการใช้pfiles <pid>หรือlsof -p <pid>เพื่อดูว่า descriptor ไฟล์ใดที่เปิดอยู่
Lambert

1
/procไม่มีอยู่เลยใน OpenBSD บน FreeBSD และ NetBSD มันจะต้องมีmount-ed อย่างชัดเจนและไม่ได้มีไดเรกทอรีย่อย/proc/<PID> fd
lcd047

3

เคล็ดลับของคุณดูน่ารัก แต่สำหรับวิธีการใช้สำนวนฉันสงสัยว่าทำไมคุณไม่ใช้:

if ( exec 1>&3 ) 2>&-

นี่คือวิธีที่สะอาดกว่า
Witiko

5
ที่สร้างเชลล์ย่อยซึ่งเป็นเชลล์ส่วนใหญ่หมายถึงการฟอร์กกระบวนการ ไม่รับประกันว่า fd นั้นสามารถเขียนได้ คุณสามารถใช้{ true >&3; } 2> /dev/nullเพื่อหลีกเลี่ยงการแยก หรือ{ command exec >&3; } 2> /dev/nullถ้าคุณต้องการเปลี่ยนเส้นทาง stdout
Stéphane Chazelas

@Stephane; เคล็ดลับ subshell ที่ @Witiko เป็นผู้คิดค้นไม่ส่งผลกระทบต่อไฟล์ descriptors ของสภาพแวดล้อมปัจจุบันเมื่อใช้การเปลี่ยนเส้นทางเพื่อรับการเปลี่ยนเส้นทาง - คุณช่วยอธิบายเกี่ยวกับ"fd ที่เขียนได้" ที่คุณพูดถึงได้ไหม?
Janis

2
{ true >&3; } 2> /dev/nullจะไม่ส่งผลกระทบต่อสภาพแวดล้อมปัจจุบันและจะไม่แยก (ยกเว้นในเชลล์เป้าหมาย) ฉันหมายความว่ามัน(exec 1>&3) 2>&-จะกลับมาจริงสำหรับ fd เปิดในโหมดอ่านอย่างเดียว
Stéphane Chazelas

1
execเป็น builtin พิเศษจะออกจากเชลล์ถ้ามันล้มเหลว (สำหรับทุบตีเฉพาะเมื่ออยู่ในโหมดความสอดคล้อง POSIX) command execป้องกันไม่ให้ trueไม่ใช่ builtin พิเศษ โปรดทราบว่าexecและcommand execมีผลกระทบต่อสภาพแวดล้อมปัจจุบัน (นั่นเป็นเหตุผลที่ฉันบอกว่าถ้าคุณต้องการเปลี่ยนเส้นทาง stdout )
Stéphane Chazelas

-1

หากคุณสนใจโซลูชันการฟอร์กกิ้งต่ำเพื่อที่จะใช้ซ้ำได้ฉันจะแนะนำฟังก์ชั่นนี้:

checkfd () {
    exec 2> / dev / null
    หาก exec> & 3; แล้วก็
        exec 1> / dev / tty
        echo "fd3 ตกลง"
    อื่น
        echo "fd3 KO"
    Fi
    exec 2> / dev / tty
}

และนี่คือสิ่งที่มันผลิตขึ้นด้วยzsh:

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3 ตกลง
$

ในหอยส่วนใหญ่exec >&3จะฆ่าเปลือกเมื่อ 3 ไม่ได้เปิด
mikeserv

อย่างน้อยก็เป็นคนที่ทำงานและzsh bashคุณช่วยจัดเตรียมเชลล์ที่ความล้มเหลวexecเป็นสาเหตุexitหรือไม่?
แดน

ใช่. ในbashไม่set -o posixและลองอีกครั้ง ในzsh... ฉันคิดว่ามันเป็นเรื่องของการตั้งค่า env POSIX_BUILTINSให้เป็นค่าที่ไม่เป็นโมฆะ - แต่ฉันลืมไปเลย ไม่ว่าในกรณีใด ๆzshไม่ใช่เชลล์ที่พยายามใช้ POSIX และเป็นมาตรฐานที่แน่นอน เปลือกหอยทั้งคู่หลีกเลี่ยงความเข้ากันได้กับสิ่งที่บางคนเชื่อว่าสะดวกสบาย
mikeserv

มันยังทำงานกับบอร์นเชลล์ธรรมดา
แดน

ในทุบตีด้วยset -o posixความพยายามจะประสบความสำเร็จ
แดน

-1

ดูเหมือนง่ายมาก (ดูความคิดเห็น):

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

ในฐานะที่เป็นพิเศษ ... การทดสอบ [-r ไฟล์] ไม่ได้ระบุว่าข้อมูลใด ๆ ที่รอการอ่าน (/ dev / null ผ่านการทดสอบนี้ (ดูความคิดเห็น))

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

จำเป็นต้องใช้ตัวเลขขนาดเล็กจำนวนหนึ่งสำหรับอาร์กิวเมนต์การหมดเวลา (read -t) หรือข้อมูลที่ต้องการการคำนวณบางอย่างอาจพลาด จำเป็นต้องมีการทดสอบการอ่าน ([-r file]) หรือคำสั่ง read จะระเบิดหากไฟล์นั้นไม่สามารถอ่านได้ สิ่งนี้จะไม่อ่านข้อมูลใด ๆ เนื่องจากจำนวนไบต์เป็นศูนย์ (อ่าน -N 0)


ถ้าคุณกำลังจะถือว่าเป็นระบบ Linux คุณก็เช่นกันอาจจะมีดูที่/proc/<pid>/fdinfo/<fd>ซึ่งแสดงรายการทั้งหมดของโหมดเปิดไฟล์ภายใต้flags:- ดูที่นี่ ทำไมส่วนที่ 2 ของคุณ (แม้หลังจากแก้ไขข้อผิดพลาดที่จ้องมอง) read -t .1 -N0 <&4จะไม่บอกถ้ามีข้อมูลที่จะได้อ่านบน FD 4: 4</dev/nullเพียงแค่ลองกับ
mosvy

และแน่นอน[ -r /proc/$$/fd/$FD ]ไม่ได้บอกคุณว่า file descriptor $FDนั้นสามารถอ่านได้หรือไม่ แต่ถ้าไฟล์นั้นเปิดจากนั้นสามารถเปิดได้อีกครั้งพร้อมด้วย file descriptor ตัวอื่นสำหรับการอ่าน:exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy

-1

คำถามนั้นค่อนข้างเก่า - แต่อย่างไรก็ตาม - ทำไมไม่ใช้บิวอิน

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

เอาท์พุท:

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

ดังนั้นเพื่อตอบคำถาม - อยากจะแนะนำ:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

-tไม่ทดสอบว่า descriptor ของไฟล์นั้นถูกต้องหรือไม่ แต่ถ้ามันเชื่อมต่อกับ tty เติมecho yup |สคริปต์ของคุณและจะบอกว่า0 is INVALID FDในความเป็นจริงมันเป็น fd ที่ถูกต้องมาก
mosvy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.