Sự khác biệt giữa những gì <<
, <<<
và < <
trong bash?
Sự khác biệt giữa những gì <<
, <<<
và < <
trong bash?
Câu trả lời:
Tài liệu ở đây
<<
được gọi là here-document
cấ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 wc
chương trình đợi EOF
chuỗ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 strace
lệnh. Thay thế bash
bằng sh
để xem cách /bin/sh
thự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ư bc
chú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 /tmp
mụ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_RDONLY
cờ là fd 4 và sau đó được hủy liên kết, sau đó dup2()
vào fd 0, được kế thừa cat
sau đó ):
$ 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/63
nơ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 ksh
trước khi có sự tồn tại của bash như trang ksh man và câu trả lời này gợi ý. Vỏ như tcsh
và mksh
tuy 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)
< <
đượ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)
< <
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<
<<<
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.
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ụ.
< <
là một lỗi cú pháp:
$ cat < <
bash: syntax error near unexpected token `<'
< <()
là 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.
<()
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
< <
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 bash
là command1
chạ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 command1
sẽ không bị mất với biến thể thay thế quy trình.
< <
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ừ ls
lệnh có thể được đọc từng dòng và echo
ed 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: