Quá trình thay thế và đường ống


86

Tôi đã tự hỏi làm thế nào để hiểu những điều sau đây:

Đặt ống thông tin của lệnh vào stdin của người khác là một kỹ thuật mạnh mẽ. Nhưng, điều gì sẽ xảy ra nếu bạn cần bỏ qua các thiết bị xuất chuẩn của nhiều lệnh? Đây là nơi thay thế quá trình đến.

Nói cách khác, quá trình thay thế có thể làm bất cứ điều gì ống có thể làm?

Những gì có thể xử lý thay thế làm, nhưng ống không thể?

Câu trả lời:


134

Một cách hay để tìm ra sự khác biệt giữa chúng là thử nghiệm một chút trên dòng lệnh. Mặc dù có sự tương đồng về mặt hình ảnh trong việc sử dụng <nhân vật, nó làm một cái gì đó rất khác so với chuyển hướng hoặc đường ống.

Hãy sử dụng datelệnh để kiểm tra.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Đây là một ví dụ vô nghĩa nhưng nó cho thấy đã catchấp nhận đầu ra của dateSTDIN và nhổ nó ra. Các kết quả tương tự có thể đạt được bằng cách thay thế quá trình:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Tuy nhiên những gì chỉ xảy ra đằng sau hậu trường là khác nhau. Thay vì được cung cấp một luồng STDIN, catthực tế đã được thông qua tên của một tệp mà nó cần để mở và đọc. Bạn có thể thấy bước này bằng cách sử dụng echothay vì cat.

$ echo <(date)
/proc/self/fd/11

Khi mèo nhận được tên tệp, nó đọc nội dung của tệp cho chúng tôi. Mặt khác, echo chỉ cho chúng ta thấy tên tệp mà nó đã được thông qua. Sự khác biệt này trở nên rõ ràng hơn nếu bạn thêm nhiều sự thay thế:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Có thể kết hợp thay thế quá trình (tạo tệp) và chuyển hướng đầu vào (kết nối tệp với STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Nó trông khá giống nhau nhưng lần này con mèo đã được truyền luồng STDIN thay vì tên tệp. Bạn có thể thấy điều này bằng cách thử nó với echo:

$ echo < <(date)
<blank>

Vì echo không đọc STDIN và không có đối số nào được thông qua, chúng tôi không nhận được gì.

Đường ống và đầu vào chuyển hướng nội dung xô vào luồng STDIN. Quá trình thay thế chạy các lệnh, lưu đầu ra của chúng vào một tệp tạm thời đặc biệt và sau đó chuyển tên tệp đó thay cho lệnh. Bất cứ lệnh nào bạn đang sử dụng đều coi nó như một tên tệp. Lưu ý rằng tệp được tạo không phải là tệp thông thường mà là đường ống có tên sẽ tự động bị xóa sau khi không còn cần thiết nữa.


Nếu tôi hiểu chính xác, tldp.org/LDP/abs/html/ process- sub.html #FTN.AEN18244 nói rằng quá trình thay thế tạo ra các tệp tạm thời, không được đặt tên là ống. Theo như tôi biết có tên không tạo tập tin tạm thời. Ghi vào đường ống không bao giờ liên quan đến việc ghi vào đĩa: stackoverflow.com/a/6977599/788700
Adobe

Tôi biết câu trả lời này là hợp pháp 'vì nó sử dụng từ grok : D
aqn

2
@Adobe bạn có thể xác nhận xem sản phẩm thay thế quá trình tệp tạm thời có phải là một ống có tên với : [[ -p <(date) ]] && echo true. Điều này tạo ra truekhi tôi chạy nó với bash 4.4 hoặc 3.2.
De Novo

24

Tôi nên giả sử bạn đang nói về bashhoặc một số shell tiên tiến khác, bởi vì vỏ posix không có sự thay thế quá trình .

bash báo cáo trang hướng dẫn:

Thay thế quy trình Thay thế
quy trình được hỗ trợ trên các hệ thống hỗ trợ các đường ống có tên (FIFO) hoặc phương thức / dev / fd để đặt tên các tệp đang mở. Nó có dạng <(danh sách) hoặc> (danh sách). Danh sách quy trình được chạy với đầu vào hoặc đầu ra của nó được kết nối với một tệp FIFO hoặc một số tệp trong / dev / fd. Tên của tệp này được truyền dưới dạng đối số cho lệnh hiện tại là kết quả của việc mở rộng. Nếu biểu mẫu> (danh sách) được sử dụng, ghi vào tệp sẽ cung cấp đầu vào cho danh sách. Nếu biểu mẫu <(danh sách) được sử dụng, tệp được truyền dưới dạng đối số sẽ được đọc để lấy đầu ra của danh sách.

Khi có sẵn, quá trình thay thế được thực hiện đồng thời với mở rộng tham số và biến, thay thế lệnh và mở rộng số học.

Nói cách khác, và từ quan điểm thực tế, bạn có thể sử dụng một biểu thức như sau

<(commands)

làm tên tệp cho các lệnh khác yêu cầu tệp làm tham số. Hoặc bạn có thể sử dụng chuyển hướng cho một tệp như vậy:

while read line; do something; done < <(commands)

Quay trở lại câu hỏi của bạn, dường như với tôi rằng quá trình thay thế và đường ống không có nhiều điểm chung.

Nếu bạn muốn sắp xếp theo thứ tự đầu ra của nhiều lệnh, bạn có thể sử dụng một trong các hình thức sau:

(command1; command2) | command3
{ command1; command2; } | command3

nhưng bạn cũng có thể sử dụng chuyển hướng để thay thế quá trình

command3 < <(command1; command2)

cuối cùng, nếu command3chấp nhận một tham số tệp (thay thế stdin)

command3 <(command1; command2)

vì vậy <() và <() tạo ra hiệu ứng tương tự, phải không?
solfish

@solfish: không exacllty: Firse có thể được sử dụng ở bất cứ nơi nào có tên tệp, thứ hai là chuyển hướng đầu vào cho tên tệp đó
enzotib

23

Dưới đây là ba điều bạn có thể làm với quá trình thay thế là không thể.

Nhiều quá trình đầu vào

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Đơn giản là không có cách nào để làm điều này với đường ống.

Bảo quản STDIN

Nói rằng bạn có những điều sau đây:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

Và bạn muốn chạy nó trực tiếp. Sau đây thất bại thảm hại. Bash đã sử dụng STDIN để đọc tập lệnh, vì vậy không thể nhập dữ liệu khác.

curl -o - http://example.com/script.sh | bash 

Nhưng cách này hoạt động hoàn hảo.

bash <(curl -o - http://example.com/script.sh)

Thay thế quá trình ra nước ngoài

Cũng lưu ý rằng quá trình thay thế hoạt động theo cách khác quá. Vì vậy, bạn có thể làm một cái gì đó như thế này:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

Đó là một chút của một ví dụ phức tạp, nhưng nó sẽ gửi stdout để /dev/null, trong khi đường ống stderr đến một kịch bản sed để trích xuất các tên của các tập tin mà một "Permission denied" lỗi được hiển thị, và sau đó gửi những kết quả vào một tập tin.

Lưu ý rằng lệnh đầu tiên và chuyển hướng xuất chuẩn nằm trong ngoặc đơn ( subshell ) để chỉ kết quả của lệnh THAT được gửi đến /dev/nullvà nó không gây rối với phần còn lại của dòng.


Điều đáng chú ý là trong diffví dụ bạn có thể muốn quan tâm đến trường hợp cdcó thể thất bại : diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk

"trong khi đường ống stderr": không phải là điểm mà đây không phải là đường ống, mà là thông qua một tập tin fifo?
Gauthier

@Gauthier không; lệnh được thay thế không phải bằng fifo mà bằng tham chiếu đến bộ mô tả tệp. Vì vậy, "echo <(echo)" sẽ mang lại một cái gì đó như "/ dev / fd / 63", một thiết bị ký tự đặc biệt đọc hoặc ghi từ số FD 63.
tylerl

10

Nếu một lệnh lấy danh sách các tệp làm đối số và xử lý các tệp đó làm đầu vào (hoặc đầu ra, nhưng không phổ biến), thì mỗi tệp đó có thể là một ống có tên hoặc / dev / fd tệp giả được cung cấp trong suốt theo quy trình:

$ sort -m <(command1) <(command2) <(command3)

Điều này sẽ "đường ống" đầu ra của ba lệnh để sắp xếp, vì sắp xếp có thể lấy danh sách các tệp đầu vào trên dòng lệnh.


1
IIRC cú pháp <(lệnh) là một tính năng chỉ bash.
Philomath

@Philomath: Nó cũng ở trong ZSH.
Caleb

Chà, ZSH có mọi thứ ... (hoặc ít nhất là cố gắng).
Philomath

@Philomath: Quá trình thay thế được thực hiện trong các shell khác như thế nào?
camh

4
@Philomath <(), giống như nhiều tính năng shell nâng cao, ban đầu là một tính năng ksh và được bash và zsh chấp nhận. psubđặc biệt là một tính năng cá, không có gì để làm với POSIX.
Gilles

3

Cần lưu ý rằng quá trình thay thế không bị giới hạn ở biểu mẫu <(command), sử dụng đầu ra commanddưới dạng tệp. Nó có thể ở dạng >(command)cung cấp một tệp như là đầu vào command. Điều này cũng được đề cập trong trích dẫn của hướng dẫn bash trong câu trả lời của @ enzotib.

Đối với date | catví dụ trên, một lệnh sử dụng thay thế quy trình của biểu mẫu >(command)để đạt được hiệu quả tương tự sẽ là,

date > >(cat)

Lưu ý rằng >trước đây >(cat)là cần thiết. Điều này một lần nữa có thể được minh họa rõ ràng echonhư trong câu trả lời của @ Caleb.

$ echo >(cat)
/dev/fd/63

Vì vậy, nếu không có thêm >, date >(cat)sẽ giống như date /dev/fd/63sẽ in một thông điệp tới thiết bị lỗi chuẩn.

Giả sử bạn có một chương trình chỉ lấy tên tệp làm tham số và không xử lý stdinhoặc stdout. Tôi sẽ sử dụng kịch bản đơn giản hóa psub.shđể minh họa điều này. Nội dung của psub.sh

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Về cơ bản, nó kiểm tra cả hai đối số của nó là các tệp (không nhất thiết là các tệp thông thường) và nếu đây là trường hợp, hãy viết trường đầu tiên của mỗi dòng "$1"để "$2"sử dụng awk. Sau đó, một lệnh kết hợp tất cả những gì được đề cập cho đến nay là,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Cái này sẽ in

a
b
c

và tương đương với

printf "a a\nc c\nb b" | awk '{print $1}' | sort

nhưng những điều sau đây sẽ không hoạt động và chúng ta phải sử dụng quy trình thay thế ở đây,

printf "a a\nc c\nb b" | ./psub.sh | sort

hoặc hình thức tương đương của nó

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Nếu ./psub.shcũng đọc stdinbên cạnh những gì được đề cập ở trên, thì một dạng tương đương như vậy không tồn tại và trong trường hợp đó, chúng ta không thể sử dụng gì thay thế cho quá trình thay thế (tất nhiên bạn cũng có thể sử dụng tệp ống hoặc temp có tên, nhưng đó là một dạng khác câu chuyện).

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.