当前位置: 首页>>技术问答>>正文


为什么“ ls> ls.out’导致’ls.out’包含在名称列表中?

, , ,

问题描述

为什么$ ls > ls.out导致’ls.out’包含在当前目录中的文件名称列表中?为什么选择这个呢?为什么不这样呢?

最佳解决办法

在评估命令时,首先解决了>重定向:因此,到ls运行时,已经创建了输出文件。

这也是为什么在同一命令中使用>重定向读取和写入同一文件会截断该文件的原因。在命令运行时,文件已被截断:

$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$ 

避免这种情况的技巧:

  • <<<"$(ls)" > ls.out(适用于需要在重定向解决之前运行的任何命令)

    在评估外部命令之前运行命令替换,因此在创建ls.out之前运行ls

    $ ls
    bar  foo
    $ <<<"$(ls)" > ls.out
    $ cat ls.out 
    bar
    foo
    
  • ls | sponge ls.out(适用于需要在重定向解决之前运行的任何命令)

    sponge仅在管道的其余部分完成执行后才写入文件,因此在创建ls.out之前,ls已运行(spongemoreutils软件包一起提供):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
    
  • ls * > ls.out(适用于ls > ls.out的特定情况)

    文件名扩展是在解决重定向之前执行的,因此ls将在其参数上运行,该参数不包含ls.out

    $ ls
    bar  foo
    $ ls * > ls.out
    $ cat ls.out 
    bar
    foo
    $
    

关于为什么要在程序/脚本/任何程序运行之前解决重定向,我看不到强制执行重定向的具体原因,但我看到了这样做更好的两个原因:

  • 事先不重定向STDIN会使程序/脚本/保持任何状态,直到STDIN被重定向;

  • 事先不重定向STDOUT应该一定会使shell缓冲程序的/脚本/诸如此类的输出,直到STDOUT被重定向为止;

因此,在第一种情况下浪费时间,在第二种情况下浪费时间和 memory 。

这就是我所发生的事情,我并不是在说这些是实际原因。但是我想,总的来说,如果有选择的话,由于上述原因,他们还是会先进行重定向。

次佳解决办法

man bash

REDIRECTION

Before a command is executed, its input and output may be redirected using a special notation interpreted by the shell. Redirection allows commands’ file handles to be duplicated, opened, closed, made to refer to different files, and can change the files the command reads from and writes to.

第一句话,表明在执行命令之前,将输出重定向到stdin以外的其他地方。因此,为了重定向到文件,必须首先由 shell 本身创建文件。

为避免产生文件,建议您首先将输出重定向到命名管道,然后再重定向到文件。请注意使用&将对终端的控制权返回给用户

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

但为什么?

考虑一下-输出将在哪里?程序具有诸如printfsprintfputs之类的功能,默认情况下,所有这些功能都移至stdout,但是如果文件不存在于文件中,它们的输出可以转到文件中吗?就像水一样。您能在不先将玻璃杯放在水龙头下方的情况下得到一杯水吗?

第三种解决办法

我不同意当前的答案。在命令运行之前,必须先打开输出文件,否则命令将无处可写其输出。

这是因为我们世界中的“一切都是文件”。屏幕输出为SDOUT(又名文件描述符1)。对于要写入终端的应用程序,它将打开fd1并像文件一样对其进行写入。

当您在Shell中重定向应用程序的输出时,您正在更改fd1,因此它实际上指向文件。进行管道传输时,将一个应用程序的STDOUT更改为另一个应用程序的STDIN(fd0)。


但这很不错,但是您可以很轻松地了解一下strace的工作原理。这是很重的东西,但是这个例子很短。

strace sh -c "ls > ls.out" 2> strace.out

strace.out中,我们可以看到以下亮点:

open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

这会将ls.out打开为fd3。只写。截断(覆盖)(如果存在),否则创建。

fcntl(1, F_DUPFD, 10)                   = 10
close(1)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0

这有点杂耍。我们将STDOUT(fd1)分流到fd10并将其关闭。这是因为我们不会使用此命令将任何内容输出到实际的STDOUT。通过将写句柄复制到ls.out并关闭原始句柄来完成。

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

这是在搜索可执行文件。一堂课可能没有很长的路要走;)

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

然后命令运行,父级等待。在此操作过程中,所有STDOUT实际上都将映射到ls.out上的打开文件句柄。当子进程发出SIGCHLD时,这告诉父进程已完成并且可以恢复。最后一点点杂耍结束,然后关闭ls.out

为什么会有这么多杂耍?不,我也不完全确定。


当然,您可以更改此行为。您可以将诸如sponge之类的内容缓冲到内存中,而这在后续命令中将是不可见的。我们仍在影响文件描述符,但不是以file-system-visible方式。

ls | sponge ls.out

第四种办法

还有一篇关于Implementation of redirection and pipe operators in shell的不错的文章。它显示了如何实现重定向,以便$ ls > ls.out看起来像:

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"); 
    }
}

参考资料

本文由Ubuntu问答整理, 博文地址: https://ubuntuqa.com/article/7444.html,未经允许,请勿转载。