10

我试图删除文件中除最后一行以外的所有行,但以下命令不起作用,尽管 file.txt 不为空。

$cat file.txt |tail -1 > file.txt

$cat file.txt

为什么会这样?

4

11 回答 11

19

通过管道从文件重定向回同一个文件是不安全的;如果在开始读取第一阶段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
于 2008-09-23T19:33:25.203 回答
5

您可以使用 sed 从文件中删除除最后一行之外的所有行:

sed -i '$!d' file
  • -i告诉 sed 将文件替换到位;否则,结果将写入 STDOUT。
  • $是匹配文件最后一行的地址。
  • d是删除命令。在这种情况下,它被! ,因此所有与地址不匹配的行都将被删除。
于 2008-09-24T14:56:43.597 回答
3

在 'cat' 被执行之前,Bash 已经打开了 'file.txt' 进行写入,清除了它的内容。

通常,不要在同一语句中写入您正在读取的文件。这可以通过写入不同的文件来解决,如上所述:

$cat 文件.txt | 尾 -1 >另一个文件.txt
$mv anotherfile.txt file.txt
或使用moreutils中的海绵之类的实用程序:
$cat 文件.txt | 尾-1 | 海绵文件.txt
这是有效的,因为海绵会等到其输入流结束后再打开其输出文件。

于 2008-09-23T19:39:29.430 回答
2

当您将命令字符串提交给 bash 时,它会执行以下操作:

  1. 创建一个 I/O 管道。
  2. 启动“/usr/bin/tail -1”,从管道读取,并写入 file.txt。
  3. 启动“/usr/bin/cat file.txt”,写入管道。

当 'cat' 开始阅读时,'file.txt' 已经被 'tail' 截断了。

这都是 Unix 和 shell 环境设计的一部分,并且可以追溯到最初的 Bourne shell。'这是一个功能,而不是一个错误。

于 2008-09-23T19:43:30.060 回答
2

tmp=$(tail -1 file.txt); 回声 $tmp > 文件.txt;

于 2008-09-23T19:44:42.493 回答
2

这在 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 除外)。

于 2010-03-25T03:56:59.177 回答
1

正如 Lewis Baumstark 所说,它不喜欢您写入相同的文件名。

这是因为 shell 打开“file.txt”并在“cat file.txt”运行之前截断它以进行重定向。所以,你必须

tail -1 file.txt > file2.txt; mv file2.txt file.txt
于 2008-09-23T19:34:23.220 回答
1

仅针对这种情况,可以使用

猫 <文件.txt | (rm file.txt;tail -1 > file.txt)
这将在连接“cat”与“(...)”中的子shell之前打开“file.txt”。“rm file.txt”将在子shell打开它以写入“tail”之前从磁盘中删除引用,但内容仍然可以通过传递给“cat”的打开的描述符获得,直到它关闭stdin。所以你最好确保这个命令会完成,否则“file.txt”的内容将会丢失

于 2010-03-25T05:15:54.063 回答
1
echo "$(tail -1 file.txt)" > file.txt
于 2010-03-25T05:24:30.030 回答
0

似乎不喜欢您将其写回相同文件名的事实。如果您执行以下操作,它将起作用:

$cat file.txt | tail -1 > anotherfile.txt
于 2008-09-23T19:31:41.243 回答
0

tail -1 > file.txt将覆盖您的文件,导致 cat 读取一个空文件,因为重写将在您的管道中的任何命令执行之前发生。

于 2008-09-23T19:34:37.947 回答