我试图删除文件中除最后一行以外的所有行,但以下命令不起作用,尽管 file.txt 不为空。
$cat file.txt |tail -1 > file.txt
$cat file.txt
为什么会这样?
通过管道从文件重定向回同一个文件是不安全的;如果在开始读取第一阶段file.txt
之前设置管道的最后阶段时被 shell 覆盖,则最终输出为空。tail
请改为执行以下操作:
tail -1 file.txt >file.txt.new && mv file.txt.new file.txt
...好吧,实际上,不要在生产代码中这样做;特别是如果您在安全敏感的环境中并以 root 身份运行,则以下内容更合适:
tempfile="$(mktemp file.txt.XXXXXX)"
chown --reference=file.txt -- "$tempfile"
chmod --reference=file.txt -- "$tempfile"
tail -1 file.txt >"$tempfile" && mv -- "$tempfile" file.txt
另一种方法(避免临时文件,除非<<<
在您的平台上隐式创建它们)如下:
lastline="$(tail -1 file.txt)"; cat >file.txt <<<"$lastline"
(上面的实现是特定于 bash 的,但在 echo 没有的情况下有效——例如当最后一行包含“--version”时)。
最后,可以使用来自moreutils的海绵:
tail -1 file.txt | sponge file.txt
您可以使用 sed 从文件中删除除最后一行之外的所有行:
sed -i '$!d' file
在 'cat' 被执行之前,Bash 已经打开了 'file.txt' 进行写入,清除了它的内容。
通常,不要在同一语句中写入您正在读取的文件。这可以通过写入不同的文件来解决,如上所述:
$cat 文件.txt | 尾 -1 >另一个文件.txt $mv anotherfile.txt file.txt或使用moreutils中的海绵之类的实用程序:
$cat 文件.txt | 尾-1 | 海绵文件.txt这是有效的,因为海绵会等到其输入流结束后再打开其输出文件。
当您将命令字符串提交给 bash 时,它会执行以下操作:
当 'cat' 开始阅读时,'file.txt' 已经被 'tail' 截断了。
这都是 Unix 和 shell 环境设计的一部分,并且可以追溯到最初的 Bourne shell。'这是一个功能,而不是一个错误。
tmp=$(tail -1 file.txt); 回声 $tmp > 文件.txt;
这在 Linux shell 中运行良好:
replace_with_filter() {
local filename="$1"; shift
local dd_output byte_count filter_status dd_status
dd_output=$("$@" <"$filename" | dd conv=notrunc of="$filename" 2>&1; echo "${PIPESTATUS[@]}")
{ read; read; read -r byte_count _; read filter_status dd_status; } <<<"$dd_output"
(( filter_status > 0 )) && return "$filter_status"
(( dd_status > 0 )) && return "$dd_status"
dd bs=1 seek="$byte_count" if=/dev/null of="$filename"
}
replace_with_filter file.txt tail -1
dd
的“notrunc”选项用于将过滤后的内容写回原位,而dd
再次需要(使用字节数)来实际截断文件。如果新文件大小大于或等于旧文件大小,dd
则不需要第二次调用。
与文件复制方法相比,它的优点是:1) 不需要额外的磁盘空间,2) 对大文件的性能更快,以及 3) 纯 shell(dd 除外)。
正如 Lewis Baumstark 所说,它不喜欢您写入相同的文件名。
这是因为 shell 打开“file.txt”并在“cat file.txt”运行之前截断它以进行重定向。所以,你必须
tail -1 file.txt > file2.txt; mv file2.txt file.txt
仅针对这种情况,可以使用
猫 <文件.txt | (rm file.txt;tail -1 > file.txt)这将在连接“cat”与“(...)”中的子shell之前打开“file.txt”。“rm file.txt”将在子shell打开它以写入“tail”之前从磁盘中删除引用,但内容仍然可以通过传递给“cat”的打开的描述符获得,直到它关闭stdin。所以你最好确保这个命令会完成,否则“file.txt”的内容将会丢失
echo "$(tail -1 file.txt)" > file.txt
似乎不喜欢您将其写回相同文件名的事实。如果您执行以下操作,它将起作用:
$cat file.txt | tail -1 > anotherfile.txt
tail -1 > file.txt
将覆盖您的文件,导致 cat 读取一个空文件,因为重写将在您的管道中的任何命令执行之前发生。