为什么’ls> ls.out’导致’ls.out’被列入名单?

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

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

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

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

诀窍避免这种情况:

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

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

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

    sponge仅在管道的其余部分完成执行时写入文件,因此在创建ls.out之前运行lsspongemoreutils包提供):

     $ 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被重定向;

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

这就是我发生的事情,我并没有声称这些是实际的原因; 但我想总而言之,如果一个人有选择的话,他们会因为上述原因而继续进行重定向。

来自man bash

REDIRECTION

在执行命令之前,可以使用shell解释的特殊表示法重定向其输入和输出。 重定向允许命令的文件句柄被复制,打开,关闭,用于引用不同的文件,并且可以更改命令读取和写入的文件。

第一句话,建议在执行命令之前,使用重定向使输出进入除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这样的printf ,默认情况下都是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这样的东西,并且从进行中的命令中看不到。 我们仍在影响文件描述符,但不是以文件系统可见的方式。

 ls | sponge ls.out 

还有一篇关于在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"); } }