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


26

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


3
可能是因为它首先创建了文件ls.out,然后将输出写入文件
Dimitri Podborski '16年

1
如果要避免包含,可以始终将输出文件存储在其他目录中。例如,您可以使用ls > ../ls.out
Elder Geek '02

Answers:


36

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

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

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

避免这种情况的技巧:

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

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

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

    sponge仅在管道的其余部分完成执行后才写入文件,因此lsls.out创建之前运行(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被重定向为止;

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

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


1
请注意,在重定向期间,shell实际上不会接触数据(在输入重定向或输出重定向上)。它只是打开文件,然后将文件描述符传递给程序。
彼得·格林

11

man bash

重新定向

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

第一句话,建议将输出输出到除 stdin在执行命令之前将重定向。因此,为了重定向到文件,必须首先由外壳本身创建文件。

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

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,但它们的输出可以去文件,如果文件不首先存在吗?就像水一样。您能在不先将玻璃杯放在水龙头下方的情况下得到一杯水吗?


10

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

这是因为我们世界中的“一切都是文件”。屏幕输出为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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.