Tại sao $ ls > ls.out
khiến 'ls.out' được đưa vào danh sách tên của các tệp trong thư mục hiện tại? Tại sao điều này được chọn là? Tại sao không?
ls > ../ls.out
Tại sao $ ls > ls.out
khiến 'ls.out' được đưa vào danh sách tên của các tệp trong thư mục hiện tại? Tại sao điều này được chọn là? Tại sao không?
ls > ../ls.out
Câu trả lời:
Khi đánh giá lệnh, việc >
chuyển hướng được giải quyết trước tiên: do đó, khi thời gian ls
chạy, tệp đầu ra đã được tạo.
Đây cũng là lý do tại sao đọc và ghi vào cùng một tệp bằng cách sử dụng >
chuyển hướng trong cùng một lệnh cắt ngắn tệp; tại thời điểm lệnh chạy tệp đã bị cắt ngắn:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
Thủ thuật để tránh điều này:
<<<"$(ls)" > ls.out
(hoạt động cho bất kỳ lệnh nào cần chạy trước khi chuyển hướng được giải quyết)
Thay thế lệnh được chạy trước khi lệnh bên ngoài được ước tính, vì vậy ls
được chạy trước khi ls.out
được tạo:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(hoạt động cho bất kỳ lệnh nào cần chạy trước khi chuyển hướng được giải quyết)
sponge
chỉ ghi vào tệp khi phần còn lại của đường ống đã thực hiện xong, do đó ls
được chạy trước khi ls.out
được tạo ( sponge
được cung cấp cùng với moreutils
gói):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(hoạt động cho ls > ls.out
trường hợp cụ thể)
Việc mở rộng tên tệp được thực hiện trước khi chuyển hướng được giải quyết, do đó ls
sẽ chạy trên các đối số của nó, không chứa ls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
Tại sao chuyển hướng được giải quyết trước khi chương trình / script / bất cứ điều gì đang chạy, tôi không thấy một lý do cụ thể tại sao nó là bắt buộc phải làm như vậy, nhưng tôi thấy hai lý do tại sao nó là tốt hơn để làm như vậy:
không chuyển hướng STDIN trước sẽ làm cho chương trình / tập lệnh / bất cứ điều gì bị giữ cho đến khi STDIN được chuyển hướng;
không chuyển hướng STDOUT trước nên nhất thiết phải tạo bộ đệm shell cho đầu ra của chương trình / script / cho đến khi STDOUT được chuyển hướng;
Vì vậy, lãng phí thời gian trong trường hợp đầu tiên và lãng phí thời gian và bộ nhớ trong trường hợp thứ hai.
Đây chỉ là những gì xảy ra với tôi, tôi không khẳng định đây là những lý do thực tế; nhưng tôi đoán rằng tất cả trong tất cả, nếu có một sự lựa chọn, họ sẽ đi với chuyển hướng trước dù sao đi nữa vì những lý do nêu trên.
Từ man bash
:
GIẢM GIÁ
Trước khi một lệnh được thực thi, đầu vào và đầu ra của nó có thể được chuyển hướng bằng cách sử dụng một ký hiệu đặc biệt được giải thích bởi shell. Chuyển hướng cho phép xử lý tệp của lệnh được sao chép, mở, đóng, được thực hiện để tham chiếu đến các tệp khác nhau và có thể thay đổi các tệp mà lệnh đọc từ và ghi vào.
Câu đầu tiên, gợi ý rằng đầu ra được thực hiện để đi đến một nơi khác ngoài stdin
chuyển hướng ngay trước khi lệnh được thực thi. Do đó, để được chuyển hướng đến tệp, trước tiên tệp phải được tạo bởi trình bao.
Để tránh có tệp, tôi khuyên bạn nên chuyển hướng đầu ra sang đường ống được đặt tên trước, sau đó mới chuyển sang tệp. Lưu ý việc sử dụng &
để trả lại quyền kiểm soát thiết bị đầu cuối cho người dùng
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
Nhưng tại sao?
Hãy nghĩ về điều này - nơi sẽ là đầu ra? Một chương trình có các chức năng như printf
, sprintf
, puts
, mà tất cả theo mặc định đi đến stdout
, nhưng sản lượng của họ có thể được đi đến tập tin nếu tập tin không tồn tại ở nơi đầu tiên? Nó giống như nước. Bạn có thể lấy một ly nước mà không cần đặt kính bên dưới vòi trước không?
Tôi không đồng ý với câu trả lời hiện tại. Tệp đầu ra phải được mở trước khi lệnh chạy hoặc lệnh sẽ không có chỗ nào để ghi đầu ra của nó.
Điều này là do "mọi thứ là một tập tin" trong thế giới của chúng ta. Đầu ra cho màn hình là SDOUT (còn gọi là mô tả tệp 1). Đối với một ứng dụng để ghi vào thiết bị đầu cuối, nó sẽ mở fd1 và ghi vào nó như một tệp.
Khi bạn chuyển hướng đầu ra của ứng dụng trong trình bao, bạn đang thay đổi fd1 để nó thực sự trỏ vào tệp. Khi bạn chuyển đổi, bạn thay đổi STDOUT của một ứng dụng để trở thành STDIN của ứng dụng khác (fd0).
Nhưng tất cả đều tốt khi nói điều đó, nhưng bạn hoàn toàn có thể dễ dàng nhìn vào cách thức hoạt động của nó strace
. Đó là thứ khá nặng nhưng ví dụ này khá ngắn.
strace sh -c "ls > ls.out" 2> strace.out
Trong strace.out
chúng ta có thể thấy những điểm nổi bật sau:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Điều này mở ra ls.out
như fd3
. Chỉ viết. Cắt ngắn (ghi đè) nếu tồn tại, nếu không sẽ tạo ra.
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
Đây là một chút tung hứng. Chúng tôi tắt STDOUT (fd1) thành fd10 và tắt nó đi. Điều này là do chúng tôi không đưa ra bất cứ điều gì cho STDOUT thực sự với lệnh này. Nó kết thúc bằng cách nhân đôi tay cầm ghi ls.out
và đóng cái ban đầu.
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
Đây là nó tìm kiếm cho thực thi. Một bài học có lẽ không có một con đường dài;)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
Sau đó, lệnh chạy và cha mẹ chờ đợi. Trong hoạt động này, bất kỳ STDOUT nào sẽ thực sự được ánh xạ tới xử lý tệp đang mở ls.out
. Khi đứa trẻ phát hành SIGCHLD
, điều này cho cha mẹ biết quá trình hoàn thành của nó và nó có thể tiếp tục. Nó kết thúc với một chút tung hứng và gần gũi ls.out
.
Tại sao có quá nhiều trò tung hứng? Không, tôi cũng không hoàn toàn chắc chắn.
Tất nhiên bạn có thể thay đổi hành vi này. Bạn có thể đệm vào bộ nhớ với thứ gì đó giống như sponge
và nó sẽ ẩn với lệnh tiến hành. Chúng tôi vẫn ảnh hưởng đến các mô tả tệp, nhưng không phải theo cách có thể nhìn thấy hệ thống tệp.
ls | sponge ls.out
Ngoài ra còn có một bài viết hay về Thực hiện chuyển hướng và vận hành đường ống trong vỏ . Điều này cho thấy cách chuyển hướng có thể được thực hiện để $ ls > ls.out
có thể trông như thế nào:
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}