Tại sao 'ls> ls.out' khiến 'ls.out' được đưa vào danh sách tên?


26

Tại sao $ ls > ls.outkhiế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?


3
có lẽ bởi vì trước tiên nó tạo một tệp ls.out và sau đó ghi đầu ra cho nó
Dimitri Podbourski

1
Nếu bạn muốn tránh việc đưa vào, bạn luôn có thể lưu trữ tệp đầu ra trong một thư mục khác. Ví dụ: bạn có thể chọn thư mục mẹ (miễn là bạn không ở thư mục gốc của hệ thống tệp) với ls > ../ls.out
Elder Geek

Câu trả lời:


36

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 lschạ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)

    spongechỉ 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 moreutilsgói):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(hoạt động cho ls > ls.outtrườ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 đó lssẽ 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.


1
Lưu ý rằng trong quá trình chuyển hướng, vỏ không thực sự chạm vào dữ liệu (trên chuyển hướng đầu vào hoặc chuyển hướng đầu ra). Nó chỉ mở tệp và chuyển mô tả tệp xuống chương trình.
Peter Green

11

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 stdinchuyể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?


10

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.outchú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.outnhư 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.outvà đó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ư spongevà 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

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.