Edw*_*lds 26 command-line bash redirect
为什么会$ ls > ls.out导致“ls.out”包含在当前目录中的文件名列表中?为什么选择这个?不然为什么不呢?
kos*_*kos 36
在评估命令时,>首先解决重定向:因此在ls运行时输出文件已经创建。
这也是为什么>在同一命令中使用重定向读取和写入同一文件会截断文件的原因;到命令运行时,文件已被截断:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
Run Code Online (Sandbox Code Playgroud)
避免这种情况的技巧:
<<<"$(ls)" > ls.out (适用于在解决重定向之前需要运行的任何命令)
命令替换在评估外部命令ls之前运行,因此在ls.out创建之前运行:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
Run Code Online (Sandbox Code Playgroud)ls | sponge ls.out (适用于在解决重定向之前需要运行的任何命令)
sponge仅当管道的其余部分完成执行时才写入文件,因此ls在ls.out创建之前运行(sponge随moreutils包提供):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
Run Code Online (Sandbox Code Playgroud)ls * > ls.out(适用于ls > ls.out's 的具体情况)
文件名扩展在重定向解决之前执行,因此ls将运行其参数,其中不包含ls.out:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
Run Code Online (Sandbox Code Playgroud)关于为什么在程序/脚本/运行之前解决重定向的原因,我没有看到强制这样做的具体原因,但我认为最好这样做有两个原因:
事先不重定向 STDIN 会使程序/脚本/任何东西保持直到 STDIN 被重定向;
事先不重定向 STDOUT 必须使 shell 缓冲程序/脚本/任何输出,直到 STDOUT 被重定向;
所以在第一种情况下浪费时间,在第二种情况下浪费时间和内存。
这只是我想到的,我并不是说这些是真正的原因;但我想总而言之,如果有选择的话,出于上述原因,他们无论如何都会先进行重定向。
Ser*_*nyy 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
Run Code Online (Sandbox Code Playgroud)
但为什么?
想一想 - 输出在哪里?程序具有诸如printf, sprintf, 之类的函数,puts默认情况下它们都转到stdout,但是如果文件首先不存在,它们的输出可以转到文件吗?这就像水一样。不先把玻璃杯放在水龙头下面,你能得到一杯水吗?
Oli*_*Oli 10
我不同意目前的答案。必须在命令运行之前打开输出文件,否则命令将无处可写其输出。
这是因为在我们的世界中“一切都是文件”。输出到屏幕是 SDOUT(又名文件描述符 1)。对于要写入终端的应用程序,它会打开fd1 并像写入文件一样写入它。
当您在 shell 中重定向应用程序的输出时,您正在更改 fd1,因此它实际上指向该文件。当您通过管道传输时,您将一个应用程序的 STDOUT 更改为另一个应用程序的 STDIN (fd0)。
但这一切都很好,但是您可以很容易地了解它是如何与strace. 这是很重的东西,但这个例子很短。
strace sh -c "ls > ls.out" 2> strace.out
Run Code Online (Sandbox Code Playgroud)
strace.out我们可以在其中看到以下亮点:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Run Code Online (Sandbox Code Playgroud)
这打开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
Run Code Online (Sandbox Code Playgroud)
这有点玩杂耍。我们将 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
Run Code Online (Sandbox Code Playgroud)
这是它在搜索可执行文件。一个教训也许没有很长的路;)
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
Run Code Online (Sandbox Code Playgroud)
然后命令运行,父进程等待。在此操作期间,任何 STDOUT 都将实际映射到 上的打开文件句柄ls.out。当子SIGCHLD进程发出 时,这会告诉父进程它已完成并且可以继续。它以更多的杂耍和结束结束ls.out。
为什么有这么多杂耍?不,我也不完全确定。
当然你可以改变这种行为。您可以使用类似的东西缓冲到内存中,sponge并且从正在进行的命令中看不到。我们仍在影响文件描述符,但不是以文件系统可见的方式。
ls | sponge ls.out
Run Code Online (Sandbox Code Playgroud)
还有一篇关于在 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");
}
}
Run Code Online (Sandbox Code Playgroud)