Bash: tạo fifo ẩn danh


38

Chúng ta đều biết mkfifovà đường ống dẫn. Cái đầu tiên tạo ra một ống có tên , do đó người ta phải chọn một tên, rất có thể với mktempvà sau đó nhớ bỏ liên kết. Cái kia tạo ra một đường ống ẩn danh, không gặp rắc rối với tên và loại bỏ, nhưng các đầu của ống được gắn với các lệnh trong đường ống, thật không tiện khi bằng cách nào đó nắm được các mô tả tệp và sử dụng chúng trong phần còn lại của kịch bản. Trong một chương trình biên dịch, tôi sẽ chỉ làm ret=pipe(filedes); Trong Bash có exec 5<>filemột người như vậy sẽ mong đợi một cái gì đó giống như "exec 5<> -"hoặc "pipe <5 >6"- có một cái gì đó giống như vậy trong Bash?

Câu trả lời:


42

Bạn có thể hủy liên kết một đường ống được đặt tên ngay sau khi gắn nó vào quy trình hiện tại, điều này thực tế dẫn đến một đường ống ẩn danh:

# create a temporary named pipe
PIPE=$(mktemp -u)
mkfifo $PIPE
# attach it to file descriptor 3
exec 3<>$PIPE
# unlink the named pipe
rm $PIPE
...
# anything we write to fd 3 can be read back from it
echo 'Hello world!' >&3
head -n1 <&3
...
# close the file descriptor when we are finished (optional)
exec 3>&-

Nếu bạn thực sự muốn tránh các đường ống được đặt tên (ví dụ: hệ thống tệp ở chế độ chỉ đọc), ý tưởng "hiểu rõ về mô tả tệp" của bạn cũng hoạt động. Lưu ý rằng đây là dành riêng cho Linux do sử dụng Procfs.

# start a background pipeline with two processes running forever
tail -f /dev/null | tail -f /dev/null &
# save the process ids
PID2=$!
PID1=$(jobs -p %+)
# hijack the pipe's file descriptors using procfs
exec 3>/proc/$PID1/fd/1 4</proc/$PID2/fd/0
# kill the background processes we no longer need
# (using disown suppresses the 'Terminated' message)
disown $PID2
kill $PID1 $PID2
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptors when we are finished (optional)
exec 3>&- 4<&-

Bạn có thể kết hợp điều này với việc tự động tìm các mô tả tệp không sử dụng: stackoverflow.com/questions/8297415/ trên
CMCDragonkai

23

Mặc dù không có loại vỏ nào tôi biết có thể làm ống mà không cần gạt, một số loại có vỏ tốt hơn ống vỏ cơ bản.

Trong bash, ksh và zsh, giả sử hệ thống của bạn hỗ trợ /dev/fd(hầu hết hiện nay), bạn có thể buộc đầu vào hoặc đầu ra của lệnh thành tên tệp: <(command)mở rộng thành tên tệp chỉ định một đường ống được kết nối với đầu ra từ command>(command)mở rộng đến một tên tệp chỉ định một đường ống được kết nối với đầu vào của command. Tính năng này được gọi là quá trình thay thế . Mục đích chính của nó là đưa nhiều hơn một lệnh vào hoặc ra một lệnh khác, ví dụ:

diff <(transform <file1) <(transform <file2)
tee >(transform1 >out1) >(transform2 >out2)

Điều này cũng hữu ích để chống lại một số thiếu sót của ống vỏ cơ bản. Ví dụ, command2 < <(command1)tương đương với command1 | command2, ngoại trừ trạng thái của nó là command2. Một trường hợp sử dụng khác exec > >(postprocessing), tương đương với, nhưng dễ đọc hơn, đặt toàn bộ phần còn lại của tập lệnh vào trong { ... } | postprocessing.


Tôi đã thử điều này với diff và nó hoạt động nhưng với kdiff3 hoặc với emacs, nó không hoạt động. Tôi đoán là tệp tạm thời / dev / fd đang bị xóa trước khi kdiff3 được đọc nó. Hoặc có lẽ kdiff3 đang cố đọc tệp hai lần và ống chỉ gửi nó một lần?
Mắt

@Eyal Với quy trình bảo trì quy trình, tên tệp là một tham chiếu ma thuật của Hồi giáo đến một đường ống (hoặc một tệp tạm thời trên các biến thể Unix không hỗ trợ các biến thể ma thuật này). Làm thế nào phép thuật được thực hiện phụ thuộc vào hệ điều hành. Linux triển khai chúng dưới dạng các liên kết tượng trưng của Ma-rốc mà mục tiêu không phải là tên tệp hợp lệ (nó giống như thế pipe:[123456]). Emacs thấy rằng mục tiêu của symlink không phải là tên tệp hiện có và khiến nó nhầm lẫn đến mức nó không đọc được tệp (dù sao cũng có thể có tùy chọn để đọc nó, mặc dù Emacs không thích mở một ống như một tập tin nào).
Gilles 'SO- ngừng trở nên xấu xa'

10

Bash 4 có đồng xử lý .

Một bộ đồng xử lý được thực thi không đồng bộ trong một lớp con, như thể lệnh đã được chấm dứt với toán tử điều khiển '&', với một đường ống hai chiều được thiết lập giữa lớp vỏ thực thi và bộ đồng xử lý.

Định dạng cho một bộ đồng xử lý là:

coproc [NAME] command [redirections] 

3

Kể từ tháng 10 năm 2012, chức năng này dường như vẫn không tồn tại ở Bash, nhưng coproc có thể được sử dụng nếu tất cả những gì bạn cần các ống không tên / ẩn danh là để nói chuyện với một quy trình con. Vấn đề với coproc tại thời điểm này là dường như chỉ có một hỗ trợ tại một thời điểm. Tôi không thể hiểu tại sao coproc có giới hạn này. Chúng đáng lẽ phải là một sự tăng cường của mã nền tác vụ hiện có (& &), nhưng đó là một câu hỏi cho các tác giả của bash.


Không chỉ có một coprocess được hỗ trợ. Bạn có thể đặt tên cho chúng, miễn là bạn không cung cấp một lệnh đơn giản. Thay vào đó coproc THING { dothing; }hãy cung cấp cho nó một danh sách lệnh: Bây giờ các FD của bạn đang ở trong ${THING[*]}và bạn có thể chạy coproc OTHERTHING { dothing; }và gửi và nhận mọi thứ đến và đi từ cả hai.
clacke

2
@clacke man bash, dưới tiêu đề BUGS, họ nói điều này: Có thể chỉ có một bộ đồng xử lý hoạt động tại một thời điểm . Và bạn nhận được một cảnh báo nếu bạn bắt đầu một coproc thứ hai. Nó xuất hiện để làm việc, nhưng tôi không biết những gì phát nổ trong nền.
Radu C

Ok, vì vậy nó hiện chỉ hoạt động bởi may mắn, không phải vì nó là cố ý. Cảnh báo công bằng, cảm ơn. :-)
clacke

2

Mặc dù câu trả lời của @ DavidAnderson bao gồm tất cả các cơ sở và cung cấp một số biện pháp bảo vệ tốt đẹp, nhưng điều quan trọng nhất mà nó tiết lộ là việc đặt tay lên một đường ống ẩn danh cũng dễ <(:)như bạn vẫn ở trên Linux.

Vì vậy, câu trả lời ngắn nhất và đơn giản nhất cho câu hỏi của bạn là:

exec 5<> <(:)

Trên macOS, nó sẽ không hoạt động, sau đó bạn sẽ cần tạo một thư mục tạm thời để chứa fifo được đặt tên cho đến khi bạn chuyển hướng đến nó. Tôi không biết về các BSD khác.


Bạn nhận ra câu trả lời của bạn chỉ hoạt động vì một lỗi trong linux. Lỗi này không tồn tại trong macOS, do đó đòi hỏi giải pháp phức tạp hơn. Phiên bản cuối cùng tôi đã đăng sẽ hoạt động trong linux ngay cả khi lỗi trong linux đã được sửa.
David Anderson

@DavidAnderson Âm thanh như bạn có kiến ​​thức sâu hơn về điều này hơn tôi. Tại sao hành vi Linux là một lỗi?
clacke

1
Nếu execđược thông qua và fifo ẩn danh chỉ được mở để đọc, thì execkhông nên cho phép fifo ẩn danh này được mở để đọc và viết bằng cách sử dụng một bộ mô tả tệp tùy chỉnh. Bạn sẽ nhận được một -bash: /dev/fd/5: Permission deniedtin nhắn, đó là vấn đề của macOS. Tôi tin rằng lỗi là Ubuntu không tạo ra cùng một thông điệp. Tôi sẽ sẵn sàng thay đổi suy nghĩ của mình nếu ai đó có thể đưa ra tài liệu nói rằng exec 5<> <(:)được cho phép giải thích.
David Anderson

@DavidAnderson Wow, thật hấp dẫn. Tôi giả sử bash đang làm một cái gì đó bên trong, nhưng hóa ra Linux của nó chỉ cho phép thực hiện open(..., O_RDWR)trên một đầu ống đơn hướng được cung cấp bởi sự thay thế và biến nó thành một ống hai chiều trong một FD. Bạn có thể đúng rằng người ta không nên dựa vào điều này. :-D Đầu ra từ việc sử dụng piperw của execline để tạo đường ống, sau đó tái sử dụng nó bằng bash <>: libranet.de/display/0b6b25a8-195c-84af-6ac7-ee6696661765
clacke

Không phải là vấn đề, nhưng nếu bạn muốn xem trong Ubuntu những gì được chuyển đến exec 5<>, thì hãy nhập fun() { ls -l $1; ls -lH $1; }; fun <(:).
David Anderson

1

Các chức năng sau đây đã được thử nghiệm bằng cách sử dụng GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu). Hệ điều hành là Ubuntu 18. Hàm này có một tham số duy nhất là bộ mô tả tệp mong muốn cho FIFO ẩn danh.

MakeFIFO() {
    local "MakeFIFO_upper=$(ulimit -n)" 
    if [[ $# -ne 1 || ${#1} -gt ${#MakeFIFO_upper} || -n ${1%%[0-9]*} || 10#$1 -le 2
        || 10#$1 -ge MakeFIFO_upper ]] || eval ! exec "$1<> " <(:) 2>"/dev/null"; then
        echo "$FUNCNAME: $1: Could not create FIFO" >&2
        return "1"
    fi
}

Các chức năng sau đây đã được thử nghiệm bằng cách sử dụng GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17). Hệ điều hành là macOS High Sierra. Hàm này bắt đầu bằng cách tạo một FIFO có tên trong một thư mục tạm thời chỉ được biết đến với quá trình tạo ra nó . Tiếp theo, bộ mô tả tập tin được chuyển hướng đến FIFO. Cuối cùng, FIFO được hủy liên kết từ tên tệp bằng cách xóa thư mục tạm thời. Điều này làm cho các ẩn danh FIFO.

MakeFIFO() {
    MakeFIFO.SetStatus() {
        return "${1:-$?}"
    }
    MakeFIFO.CleanUp() {
        local "MakeFIFO_status=$?"
        rm -rf "${MakeFIFO_directory:-}"    
        unset "MakeFIFO_directory"
        MakeFIFO.SetStatus "$MakeFIFO_status" && true
        eval eval "${MakeFIFO_handler:-:}'; true'" 
    }
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file=" 
    MakeFIFO_handler="$(trap -p EXIT)"
    MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
    MakeFIFO_handler="${MakeFIFO_handler% *}"
    trap -- 'MakeFIFO.CleanUp' EXIT
    until "$MakeFIFO_success"; do
        [[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
        && 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
        MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
        MakeFIFO_file="$MakeFIFO_directory/pipe"
        mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
        ! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
        MakeFIFO_success="true"
    done
    rm -rf "${MakeFIFO_directory:-}"
    unset  "MakeFIFO_directory"
    eval trap -- "$MakeFIFO_handler" EXIT
    unset  "MakeFIFO_handler"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

Các chức năng trên có thể được kết hợp thành một chức năng duy nhất sẽ hoạt động trên cả hai hệ điều hành. Dưới đây là một ví dụ về chức năng như vậy. Ở đây, một nỗ lực được thực hiện để tạo ra một FIFO thực sự ẩn danh. Nếu không thành công, thì một FIFO có tên được tạo và chuyển đổi thành một FIFO ẩn danh.

MakeFIFO() {
    MakeFIFO.SetStatus() {
        return "${1:-$?}"
    }
    MakeFIFO.CleanUp() {
        local "MakeFIFO_status=$?"
        rm -rf "${MakeFIFO_directory:-}"    
        unset "MakeFIFO_directory"
        MakeFIFO.SetStatus "$MakeFIFO_status" && true
        eval eval "${MakeFIFO_handler:-:}'; true'" 
    }
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file=" 
    MakeFIFO_handler="$(trap -p EXIT)"
    MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
    MakeFIFO_handler="${MakeFIFO_handler% *}"
    trap -- 'MakeFIFO.CleanUp' EXIT
    until "$MakeFIFO_success"; do
        [[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
        && 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
        if eval ! exec "$1<> " <(:) 2>"/dev/null"; then
            MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
            MakeFIFO_file="$MakeFIFO_directory/pipe"
            mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
            ! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
        fi
        MakeFIFO_success="true"
    done
    rm -rf "${MakeFIFO_directory:-}"
    unset  "MakeFIFO_directory"
    eval trap -- "$MakeFIFO_handler" EXIT
    unset  "MakeFIFO_handler"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

Dưới đây là một ví dụ về việc tạo một FIFO ẩn danh, sau đó viết một số văn bản cho cùng một FIFO.

fd="6"
MakeFIFO "$fd"
echo "Now is the" >&"$fd"
echo "time for all" >&"$fd"
echo "good men" >&"$fd"

Dưới đây là một ví dụ về việc đọc toàn bộ nội dung của FIFO ẩn danh.

echo "EOF" >&"$fd"
while read -u "$fd" message; do
    [[ $message != *EOF ]] || break
    echo "$message"
done

Điều này tạo ra đầu ra sau đây.

Now is the
time for all
good men

Lệnh dưới đây sẽ đóng cửa FIFO ẩn danh.

eval exec "$fd>&-"

Tài liệu tham khảo:
Tạo một đường dẫn ẩn danh để sử dụng sau này
Tập tin trong các thư mục có thể ghi công khai là bảo mật Shell nguy hiểm


0

Sử dụng câu trả lời tuyệt vời và sáng sủa từ htamas, tôi đã sửa đổi nó một chút để sử dụng nó trong một lớp lót, đây là:

# create a temporary named pipe
PIPE=(`(exec 0</dev/null 1</dev/null; (( read -d \  e < /proc/self/stat ; echo $e >&2 ; exec tail -f /dev/null 2> /dev/null ) | ( read -d \  e < /proc/self/stat ; echo $e  >&2 ; exec tail -f /dev/null 2> /dev/null )) &) 2>&1 | for ((i=0; i<2; i++)); do read e; printf "$e "; done`)
# attach it to file descriptors 3 and 4
exec 3>/proc/${PIPE[0]}/fd/1 4</proc/${PIPE[1]}/fd/0
...
# kill the temporary pids
kill ${PIPE[@]}
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptor when we are finished (optional)
exec 3>&- 4<&-

7
Tôi không thể nhận thấy rằng một lớp lót của bạn có nhiều hơn một dòng.
Dmitry Grigoryev
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.