Sự khác biệt giữa <<, <<< và << trong bash là gì?


102

Sự khác biệt giữa những gì <<, <<<< <trong bash?


20
Ít nhất là trong những thời điểm tập trung vào Google này, thật khó để tìm kiếm các toán tử dựa trên biểu tượng này. Có một công cụ tìm kiếm nơi bạn có thể cắm "<< <<< <<" và nhận được bất cứ điều gì hữu ích không?
Daniel Griscom

11
@DanielGriscom Có SymbolHound .
Dennis

1
@DanielGriscom Stack Exchange được sử dụng để hỗ trợ tìm kiếm các biểu tượng, nhưng sau đó một cái gì đó đã bị hỏng và không ai từng sửa nó.
muru

Nó đã ở đó (và đã được gần một năm): Các toán tử điều khiển và chuyển hướng của shell là gì?
Scott

Câu trả lời:


115

Tài liệu ở đây

<<được gọi là here-documentcấu trúc. Bạn cho chương trình biết văn bản kết thúc là gì và bất cứ khi nào dấu phân cách đó được nhìn thấy, chương trình sẽ đọc tất cả nội dung bạn đã cung cấp cho chương trình dưới dạng đầu vào và thực hiện một tác vụ theo nó.

Ý tôi là đây:

$ wc << EOF
> one two three
> four five
> EOF
 2  5 24

Trong ví dụ này, chúng tôi bảo wcchương trình đợi EOFchuỗi, sau đó nhập năm từ và sau đó nhập EOFđể báo hiệu rằng chúng tôi đã hoàn thành việc đưa ra đầu vào. Trên thực tế, nó tương tự như tự chạy wc, gõ từ, sau đó nhấnCtrlD

Trong bash, chúng được thực hiện thông qua các tệp tạm thời, thường ở dạng /tmp/sh-thd.<random string>, trong khi trong dấu gạch ngang, chúng được triển khai dưới dạng các đường dẫn ẩn danh. Điều này có thể được quan sát thông qua truy tìm các cuộc gọi hệ thống bằng stracelệnh. Thay thế bashbằng shđể xem cách /bin/shthực hiện chuyển hướng này.

$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'

Đây

<<<được gọi là here-string. Thay vì nhập văn bản, bạn đưa ra một chuỗi văn bản được tạo sẵn cho một chương trình. Ví dụ, với chương trình như bcchúng ta có thể làm bc <<< 5*4để chỉ nhận đầu ra cho trường hợp cụ thể đó, không cần phải chạy bc tương tác.

Các chuỗi trong bash được triển khai thông qua các tệp tạm thời, thường ở định dạng /tmp/sh-thd.<random string>, sau đó không được liên kết, do đó chúng tạm thời chiếm một số không gian bộ nhớ nhưng không hiển thị trong danh sách các /tmpmục nhập thư mục và tồn tại dưới dạng tệp ẩn danh, vẫn có thể tồn tại được tham chiếu thông qua bộ mô tả tệp bởi chính shell và bộ mô tả tệp đó được kế thừa bởi lệnh và sau đó được sao chép vào bộ mô tả tệp 0 (stdin) thông qua dup2()chức năng. Điều này có thể được quan sát thông qua

$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd

Và thông qua các tòa nhà chọc trời (đầu ra được rút ngắn để dễ đọc; chú ý cách tệp tạm thời được mở dưới dạng fd 3, dữ liệu được ghi vào nó, sau đó nó được mở lại với O_RDONLYcờ là fd 4 và sau đó được hủy liên kết, sau đó dup2()vào fd 0, được kế thừa catsau đó ):

$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4)         = 4
[pid 10229] write(3, "\n", 1)           = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0)                  = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072)   = 5
[pid 10229] write(1, "TEST\n", 5TEST
)       = 5
[pid 10229] read(0, "", 131072)         = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Ý kiến: có khả năng vì ở đây các chuỗi sử dụng các tệp văn bản tạm thời, đó là lý do có thể khiến chuỗi ở đây luôn chèn một dòng mới, vì tệp văn bản theo định nghĩa POSIX phải có các dòng kết thúc bằng ký tự dòng mới.

Quy trình thay thế

Như tldp.org giải thích,

Thay thế quy trình cung cấp đầu ra của một quy trình (hoặc quy trình) vào stdin của quy trình khác.

Vì vậy, trong thực tế, điều này tương tự như thiết bị xuất chuẩn của một lệnh này sang lệnh khác, vd echo foobar barfoo | wc. Nhưng lưu ý: trong trang bash bạn sẽ thấy nó được ký hiệu là <(list). Vì vậy, về cơ bản, bạn có thể chuyển hướng đầu ra của nhiều lệnh (!).

Lưu ý: về mặt kỹ thuật khi bạn nói rằng < <bạn không đề cập đến một điều, nhưng hai chuyển hướng với <chuyển hướng đầu ra đơn và quá trình từ <( . . .).

Bây giờ điều gì xảy ra nếu chúng ta chỉ thay thế quá trình?

$ echo <(echo bar)
/dev/fd/63

Như bạn có thể thấy, trình bao tạo ra bộ mô tả tệp tạm thời /dev/fd/63nơi đầu ra đi (mà theo câu trả lời của Gilles , là một đường ống ẩn danh). Điều đó có nghĩa là <chuyển hướng mô tả tệp đó làm đầu vào thành một lệnh.

Vì vậy, ví dụ rất đơn giản sẽ là thực hiện thay thế quá trình đầu ra từ hai lệnh echo thành wc:

$ wc < <(echo bar;echo foo)
      2       2       8

Vì vậy, ở đây chúng ta tạo shell tạo một bộ mô tả tệp cho tất cả đầu ra xảy ra trong ngoặc đơn và chuyển hướng như đầu vào wc. Như mong đợi, wc nhận luồng đó từ hai lệnh echo, chính nó sẽ xuất ra hai dòng, mỗi dòng có một từ, và một cách thích hợp, chúng tôi có 2 từ, 2 dòng và 6 ký tự cộng với hai dòng mới được tính.

Lưu ý bên lề: Việc thay thế quy trình có thể được gọi là bashism (một lệnh hoặc cấu trúc có thể sử dụng trong các shell nâng cao như bash, nhưng không được chỉ định bởi POSIX), nhưng nó đã được thực hiện kshtrước khi có sự tồn tại của bash như trang ksh mancâu trả lời này gợi ý. Vỏ như tcshmkshtuy nhiên không có quá trình thay thế. Vì vậy, làm thế nào chúng ta có thể đi xung quanh chuyển hướng đầu ra của nhiều lệnh sang một lệnh khác mà không cần thay thế quá trình? Phân nhóm cộng với đường ống!

$ (echo foo;echo bar) | wc
      2       2       8

Thực tế, điều này giống như ví dụ trên, tuy nhiên, điều này khác với quy trình thay thế quy trình, vì chúng tôi tạo ra thiết bị xuất chuẩn của toàn bộ khung con và stdin wc liên kết với đường ống . Mặt khác, quá trình thay thế làm cho một lệnh đọc mô tả tệp tạm thời.

Vì vậy, nếu chúng ta có thể nhóm với đường ống, tại sao chúng ta cần thay thế quá trình? Bởi vì đôi khi chúng ta không thể sử dụng đường ống. Xem xét ví dụ dưới đây - so sánh kết quả đầu ra của hai lệnh với diff(cần hai tệp và trong trường hợp này chúng tôi sẽ cung cấp cho nó hai mô tả tệp)

diff <(ls /bin) <(ls /usr/bin)

7
< <được sử dụng khi một người nhận được stdin từ một sự thay thế quá trình . Một lệnh như vậy có thể trông giống như : cmd1 < <(cmd2). Ví dụ:wc < <(date)
John1024

4
Có, quá trình thay thế được hỗ trợ bởi bash, zsh và AT & T ksh {88,93} nhưng không phải bởi pdksh / mksh.
John1024

2
< < không phải là một điều tự nó, trong trường hợp thay thế tiến trình nó chỉ là một <tiếp theo một cái gì đó khác điều đó xảy ra để bắt đầu với<
immibis

1
@muru Theo như tôi biết, <<<lần đầu tiên được triển khai bởi cổng Unix của vỏ RC Plan 9 sau đó được zsh, bash và ksh93 áp dụng. Sau đó tôi sẽ không gọi nó là bashism.
jlliagre

3
Một ví dụ khác về nơi không thể sử dụng đường ống: echo 'foo' | read; echo ${REPLY}sẽ không quay trở lại foo, vì readđược bắt đầu trong lớp vỏ phụ - đường ống bắt đầu lớp vỏ phụ. Tuy nhiên, read < <(echo 'foo'); echo ${REPLY}trả về chính xác foo, vì không có vỏ phụ.
Paddy Landau

26

< < là một lỗi cú pháp:

$ cat < <
bash: syntax error near unexpected token `<'

< <()quá trình thay thế ( <()) kết hợp với chuyển hướng ( <):

Một ví dụ giả định:

$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63

Với quá trình thay thế, đường dẫn đến bộ mô tả tệp được sử dụng như tên tệp. Trong trường hợp bạn không muốn (hoặc không thể) sử dụng tên tệp trực tiếp, bạn kết hợp thay thế quy trình với chuyển hướng.

Để rõ ràng, không có < <nhà điều hành.


Tôi nhận được câu trả lời của bạn rằng, <() hữu ích hơn <() phải không?
solfish

1
@solfish <()cung cấp một thứ giống như tên tệp, vì vậy nó thường hữu ích hơn - < <()đang thay thế stdin khi không cần thiết. Trong wc, sau này xảy ra là hữu ích hơn. Nó có thể ít hữu ích hơn ở nơi khác
muru

12

< <là một lỗi cú pháp, có lẽ bạn có nghĩa command1 < <( command2 )là một chuyển hướng đầu vào đơn giản theo sau là một sự thay thế quá trình và rất giống nhau nhưng không tương đương với:

command2 | command1

Sự khác biệt giả sử bạn đang chạy bashcommand1chạy trong một lớp con trong trường hợp thứ hai trong khi nó được chạy trong trình bao hiện tại trong trường hợp thứ nhất. Điều đó có nghĩa là các biến được đặt command1sẽ không bị mất với biến thể thay thế quy trình.


11

< <sẽ đưa ra một lỗi cú pháp. Sử dụng đúng cách như sau:

Giải thích với sự giúp đỡ của các ví dụ:

Ví dụ cho < <():

while read line;do
   echo $line
done< <(ls)

Trong ví dụ trên, đầu vào của vòng lặp while sẽ đến từ lslệnh có thể được đọc từng dòng và echoed trong vòng lặp.

<()được sử dụng để thay thế quá trình. Thêm thông tin và ví dụ cho <()có thể được tìm thấy tại liên kết này:

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

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.