คุณจะใช้ตัวอธิบายไฟล์เพิ่มเติมเมื่อใด


74

ฉันรู้ว่าคุณสามารถสร้างไฟล์ descriptor และเปลี่ยนเส้นทางไปยังมัน เช่น

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

แต่คุณสามารถทำสิ่งเดียวกันโดยไม่มีไฟล์ descriptor:

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

ฉันกำลังมองหาตัวอย่างที่ดีเมื่อคุณจะต้องใช้ตัวอธิบายไฟล์เพิ่มเติม

คำตอบ:


50

คำสั่งส่วนใหญ่จะมีช่องสัญญาณอินพุตเดียว (อินพุตมาตรฐาน, ตัวอธิบายไฟล์ 0) และช่องสัญญาณเอาต์พุตเดียว (เอาต์พุตมาตรฐาน, ตัวอธิบายไฟล์ 1) หรือดำเนินการกับไฟล์หลายไฟล์ที่พวกเขาเปิดด้วยตัวเอง (นอกเหนือจากข้อผิดพลาดมาตรฐาน (fd 2) ซึ่งมักจะกรองข้อมูลไปยังผู้ใช้) บางครั้งมันก็สะดวกที่จะมีคำสั่งที่ทำหน้าที่เป็นตัวกรองจากแหล่งต่าง ๆ หรือไปยังเป้าหมายหลายแห่ง ตัวอย่างเช่นต่อไปนี้เป็นสคริปต์แบบง่าย ๆ ที่แยกเส้นเลขคี่ในไฟล์จากเลขคู่

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

ทีนี้สมมติว่าคุณต้องการใช้ตัวกรองที่แตกต่างกับบรรทัดเลขคี่และบรรทัดที่มีเลขคู่ (แต่ไม่รวมกลับเข้าด้วยกันนั่นจะเป็นปัญหาที่แตกต่างกันและไม่สามารถทำได้โดยทั่วไปจากเชลล์) ในเชลล์คุณสามารถไพพ์เอาต์พุตมาตรฐานของคำสั่งไปยังคำสั่งอื่นเท่านั้น หากต้องการไพพ์ตัวให้คำอธิบายไฟล์อื่นคุณต้องเปลี่ยนเส้นทางไปยัง fd 1 ก่อน

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

กรณีการใช้งานที่ง่ายอีกถูกกรองออกข้อผิดพลาดของคำสั่ง

exec M>&Nเปลี่ยนทิศทาง file descriptor ไปยังอีกอันหนึ่งสำหรับส่วนที่เหลือของสคริปต์ (หรือจนกว่าคำสั่งอื่นจะเปลี่ยน descriptor ไฟล์อีกครั้ง) มีบางส่วนทับซ้อนในการทำงานระหว่างเป็นและexec M>&N รูปแบบมีประสิทธิภาพมากขึ้นในการที่จะไม่ได้มีการซ้อนกัน:somecommand M>&Nexec

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

ตัวอย่างอื่น ๆ ที่อาจเป็นที่สนใจ:

และสำหรับตัวอย่างเพิ่มเติม:

PS นี่เป็นคำถามที่น่าแปลกใจที่มาจากผู้เขียนโพสต์ upvoted ที่สุดในเว็บไซต์ที่ใช้การเปลี่ยนเส้นทางผ่าน fd 3 !


ฉันอยากจะบอกว่า "คำสั่งส่วนใหญ่มีทั้งช่องสัญญาณเอาต์พุตเดี่ยวหรือคู่ - stdout (fd 1) และบ่อยครั้งมาก stderr (fd 2)"
rozcietrzewiacz

นอกจากนี้คุณสามารถอธิบายได้ไหมว่าทำไมคุณถึงใช้while IFS= read -r line;? วิธีที่ฉันเห็นไอเอฟเอไม่มีผลที่นี่เนื่องจากคุณกำหนดค่าให้กับตัวแปรเดียว ( บรรทัด ) ดูคำถามนี้
rozcietrzewiacz

@rozcietrzewiacz ฉันได้กล่าวถึง stderr และดูส่วนแรกของคำตอบของฉันสำหรับสาเหตุที่IFSสร้างความแตกต่างแม้ว่าคุณจะอ่านเป็นตัวแปรเดียว (เพื่อรักษาช่องว่างนำ)
Gilles

คุณไม่สามารถทำเช่นเดียวกันกับsed -ne 'w odd.txt' -e 'n;w even.txt'?
สัญลักษณ์แทน

1
@ Wildcard คุณสามารถทำเช่นเดียวกันกับเครื่องมืออื่น ๆ ได้อย่างแน่นอน แต่เป้าหมายของคำตอบนี้คือเพื่อแสดงให้เห็นถึงการเปลี่ยนเส้นทางในเชลล์
Gilles

13

นี่คือตัวอย่างของการใช้ FDs พิเศษเป็นการควบคุมสคริปต์แชตของ 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"

8

ในบริบทของ pipes ที่มีชื่อ (fifos) การใช้ file descriptor เพิ่มเติมสามารถเปิดใช้งานลักษณะการทำงานของ piping ที่ไม่บล็อก

(
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

ดู: การปิดชื่อไปป์ในสคริปต์ก่อนกำหนดหรือไม่


1
คุณขุดคำถามเก่า ๆ ข้อหนึ่งของฉัน :) ชาดถูกต้องคุณจะเจอกับสภาพการแข่งขัน
n0pe

6

ตัวอธิบายไฟล์พิเศษนั้นดีสำหรับเมื่อคุณต้องการจับ stdout ในตัวแปร แต่ยังคงต้องการที่จะเขียนออกไปที่หน้าจอเช่นในอินเทอร์เฟซผู้ใช้สคริปต์ทุบตี

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 
}

2
ฉันรู้ว่านี่ไม่ใช่คำตอบใหม่ แต่ฉันต้องจ้องไปที่นี้ค่อนข้างนานเพื่อดูว่ามันทำอะไรและคิดว่ามันจะเป็นประโยชน์ถ้ามีคนเพิ่มตัวอย่างของฟังก์ชั่นการใช้นี้หนึ่ง echos และจับเอาท์พุททั้งหมดของ คำสั่ง - df ในกรณีนี้ dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe

3

นี่เป็นอีกสถานการณ์หนึ่งเมื่อใช้อธิบายไฟล์เพิ่มเติมที่เหมาะสม (ใน Bash):

ความปลอดภัยรหัสผ่านเชลล์สคริปต์ของพารามิเตอร์บรรทัดคำสั่ง

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

1

ตัวอย่าง: การใช้ flock เพื่อบังคับให้สคริปต์รันพร้อมกับการล็อคไฟล์

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

#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>&-

ใช้ฝูงทำงานโดยการกำหนดล็อคและปลดล็อค

นอกจากนี้คุณยังสามารถรวมตรรกะการล็อค / ปลดล็อกนี้ไว้ในฟังก์ชั่นที่ใช้ซ้ำได้ trapบิวด์เชลล์ต่อไปนี้จะปลดล็อกไฟล์โดยอัตโนมัติเมื่อสคริปต์ออก (ข้อผิดพลาดหรือสำเร็จ) trapช่วยทำความสะอาดล็อคไฟล์ของคุณ เส้นทาง/tmp/file.lockควรเป็นเส้นทางที่มีการเข้ารหัสอย่างหนักเพื่อให้สคริปต์หลายรายการสามารถลองล็อคได้

# 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>&-
}

unlockตรรกะข้างต้นคือการลบไฟล์ก่อนที่จะปลดล็อคตัวล็อค วิธีนี้จะทำการล้างไฟล์ล็อค เนื่องจากไฟล์ถูกลบอินสแตนซ์อื่นของโปรแกรมนี้สามารถขอรับการล็อกไฟล์ได้

การใช้ฟังก์ชั่นล็อคและปลดล็อคในสคริปต์

คุณสามารถใช้มันในสคริปต์ของคุณเช่นตัวอย่างต่อไปนี้

#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

หากคุณต้องการให้รหัสของคุณรอจนกว่าจะสามารถล็อคคุณสามารถปรับสคริปต์เช่น:

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

0

ตัวอย่างที่เป็นรูปธรรมฉันเพิ่งเขียนสคริปต์ที่ต้องการข้อมูลเวลาจากคำสั่งย่อย การใช้ไฟล์อธิบายเพิ่มเติมช่วยให้ฉันสามารถจับtimestderr ของคำสั่งโดยไม่ขัดจังหวะ stdout หรือ stderr ของคำสั่งย่อย

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

สิ่งนี้จะเป็นจุดlsstderr ของ fd 3, ชี้ fd 3 ไปยัง stderr ของสคริปต์และ stderr ของชี้timeไปที่ไฟล์ เมื่อสคริปต์รัน stdout และ stderr จะเหมือนกับคำสั่งย่อยซึ่งสามารถเปลี่ยนทิศทางได้ตามปกติ เฉพาะtimeเอาต์พุตของจะถูกเปลี่ยนเส้นทางไปยังไฟล์

$ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.