Sự khác biệt giữa Chuyển hướng trực tiếp trên đường cao tốc và trên đường ống là gì?


204

Câu hỏi này nghe có vẻ hơi ngu ngốc, nhưng tôi thực sự không thể thấy sự khác biệt giữa chuyển hướng và đường ống.

Chuyển hướng được sử dụng để chuyển hướng thiết bị xuất chuẩn / stdin / stderr, vd ls > log.txt.

Các đường ống được sử dụng để đưa ra đầu ra của một lệnh làm đầu vào cho một lệnh khác, vd ls | grep file.txt.

Nhưng tại sao có hai nhà khai thác cho cùng một điều?

Tại sao không chỉ viết ls > grepđể chuyển đầu ra qua, đây không phải chỉ là một loại chuyển hướng? Tôi đang thiếu gì?

Câu trả lời:


223

Ống được sử dụng để chuyển đầu ra cho một chương trình hoặc tiện ích khác .

Chuyển hướng được sử dụng để chuyển đầu ra cho một tệp hoặc luồng .

Ví dụ: thing1 > thing2vsthing1 | thing2

thing1 > thing2

  1. Shell của bạn sẽ chạy chương trình có tên thing1
  2. Tất cả mọi thứ thing1đầu ra sẽ được đặt trong một tập tin gọi là thing2. (Lưu ý - nếu thing2tồn tại, nó sẽ bị ghi đè)

Nếu bạn muốn chuyển đầu ra từ chương trình thing1sang một chương trình được gọi thing2, bạn có thể làm như sau:

thing1 > temp_file && thing2 < temp_file

mà sẽ

  1. chạy chương trình có tên thing1
  2. lưu kết quả vào một tập tin có tên temp_file
  3. chạy chương trình có tên thing2, giả vờ rằng người ở bàn phím đã gõ nội dung temp_filelà đầu vào.

Tuy nhiên, điều đó thật rắc rối, vì vậy họ đã tạo ra các đường ống như một cách đơn giản hơn để làm điều đó. thing1 | thing2làm điều tương tự nhưthing1 > temp_file && thing2 < temp_file

EDIT để cung cấp thêm chi tiết cho câu hỏi trong bình luận:

Nếu >cố gắng là cả "chuyển đến chương trình" và "ghi vào tệp", nó có thể gây ra sự cố theo cả hai hướng.

Ví dụ đầu tiên: Bạn đang cố gắng ghi vào một tập tin. Đã tồn tại một tệp có tên mà bạn muốn ghi đè. Tuy nhiên, các tập tin là thực thi. Có lẽ, nó sẽ cố gắng thực hiện tập tin này, thông qua đầu vào. Bạn sẽ phải làm một cái gì đó như ghi đầu ra vào một tên tệp mới, sau đó đổi tên tệp.

Ví dụ thứ hai: Như Florian Diesch đã chỉ ra, nếu có một lệnh khác ở đâu đó trong hệ thống có cùng tên (đó là trong đường dẫn thực thi). Nếu bạn định tạo một tệp có tên đó trong thư mục hiện tại của mình, bạn sẽ bị kẹt.

Thứ ba: nếu bạn gõ sai một lệnh, nó sẽ không cảnh báo bạn rằng lệnh đó không tồn tại. Ngay bây giờ, nếu bạn gõ ls | gerp log.txtnó sẽ cho bạn biết bash: gerp: command not found. Nếu >có nghĩa là cả hai, nó chỉ đơn giản là tạo một tệp mới cho bạn (sau đó cảnh báo nó không biết phải làm gì với log.txt).


Cảm ơn bạn. Bạn đã đề cập thing1 > temp_file && thing2 < temp_fileđể làm dễ dàng hơn với đường ống. Nhưng tại sao không sử dụng lại >toán tử để làm điều này, ví dụ thing1 > thing2cho các lệnh thing1thing2? Tại sao một nhà điều hành thêm |?
John Threepwood

1
"Lấy đầu ra và ghi nó vào một tệp" là một hành động khác với "Lấy đầu ra và chuyển nó đến một chương trình khác". Tôi sẽ chỉnh sửa thêm suy nghĩ vào câu trả lời của mình ...
David Oneill

1
@JohnThreepwood Chúng có ý nghĩa khác nhau. Điều gì xảy ra nếu tôi muốn chuyển hướng một cái gì đó đến một tệp có tên less, ví dụ? thing | lessthing > lesshoàn toàn khác nhau, vì họ làm những việc khác nhau. Những gì bạn đề xuất sẽ tạo ra một sự mơ hồ.
Darkhogg

Có chính xác không khi nói rằng "thing1> temp_file" chỉ là cú pháp cú pháp cho "thing1 | tee temp_file"? Kể từ khi tìm hiểu về tee, tôi gần như không bao giờ sử dụng chuyển hướng.
Sridhar Sarnobat

2
@ Sridhar-Sarnobat không, teelệnh làm điều gì đó khác biệt. teeghi đầu ra vào cả màn hình ( stdout) tệp. Redirect không chỉ tập tin.
David Oneill

22

Nếu ý nghĩa của foo > barnó phụ thuộc vào việc có một lệnh được đặt tên barsẽ khiến việc sử dụng chuyển hướng trở nên khó khăn hơn và dễ bị lỗi hơn: Mỗi lần tôi muốn chuyển hướng đến một tệp, trước tiên tôi phải kiểm tra xem có lệnh nào có tên như tệp đích của tôi không.


Đây chỉ là vấn đề nếu bạn viết barvào thư mục là một phần của $PATHbiến env của bạn . Nếu bạn đang ở trong một cái gì đó như / bin, thì ot có thể là một vấn đề. Nhưng ngay cả sau đó, barsẽ phải có bộ quyền thực thi, để kiểm tra shell không chỉ để tìm một tệp thực thi barmà thực sự có thể thực thi nó. Và nếu mối quan tâm là ghi đè lên tệp hiện có, noclobertùy chọn shell sẽ ngăn việc ghi đè các tệp hiện có trong các chuyển hướng.
Sergiy Kolodyazhnyy

13

Từ Cẩm nang Quản trị Hệ thống Unix và Linux:

Chuyển hướng

Shell diễn giải các ký hiệu <,> và >> dưới dạng hướng dẫn để định tuyến lại đầu vào hoặc đầu ra của lệnh đến hoặc từ một tệp .

Ống

Để kết nối STDOUT của một lệnh với STDIN của lệnh khác, hãy sử dụng | biểu tượng, thường được gọi là một đường ống.

Vì vậy, cách giải thích của tôi là: Nếu đó là lệnh để ra lệnh, hãy sử dụng một đường ống. Nếu bạn xuất ra hoặc từ một tệp, hãy sử dụng chuyển hướng.


12

Có một sự khác biệt quan trọng giữa hai nhà khai thác:

  1. ls > log.txt -> Lệnh này sẽ gửi đầu ra đến tệp log.txt.

  2. ls | grep file.txt-> Lệnh này sẽ gửi đầu ra của lệnh ls đến lệnh grep thông qua việc sử dụng pipe ( |) và lệnh grep tìm kiếm file.txt trong đầu vào được cung cấp cho lệnh trước đó.

Nếu bạn phải thực hiện cùng một tác vụ bằng cách sử dụng kịch bản đầu tiên, thì đó sẽ là:

ls > log.txt; grep 'file.txt' log.txt

Vì vậy, một đường ống (với |) được sử dụng để gửi đầu ra sang lệnh khác, trong khi chuyển hướng (với >) được sử dụng để chuyển hướng đầu ra đến một số tệp.


3

Có một sự khác biệt lớn về cú pháp giữa hai:

  1. Chuyển hướng là một đối số cho một chương trình
  2. Một đường ống ngăn cách hai lệnh

Bạn có thể nghĩ về các chuyển hướng như thế này : cat [<infile] [>outfile]. Điều này ngụ ý thứ tự không quan trọng: cat <infile >outfilegiống như cat >outfile <infile. Bạn thậm chí có thể kết hợp chuyển hướng lên với các đối số khác: cat >outfile <infile -bcat <infile -b >outfilecả hai đều hoàn toàn tốt. Ngoài ra, bạn có thể xâu chuỗi nhiều hơn một đầu vào hoặc đầu ra (đầu vào sẽ được đọc tuần tự và tất cả đầu ra sẽ được ghi vào mỗi tệp đầu ra) : cat >outfile1 >outfile2 <infile1 <infile2. Mục tiêu hoặc nguồn của chuyển hướng có thể là tên tệp hoặc tên của luồng (như & 1, ít nhất là trong bash).

Nhưng các đường ống hoàn toàn tách biệt một lệnh với một lệnh khác, bạn không thể trộn chúng với các đối số:

[command1] | [command2]

Ống lấy mọi thứ được ghi vào đầu ra tiêu chuẩn từ lệnh1 và gửi nó đến đầu vào tiêu chuẩn của lệnh2.

Bạn cũng có thể kết hợp đường ống và chuyển hướng. Ví dụ:

cat <infile >outfile | cat <infile2 >outfile2

Đầu tiên catsẽ đọc các dòng từ infile, sau đó viết đồng thời từng dòng để outfile và gửi nó đến dòng thứ hai cat.

Trong lần thứ hai cat, đầu vào tiêu chuẩn trước tiên đọc từ đường ống (nội dung của infile), sau đó đọc từ infile2, ghi từng dòng vào outfile2. Sau khi chạy này, outfile sẽ là một bản sao của infile và outfile2 sẽ chứa infile theo sau là infile2.

Cuối cùng, bạn thực sự làm một cái gì đó thực sự giống với ví dụ của bạn bằng cách sử dụng chuyển hướng "chuỗi ở đây" (chỉ dành cho gia đình bash) và backticks:

grep blah <<<`ls`

sẽ cho kết quả tương tự như

ls | grep blah

Nhưng tôi nghĩ rằng phiên bản chuyển hướng trước tiên sẽ đọc tất cả đầu ra của ls vào một bộ đệm (trong bộ nhớ), sau đó cung cấp bộ đệm đó để grep một dòng tại một thời điểm, trong khi phiên bản đường ống sẽ lấy từng dòng từ ls khi nó xuất hiện, và chuyển dòng đó đến grep.


1
Nitpick: thứ tự các vấn đề trong chuyển hướng nếu bạn chuyển hướng một fd sang một cái khác: echo yes 1>&2 2>/tmp/blah; wc -l /tmp/blah; echo yes 2>/tmp/blah 1>&2; wc -l /tmp/blahHơn nữa, chuyển hướng đến một tệp sẽ chỉ sử dụng chuyển hướng cuối cùng. echo yes >/tmp/blah >/tmp/blah2sẽ chỉ viết cho /tmp/blah2.
muru

2
Chuyển hướng không thực sự tranh luận với chương trình. Chương trình sẽ không biết hoặc quan tâm đầu ra của nó đi đâu (hoặc đầu vào đến từ đâu). Đó chỉ là cách nói với bash cách sắp xếp mọi thứ trước khi chạy chương trình.
Alois Mahdal

3

Lưu ý: Câu trả lời phản ánh sự hiểu biết của tôi về các cơ chế này cho đến nay, được tích lũy qua nghiên cứu và đọc câu trả lời của các đồng nghiệp trên trang web này và unix.stackexchange.com , và sẽ được cập nhật khi có thời gian. Đừng ngần ngại đặt câu hỏi hoặc đề xuất cải tiến trong các ý kiến. Tôi cũng đề nghị bạn thử xem các tòa nhà chọc trời hoạt động như thế nào trong shell với stracelệnh. Ngoài ra, vui lòng không bị đe dọa bởi khái niệm bên trong hoặc tòa nhà - bạn không cần phải biết hoặc có thể sử dụng chúng để hiểu cách vỏ làm mọi thứ, nhưng chúng chắc chắn giúp hiểu.

TL; DR

  • |các đường ống không được liên kết với một mục trên đĩa, do đó không có số lượng hệ thống tập tin đĩa inode (nhưng có inode trong hệ thống tập tin ảo pipefs trong không gian kernel), nhưng chuyển hướng thường liên quan đến các tập tin, có mục nhập đĩa và do đó có tương ứng inode.
  • các đường ống không lseek()có khả năng để các lệnh không thể đọc một số dữ liệu và sau đó tua lại, nhưng khi bạn chuyển hướng với >hoặc <thường là một tệp lseek()có thể là đối tượng, vì vậy các lệnh có thể điều hướng theo ý muốn.
  • chuyển hướng là các thao tác trên mô tả tập tin, có thể nhiều; ống chỉ có hai mô tả tập tin - một cho lệnh trái và một cho lệnh phải
  • chuyển hướng trên các luồng tiêu chuẩn và đường ống đều được đệm.
  • ống hầu như luôn luôn liên quan đến việc rèn và do đó các cặp quy trình có liên quan; chuyển hướng - không phải luôn luôn, mặc dù trong cả hai trường hợp, mô tả tệp kết quả được kế thừa bởi các quy trình phụ.
  • ống luôn kết nối bộ mô tả tệp (một cặp), chuyển hướng - hoặc sử dụng tên đường dẫn hoặc mô tả tệp.
  • các đường ống là phương thức Giao tiếp giữa các quá trình, trong khi các chuyển hướng chỉ là các thao tác trên các tệp đang mở hoặc các đối tượng giống như tệp
  • cả hai đều sử dụng dup2()các tòa nhà cao tầng bên dưới mui xe để cung cấp các bản sao mô tả tệp, nơi xảy ra luồng dữ liệu thực tế.
  • chuyển hướng có thể được áp dụng "toàn cầu" với execlệnh tích hợp (xem cái nàycái này ), vì vậy nếu bạn thực hiện thì exec > output.txtmọi lệnh sẽ ghi vào output.txttừ đó trở đi. |ống chỉ được áp dụng cho lệnh hiện tại (có nghĩa là lệnh đơn giản hoặc lệnh con giống seq 5 | (head -n1; head -n2)hoặc lệnh ghép.
  • Khi chuyển hướng được thực hiện trên các tệp, những thứ như echo "TEST" > fileecho "TEST" >> filecả hai đều sử dụng hình chữ nhật open()trên tệp đó ( xem thêm ) và lấy mô tả tệp từ nó để chuyển nó đến dup2(). Ống |chỉ sử dụng pipe()dup2()tòa nhà.

  • Theo như các lệnh được thực thi, các đường ống và chuyển hướng không nhiều hơn các mô tả tệp - các đối tượng giống như tệp mà chúng có thể viết một cách mù quáng hoặc thao túng chúng bên trong (có thể tạo ra các hành vi không mong muốn; aptví dụ, có xu hướng thậm chí không ghi vào thiết bị xuất chuẩn nếu nó biết có sự chuyển hướng).

Giới thiệu

Để hiểu hai cơ chế này khác nhau như thế nào, cần phải hiểu các thuộc tính thiết yếu của chúng, lịch sử đằng sau hai cơ chế và nguồn gốc của chúng trong ngôn ngữ lập trình C. Trong thực tế, biết mô tả tập tin là gì, và cách thức dup2()pipe()các cuộc gọi hệ thống hoạt động là điều cần thiết, cũng như lseek(). Shell có nghĩa là một cách làm cho các cơ chế này trở nên trừu tượng với người dùng, nhưng đào sâu hơn sự trừu tượng giúp hiểu bản chất thực sự của hành vi của shell.

Nguồn gốc của chuyển hướng và đường ống

Theo bài báo Prophetic Petroglyphs của Dennis Ritche , các đường ống có nguồn gốc từ một bản ghi nhớ nội bộ năm 1964 của Malcolm Douglas McIlroy , tại thời điểm họ đang làm việc trên hệ điều hành Multics . Trích dẫn:

Để đặt những mối quan tâm mạnh mẽ nhất của tôi vào một tóm tắt:

  1. Chúng ta nên có một số cách kết nối các chương trình như vòi vườn - vít ở một phân khúc khác khi nó trở nên cần thiết để xoa bóp dữ liệu theo cách khác. Đây cũng là cách của IO.

Điều rõ ràng là vào thời điểm đó, các chương trình có khả năng ghi vào đĩa, tuy nhiên điều đó không hiệu quả nếu đầu ra lớn. Để trích dẫn lời giải thích của Brian Kernighan trong video Unix Pipeline :

Trước tiên, bạn không phải viết một chương trình lớn - bạn đã có các chương trình nhỏ hơn hiện có thể thực hiện các phần của công việc ... Một điều nữa là có thể lượng dữ liệu bạn cung cấp sẽ không phù hợp nếu bạn đã lưu nó trong một tệp ... bởi vì hãy nhớ rằng, chúng ta sẽ quay lại thời mà đĩa trên những thứ này có, nếu bạn may mắn, một Megabyte hoặc hai dữ liệu ... Vì vậy, đường ống không bao giờ phải khởi tạo toàn bộ đầu ra .

Do đó, sự khác biệt về khái niệm là rõ ràng: đường ống là một cơ chế làm cho các chương trình nói chuyện với nhau. Chuyển hướng - là cách viết để nộp ở mức cơ bản. Trong cả hai trường hợp, shell làm cho hai điều này trở nên dễ dàng, nhưng bên dưới mui xe, có rất nhiều điều đang diễn ra.

Đi sâu hơn: các tòa nhà cao tầng và hoạt động bên trong của vỏ

Chúng tôi bắt đầu với khái niệm mô tả tập tin . Mô tả tệp mô tả về cơ bản một tệp đang mở (cho dù đó là tệp trên đĩa, hoặc trong bộ nhớ hoặc tệp ẩn danh), được biểu thị bằng một số nguyên. Hai luồng dữ liệu tiêu chuẩn (stdin, stdout, stderr) lần lượt là các mô tả tệp 0,1 và 2. Họ đến từ đâu ? Vâng, trong các lệnh shell, các bộ mô tả tệp được kế thừa từ shell cha của chúng. Và nó nói chung đúng cho tất cả các quy trình - quy trình con kế thừa các mô tả tệp của cha mẹ. Đối với trình tiện ích, thông thường đóng tất cả các mô tả tệp được kế thừa và / hoặc chuyển hướng đến các vị trí khác.

Quay lại chuyển hướng. Nó thực sự là gì? Đó là một cơ chế yêu cầu shell chuẩn bị các mô tả tệp cho lệnh (vì việc chuyển hướng được thực hiện bởi shell trước khi lệnh chạy) và chỉ ra chúng ở nơi người dùng đề xuất. Các định nghĩa tiêu chuẩn của chuyển hướng đầu ra là

[n]>word

Đó [n]là số mô tả tập tin. Khi bạn làm echo "Something" > /dev/nullsố 1 ​​được ngụ ý ở đó, và echo 2> /dev/null.

Bên dưới mui xe, việc này được thực hiện bằng cách sao chép mô tả tệp qua dup2()lệnh gọi hệ thống. Hãy lấy đi df > /dev/null. Shell sẽ tạo ra một tiến trình con khi dfchạy, nhưng trước đó nó sẽ mở /dev/nulldưới dạng mô tả tệp # 3 và dup2(3,1)sẽ được phát hành, tạo ra một bản sao của mô tả tệp 3 và bản sao sẽ là 1. Bạn biết cách bạn có hai tệp file1.txtfile2.txtvà khi bạn cp file1.txt file2.txtcó hai tệp giống nhau, nhưng bạn có thể thao tác chúng một cách độc lập không? Đó là điều tương tự xảy ra ở đây. Thường thì bạn có thể thấy rằng trước khi chạy, bashsẽ làm dup(1,10)một bản mô tả tệp sao chép số 1 stdout(và bản sao đó sẽ là fd # 10) để khôi phục lại sau này. Quan trọng là lưu ý rằng khi bạn xem xét các lệnh tích hợp(là một phần của chính shell và không có tệp trong /binhoặc ở nơi khác) hoặc các lệnh đơn giản trong shell không tương tác , shell không tạo ra một tiến trình con.

Và sau đó chúng ta có những thứ như [n]>&[m][n]&<[m]. Đây là sao chép mô tả tệp, cơ chế giống như dup2()bây giờ trong cú pháp shell, có sẵn thuận tiện cho người dùng.

Một trong những điều quan trọng cần lưu ý về chuyển hướng là thứ tự của chúng không cố định, nhưng rất quan trọng đối với cách shell diễn giải những gì người dùng muốn. So sánh như sau:

# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/  3>&2  2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd

# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/    2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd

Việc sử dụng thực tế những điều này trong kịch bản shell có thể linh hoạt:

và nhiều thứ khác.

Hệ thống nước với pipe()dup2()

Vậy làm thế nào để đường ống được tạo ra? Via pipe()syscall , mà sẽ mất đầu vào một mảng (hay còn gọi là danh sách) gọi là pipefdhai mặt hàng các loại int(số nguyên). Hai số nguyên này là mô tả tập tin. Các pipefd[0]sẽ được kết thúc đọc của ống và pipefd[1]sẽ là ghi kết thúc. Vì vậy, trong df | grep 'foo', grepsẽ nhận được bản sao pipefd[0]dfsẽ nhận được một bản sao của pipefd[1]. Nhưng bằng cách nào ? Tất nhiên, với sự kỳ diệu của tòa nhà dup2()chọc trời. Trong dfví dụ của chúng tôi, giả sử pipefd[1]có số 4, vì vậy, vỏ sẽ tạo ra một đứa trẻ, làm dup2(4,1)(nhớ cpví dụ của tôi ?), Và sau đó làm execve()để thực sự chạy df. Một cách tự nhiên,dfsẽ kế thừa mô tả tệp số 1, nhưng sẽ không biết rằng nó không còn trỏ đến thiết bị đầu cuối, mà thực sự là fd # 4, thực sự là đầu ghi của đường ống. Đương nhiên, điều tương tự sẽ xảy ra với grep 'foo'ngoại trừ với số lượng mô tả tệp khác nhau.

Bây giờ, câu hỏi thú vị: chúng ta có thể tạo đường ống chuyển hướng fd # 2 không, không chỉ fd # 1? Vâng, trong thực tế đó là những gì |&làm trong bash. Các tiêu chuẩn POSIX đòi hỏi ngôn ngữ lệnh shell để hỗ trợ df 2>&1 | grep 'foo'cú pháp cho mục đích đó, nhưng bashkhông |&là tốt.

Điều quan trọng cần lưu ý là các đường ống luôn luôn xử lý các mô tả tập tin. Có tồn tại FIFOhoặc tên ống , có tên tệp trên đĩa và cho phép bạn sử dụng nó như một tệp, nhưng hoạt động như một đường ống. Nhưng các |loại ống là những gì được gọi là ống ẩn danh - chúng không có tên tệp, vì chúng thực sự chỉ là hai đối tượng được kết nối với nhau. Việc chúng ta không xử lý các tệp cũng tạo ra một hàm ý quan trọng: đường ống không lseek()thể thực hiện được. Các tệp, trong bộ nhớ hoặc trên đĩa, là tĩnh - các chương trình có thể sử dụng lseek()syscall để chuyển đến byte 120, sau đó quay lại byte 10, sau đó chuyển tiếp đến hết. Các đường ống không tĩnh - chúng tuần tự và do đó bạn không thể tua lại dữ liệu bạn nhận được từ chúng bằnglseek(). Đây là điều làm cho một số chương trình nhận biết nếu họ đang đọc từ tệp hoặc từ đường ống, và do đó họ có thể thực hiện các điều chỉnh cần thiết để thực hiện hiệu quả; nói cách khác, a progcó thể phát hiện nếu tôi làm cat file.txt | proghoặc prog < input.txt. Ví dụ công việc thực sự của đó là đuôi .

Hai thuộc tính rất thú vị khác của các đường ống là chúng có bộ đệm, trên Linux là 4096 byte và chúng thực sự có một hệ thống tệp như được định nghĩa trong mã nguồn Linux ! Chúng không chỉ đơn giản là một đối tượng để truyền dữ liệu xung quanh, mà chúng còn là cơ sở hạ tầng! Trong thực tế, vì tồn tại hệ thống tập tin pipefs, quản lý cả hai đường ống và FIFO, các đường ống có số inode trên hệ thống tập tin tương ứng của chúng:

# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/  | cat  
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'

Trên các ống Linux là một hướng, giống như chuyển hướng. Trên một số triển khai giống như Unix - có các đường ống hai chiều. Mặc dù với phép thuật của kịch bản shell, bạn cũng có thể tạo các đường ống hai chiều trên Linux .

Xem thêm:


2

Để thêm vào các câu trả lời khác, cũng có sự khác biệt về ngữ nghĩa tinh tế - ví dụ: các đường ống gần dễ dàng hơn các chuyển hướng:

seq 5 | (head -n1; head -n1)                # just 1
seq 5 > tmp5; (head -n1; head -n1) < tmp5   # 1 and 2
seq 5 | (read LINE; echo $LINE; head -n1)   # 1 and 2

Trong ví dụ đầu tiên, khi cuộc gọi đầu tiên headkết thúc, nó sẽ đóng đường ống và seqkết thúc, do đó không có đầu vào nào cho lần thứ hai head.

Trong ví dụ thứ hai, head tiêu thụ dòng đầu tiên, nhưng khi nó đóng stdin đường ống của chính nó , tệp vẫn mở cho cuộc gọi tiếp theo sử dụng.

Ví dụ thứ ba cho thấy rằng nếu chúng ta sử dụng readđể tránh đóng đường ống thì nó vẫn có sẵn trong quy trình con.

Vì vậy, "luồng" là thứ mà chúng ta chuyển dữ liệu qua (stdin, v.v.) và giống nhau trong cả hai trường hợp, nhưng đường ống kết nối các luồng từ hai quy trình, trong đó một chuyển hướng kết nối một luồng giữa một quy trình và một tệp, vì vậy bạn có thể thấy nguồn của cả những điểm tương đồng và khác biệt.

PS Nếu bạn tò mò và / hoặc ngạc nhiên với những ví dụ như tôi, bạn có thể tìm hiểu thêm bằng cách sử dụng trapđể xem các quy trình giải quyết như thế nào, vd:

(trap 'echo seq EXITed >&2' EXIT; seq 5) | (trap 'echo all done' EXIT; (trap 'echo first head exited' EXIT; head -n1)
echo '.'
(trap 'echo second head exited' EXIT; head -n1))

Đôi khi quá trình đầu tiên đóng trước khi 1được in, đôi khi sau đó.

Tôi cũng thấy thú vị khi sử dụng exec <&-để đóng luồng từ chuyển hướng để gần đúng hành vi của đường ống (mặc dù có lỗi):

seq 5 > tmp5
(trap 'echo all done' EXIT
(trap 'echo first head exited' EXIT; head -n1)
echo '.'
exec <&-
(trap 'echo second head exited' EXIT; head -n1)) < tmp5`

"Khi cuộc gọi đầu tiên kết thúc, nó sẽ đóng đường ống" Điều này thực sự không chính xác vì hai lý do. Một, (head -n1; head -n1) là subshell với hai lệnh, mỗi lệnh thừa hưởng đầu đọc của ống là mô tả 0, và do đó, subshell VÀ mỗi lệnh mở mô tả tệp đó. Lý do thứ hai, bạn có thể thấy điều đó với strace -f bash -c 'seq 5 | (đầu -n1; đầu -n1) '. Vì vậy, người đứng đầu chỉ đóng bản sao mô tả tệp
Sergiy Kolodyazhnyy

Ví dụ thứ ba cũng không chính xác, vì readchỉ tiêu thụ dòng đầu tiên (đó là một byte cho 1và dòng mới). seqđược gửi trong tổng số 10 byte (5 số và 5 dòng mới). Vì vậy, có 8 byte còn lại trong bộ đệm ống và đó là lý do tại sao thứ hai headhoạt động - vẫn còn dữ liệu trong bộ đệm ống. Btw, đầu chỉ thoát khi có 0 byte đọc, giống như tronghead /dev/null
Sergiy Kolodyazhnyy

Cảm ơn bạn đã làm rõ. Tôi có hiểu chính xác rằng trong seq 5 | (head -n1; head -n1)cuộc gọi đầu tiên làm trống đường ống, vì vậy nó vẫn tồn tại ở trạng thái mở nhưng không có dữ liệu cho cuộc gọi thứ hai tới head? Vì vậy, sự khác biệt trong hành vi giữa đường ống và chuyển hướng là do đầu kéo tất cả dữ liệu ra khỏi đường ống, nhưng chỉ có 2 dòng ra khỏi tệp xử lý?
Julian de Bhal

Đúng rồi. Và đó là thứ có thể nhìn thấy bằng stracelệnh tôi đã đưa ra trong bình luận đầu tiên. Với tính năng chuyển hướng, tệp tmp nằm trên đĩa khiến nó có thể tìm kiếm được (vì chúng sử dụng lseek()syscall - các lệnh có thể nhảy xung quanh tệp từ byte đầu tiên đến cuối cùng theo ý muốn. Nhưng các đường ống là tuần tự và không thể tìm kiếm được. công việc là đọc mọi thứ trước, hoặc nếu tệp lớn - ánh xạ một số tệp vào RAM thông qua mmap()cuộc gọi. Tôi đã từng tự mình làm tailPython và gặp vấn đề chính xác như vậy.
Sergiy Kolodyazhnyy

Điều quan trọng cần nhớ là đầu đọc của ống (bộ mô tả tệp) được đưa vào subshell trước (...)và subshell sẽ tạo bản sao stdin của chính nó cho mỗi lệnh bên trong (...). Vì vậy, về mặt kỹ thuật, chúng được đọc từ cùng một đối tượng. Đầu tiên head nghĩ rằng nó đọc từ stdin của chính nó. Thứ hai headnghĩ rằng nó có stdin riêng của mình. Nhưng trong thực tế, fd # 1 (stdin) của họ chỉ là bản sao của cùng một fd, được đọc ở cuối ống. Ngoài ra, tôi đã đăng một câu trả lời, vì vậy có lẽ nó sẽ giúp làm rõ mọi thứ.
Sergiy Kolodyazhnyy

1

Tôi đã gặp một vấn đề với điều này trong C ngày hôm nay. Về cơ bản, ống có các ngữ nghĩa khác nhau để chuyển hướng là tốt, ngay cả khi được gửi đến stdin. Thực sự tôi nghĩ với sự khác biệt, các đường ống nên đi đâu đó ngoài stdin, để stdinvà gọi nó stdpipe(để tạo ra một sự khác biệt tùy ý) có thể được xử lý theo những cách khác nhau.

Xem xét điều này. Khi đường ống một đầu ra chương trình đến một chương trình khác fstatdường như trả về 0 vì st_sizemặc dù ls -lha /proc/{PID}/fdcho thấy có một tệp. Khi chuyển hướng một tập tin, đây không phải là trường hợp (ít nhất là trên debian wheezy, stretchjessievanilla và ubfox 14.04, 16.04vanilla.

Nếu bạn cat /proc/{PID}/fd/0chuyển hướng, bạn sẽ có thể lặp lại để đọc bao nhiêu lần tùy thích. Nếu bạn làm điều này với một đường ống, bạn sẽ nhận thấy rằng lần thứ hai bạn thực hiện nhiệm vụ liên tiếp, bạn sẽ không nhận được cùng một đầu ra.

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.