回显 tail 命令会产生意外的输出?

回显 tail 命令会产生意外的输出?

该命令单独运行时会产生预期结果(crontab 的最后一行):

tail -n 1 /etc/crontab

但是,当我将其作为 echo 命令的一部分运行以将结果发送到文件时,它会添加工作目录中所有文件的摘要以及预期结果:

sudo bash -c 'echo $(tail -n 1 /etc/crontab) > /path/to/file'

为什么这个命令会产生额外的数据?

答案1

您的 crontab 行*中有一个或多个星号,表示“任何时间”。当从命令替换中替换该行时,结果类似于

echo * * * * * cmd > /path/to/file

尽管最多进一步的扩展不适用于命令替换的输出,路径名扩展(与字段分割一样):

命令替换的结果不应被处理以进行进一步的波形符扩展、参数扩展、命令替换或算术扩展。如果命令替换发生在双引号内,则字段分割和路径名扩展不得对替换结果执行。

路径名扩展会变成*.txt匹配文件名列表(通配符),其中*匹配所有内容。最终结果是您获得*crontab 行中列出的工作目录中的每个(非隐藏)文件名。


如果您发布的代码代表更复杂的命令,您可以通过引用扩展来解决此问题:

sudo bash -c 'echo "$(tail -n 1 /etc/crontab)" > /path/to/file'

但更直接的是完全失去echo

sudo bash -c 'tail -n 1 /etc/crontab > /path/to/file'

这应该可以满足您的要求,并且也更简单(唯一的其他实质性区别是该版本将省略原本会发生的字段分割,因此空间的运行不会被折叠)。

答案2

让我们考虑一个包含这些文件的目录:

$ ls
crontab  file1  file2  file3
$ cat crontab
f*

现在,让我们运行 tail 命令:

$ tail -n 1 crontab
f*

上面是最后一行crontab,这就是我们所期望的。然而:

$ echo $(tail -n 1 crontab)
file1 file2 file3

双引号消除了这个问题:

$ echo "$(tail -n 1 crontab)"
f*

如果没有双引号,则结果命令替换被外壳扩展。其中一项扩展是路径名扩展。在上面的情况下,这意味着f*扩展以匹配以f.

除非您明确想要 shell 扩展,否则请将所有 shell 变量和/或命令替换放在双引号内。

答案3

globing shell 机制将扩展*到本地文件。

crontab 行可能有一个*as 占位符。

例如,crontab 中的这一行在周日上午 7.47 运行,第一个星表示任何一天,第二个星表示任何月。

47  7 * * 0 /run/on/sunday

然后你tail,并发出

echo 47  7 * * 0 /run/on/sunday

这将扩展*到本地文件。

相关内容