如何延迟 BASH`>` 的`redirection operator`

How to delay `redirection operator` of BASH `>`

首先我创建了 3 个文件:

$ touch alpha bravo carlos

然后我想将列表保存到文件中:

$ ls > info.txt

然而,我总是把我的 info.txt 放在里面:

$ cat info.txt
alpha
bravo
carlos
info.txt

看起来重定向运算符首先创建了我的 info.txt

在这种情况下,我的问题是。在创建 info.txt 之前如何保存我的文件列表?


主要问题是关于重定向运算符的。为什么它先行动,如何延迟它让我先完成我的任务?用上面的例子来回答。

一种简单的方法是将命令输出保存到一个变量中,如下所示:

ls_output="$(ls)"

然后使用以下任一命令将该变量的值写入文件:

printf '%s\n' "$ls_output" > info.txt
cat <<< "$ls_output" > info.txt
echo "$ls_output" > info.txt

这种方法的一些注意事项:

  • Bash 变量不能包含空字节。如果命令的输出包含空字节,则该字节及其后的所有内容都将被丢弃。
    • 但是,在 ls 的特定情况下,这应该不是问题,因为 ls 的输出永远不应包含空字节。
  • $(...) 删除尾随换行符。上面通过在创建 info.txt 时添加一个换行符来弥补这一点,但是如果命令输出以 多个 换行符结尾,那么上面将有效地将它们折叠成一个换行符。
    • ls 的特定情况下,如果文件名以换行符结尾,则可能会发生这种情况 — 非常不寻常,不太可能是故意的,但仍然有可能。
  • 由于上面在创建 info.txt 时添加了一个换行符,即使命令输出没有以换行符结尾,它也会在那里放置一个换行符。
    • ls 的特定情况下,这应该不是问题,因为 ls 的输出应始终以换行符结尾。

如果你想避免上述问题,另一种方法是将你的命令输出保存到不同目录的临时文件中,然后将其移动到正确的位置;例如:

tmpfile="$(mktemp)"
ls > "$tmpfile"
mv -- "$tmpfile" info.txt

。 . .这显然有不同的注意事项(例如,它需要访问才能写入不同的目录),但应该适用于大多数系统。

当您将命令的输出重定向到文件时,shell 打开目标文件的文件句柄,然后在其标准输出连接到该文件句柄的子进程中运行该命令。无法更改此顺序,但如果您不希望 ls 输出包含新文件,您可以重定向到不同目录中的文件。

ls >/tmp/info.txt
mv /tmp/info.txt ./

在生产脚本中,您应该确保文件名是唯一且不可预测的。

t=$(mktemp -t lstemp.XXXXXXXXXX) || exit
trap 'rm -f "$t"' INT HUP 
ls >"$t"
mv "$t" ./info.txt

或者,将输出捕获到一个变量中,然后将该变量写入文件。

files=$(ls)
echo "$files" >info.txt

顺便说一句,可能don't use ls in scripts.如果你想要当前目录中的文件列表

printf '%s\n' *

这样做。

一种方法是从 ls 输出中排除 info.txt 文件。

如果您可以将列表文件重命名为 .info.txt 那么它就很简单:

ls >.info.txt
默认情况下,

ls 不列出名称以 . 开头的文件。

如果你不能重命名列表文件但你有 GNU ls 那么你可以使用:

ls --ignore=info.txt >info.txt

否则,您可以使用:

ls | grep -v '^info\.txt$' >info.txt

以上所有选项的优点是您可以在创建列表文件后安全地运行它们。

另一种通用方法是用一条命令捕获ls的输出,然后用第二条命令将其保存到列表文件中。正如其他人指出的那样,临时文件和 shell 变量是捕获输出的两种特定方式。另一种方式,如果你有 moreutils package installed, is to use the sponge 实用程序:

ls | sponge info.txt

最后,请注意,如果 info.txt 包含纯 ls 输出,您可能无法从中可靠地提取文件列表。有关详细信息,请参阅 ParsingLs - Greg's Wiki