它是否相当于将命令直接打印到文件,而不是写入文件描述符?
插图
直接写入文件:
for i in {1..1000}; do >>x echo "$i"; done
使用 fd:
exec 3>&1 1>x
for i in {1..1000}; do echo "$i"; done
exec 1>&3 3>&-
后一种效率更高吗?
答案1
exec
使用 循环之前打开文件和将重定向放入循环中的命令之间的主要区别在于,前者只需要设置文件描述符一次,而后者会在循环的每次迭代中打开和关闭文件。
执行一次可能会更有效,但如果您要在循环内运行外部命令,则启动命令的成本差异可能会消失。 (echo
这里可能是内置的,所以这不适用)
如果输出将被发送到常规文件以外的其他文件(例如,如果x
是命名管道),则打开和关闭文件的行为可能对其他进程可见,因此行为也可能存在差异。
请注意,通过命令重定向和命令重定向之间实际上没有区别exec
,它们都打开文件并处理文件描述符编号。
这两个应该几乎是等价的,因为它们都是open()
文件和write()
文件。 (不过,在命令执行期间,fd 1 的存储方式有所不同。):
for i in {1..1000}; do
>>x echo "$i"
done
for i in {1..1000}; do
exec 3>&1 1>>x # assuming fd 3 is available
echo "$i" # here, fd 3 is visible to the command
exec 1>&3 3>&-
done
答案2
是的,这样效率更高
最简单的测试方法是将计数增加到 500000 并计时:
> time bash s1.sh; time bash s2.sh
bash s1.sh 16,47s user 10,00s system 99% cpu 26,537 total
bash s2.sh 10,51s user 3,50s system 99% cpu 14,008 total
strace(1) 揭示了原因(我们有一个简单的write
,而不是open
+5* fcntl
+2* dup
+2* close
+ write
):
因为for i in {1..1000}; do >>x echo "$i"; done
我们得到:
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "997\n", 4) = 4
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "998\n", 4) = 4
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "999\n", 4) = 4
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
open("x", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
write(1, "1000\n", 5) = 5
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
而exec 3>&1 1>x
我们会变得更加干净
write(1, "995\n", 4) = 4
write(1, "996\n", 4) = 4
write(1, "997\n", 4) = 4
write(1, "998\n", 4) = 4
write(1, "999\n", 4) = 4
write(1, "1000\n", 5) = 5
但请注意,差异不是由于“使用 FD”,而是由于您进行重定向的位置。例如,如果您这样做,for i in {1..1000}; do echo "$i"; done > x
您将获得与第二个示例几乎相同的性能:
bash s3.sh 10,35s user 3,70s system 100% cpu 14,042 total
答案3
总结一下并在此线程中添加一些新的信息,这里对四种方法进行了比较,按效率排序。我根据两个测试系列,通过时间测量(用户 + 系统)估算 100 万次迭代的效率。
- 这两个大致相同:
- 简单
>
循环重定向(时间:100%) exec
整个循环使用一次(时间:〜100%)
- 简单
- 用于
>>
每次迭代(时间:200% - 250%) - 用于
exec
每次迭代(时间:340% - 480%)
结论是这样的:
有一个小的使用exec
与简单重定向(例如>>
. (简单的更便宜)。它不会在单个命令执行级别上显示出来,但随着重复次数的增加,差异变得可见。尽管命令的执行权重重定向到了阴影差异,正如 ikkachu 在另一个答案中注意到的那样。