Khi nào bạn sẽ sử dụng một mô tả tập tin bổ sung?


74

Tôi biết bạn có thể tạo một mô tả tập tin và chuyển hướng đầu ra cho nó. ví dụ

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

Nhưng bạn có thể làm điều tương tự mà không cần mô tả tệp:

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

Tôi đang tìm một ví dụ tốt về thời điểm bạn sẽ phải sử dụng một bộ mô tả tệp bổ sung.

Câu trả lời:


50

Hầu hết các lệnh đều có một kênh đầu vào (đầu vào tiêu chuẩn, mô tả tệp 0) và một kênh đầu ra duy nhất (đầu ra tiêu chuẩn, mô tả tệp 1) hoặc hoạt động trên một số tệp mà chúng tự mở (vì vậy bạn chuyển cho chúng một tên tệp). (Đó là ngoài lỗi tiêu chuẩn (fd 2), thường lọc tất cả các cách cho người dùng.) Tuy nhiên, đôi khi có một lệnh hoạt động như một bộ lọc từ một số nguồn hoặc đến một số mục tiêu. Ví dụ: đây là một tập lệnh đơn giản tách các dòng được đánh số lẻ trong một tệp khỏi các tập lệnh được đánh số chẵn

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

Bây giờ, giả sử bạn muốn áp dụng một bộ lọc khác cho các dòng số lẻ và cho các dòng có số chẵn (nhưng không đặt chúng lại với nhau, đó sẽ là một vấn đề khác, không khả thi so với trình bao nói chung). Trong trình bao, bạn chỉ có thể chuyển đầu ra tiêu chuẩn của lệnh sang lệnh khác; để chuyển một mô tả tập tin khác, trước tiên bạn cần chuyển hướng nó đến fd 1.

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

Một trường hợp sử dụng đơn giản hơn là lọc đầu ra lỗi của lệnh .

exec M>&Nchuyển hướng một bộ mô tả tập tin sang một bộ mô tả khác cho phần còn lại của tập lệnh (hoặc cho đến khi một lệnh khác như vậy thay đổi bộ mô tả tập tin một lần nữa). Có một số sự chồng chéo về chức năng giữa exec M>&Nsomecommand M>&N. Biểu execmẫu mạnh hơn ở chỗ không phải lồng nhau:

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

Các ví dụ khác có thể được quan tâm:

Và cho nhiều ví dụ hơn:

PS Đây là một câu hỏi đáng ngạc nhiên đến từ tác giả của bài đăng được đánh giá cao nhất trên trang web sử dụng chuyển hướng qua fd 3 !


Tôi muốn nói rằng "hầu hết các lệnh đều có kênh đầu ra đơn hoặc đôi - stdout (fd 1) và rất thường là stderr (fd 2)".
rozcietrzewiacz

Ngoài ra, bạn có thể giải thích tại sao bạn sử dụng while IFS= read -r line;? Theo cách tôi thấy, IFS không có hiệu lực ở đây vì bạn chỉ gán giá trị cho một biến ( dòng ). Xem câu hỏi này.
rozcietrzewiacz

@rozcietrzewiacz Tôi đã đề cập đến stderr và xem phần đầu tiên trong câu trả lời của tôi để biết tại sao lại IFStạo ra sự khác biệt ngay cả khi bạn đang đọc một biến duy nhất (đó là giữ lại khoảng trắng hàng đầu).
Gilles

Bạn không thể làm như vậy với sed -ne 'w odd.txt' -e 'n;w even.txt'?
tự đại diện

1
@Wildcard Bạn có thể làm tương tự với các công cụ khác, chắc chắn. Nhưng mục tiêu của câu trả lời này là để minh họa các chuyển hướng trong vỏ.
Gilles

13

Đây là một ví dụ về việc sử dụng các FD bổ sung làm kiểm soát độ chói của tập lệnh 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

Trong ngữ cảnh của các đường ống được đặt tên (fifos), việc sử dụng một bộ mô tả tệp bổ sung có thể cho phép hành vi đường ống không chặn.

(
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

Xem: Đặt tên ống đóng sớm trong kịch bản?


1
bạn đã đào lên một trong những câu hỏi cũ của tôi :) đã đúng, bạn sẽ gặp phải tình trạng đua xe.
n0pe

6

Một bộ mô tả tệp bổ sung phù hợp khi bạn muốn bắt đầu xuất chuẩn trong một biến nhưng vẫn muốn ghi ra màn hình, ví dụ như trong giao diện người dùng bash script

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
Tôi biết đây không phải là một câu trả lời mới, nhưng tôi phải nhìn chằm chằm vào điều này khá lâu để xem những gì nó làm và nghĩ rằng nó sẽ hữu ích nếu ai đó thêm một ví dụ về chức năng này được sử dụng. Đây là một tiếng vang và nắm bắt toàn bộ đầu ra của một lệnh - df, trong trường hợp này. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe


1

Ví dụ: sử dụng bầy để buộc các tập lệnh chạy ser seri với khóa tập tin

Một ví dụ là sử dụng khóa tệp để buộc các tập lệnh chạy toàn hệ thống. Điều này hữu ích nếu bạn không muốn hai tập lệnh cùng loại hoạt động trên cùng một tệp. Nếu không, hai tập lệnh sẽ can thiệp lẫn nhau và có thể dữ liệu bị hỏng.

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

Sử dụng đàn theo chức năng bằng cách xác định khóa và mở khóa

Bạn cũng có thể gói logic khóa / mở khóa này vào các chức năng có thể sử dụng lại. Nội dung trapshell sau đây sẽ tự động giải phóng khóa tệp khi tập lệnh thoát (lỗi hoặc thành công). trapgiúp dọn dẹp ổ khóa tập tin của bạn. Đường dẫn /tmp/file.lockphải là một đường dẫn được mã hóa cứng để nhiều tập lệnh có thể cố gắng khóa trên nó.

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

Các unlocklogic ở trên là để xóa các tập tin trước khi khóa được phát hành. Bằng cách này, nó làm sạch các tập tin khóa. Vì tệp đã bị xóa, một phiên bản khác của chương trình này có thể có được khóa tệp.

Sử dụng các chức năng khóa và mở khóa trong các tập lệnh

Bạn có thể sử dụng nó trong các kịch bản của bạn như ví dụ sau đây.

#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

Nếu bạn muốn mã của mình đợi cho đến khi có thể khóa, bạn có thể điều chỉnh tập lệnh như:

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

Để làm ví dụ cụ thể, tôi chỉ viết một kịch bản cần thông tin về thời gian từ một tiểu ban. Sử dụng một bộ mô tả tệp bổ sung cho phép tôi ghi lại timethiết bị xuất chuẩn của lệnh mà không làm gián đoạn thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn của tiểu ban.

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

Những gì nó làm là lsstderr của điểm đến fd 3, điểm fd 3 đến stderr của tập lệnh và stderr của điểm timevào một tập tin. Khi tập lệnh được chạy, thiết bị xuất chuẩn và thiết bị xuất chuẩn của nó giống như của các tiểu ban, có thể được chuyển hướng như bình thường. Chỉ timeđầu ra của được chuyển hướng đến tập tin.

$ 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.