问题与Bash输出redirect

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

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

为什么这样?

Solutions Collecting From Web of "问题与Bash输出redirect"

通过管道将文件重定向回相同的文件是不安全的; 如果在tail开始读取第一个阶段之前设置管道的最后一个阶段,shell将shell覆盖file.txt ,那么最终会产生空的输出。

做相反的事情:

 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 
  • -i告诉sed将文件替换到位; 否则,结果会写入STDOUT。
  • $是匹配文件最后一行的地址。
  • d是删除命令。 在这种情况下,它被否定了 ,所以所有符合地址的行都会被删除。

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

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

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

或者使用moreutils的海绵等工具 :

  $ cat file.txt |  tail -1 | 海绵file.txt 

这是因为海绵在打开输出文件之前等待输入流结束。

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

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

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

这就是Unix和shell环境设计的一部分,并且一直回到原来的Bourne shell。 '这是一个功能,而不是一个错误。

tmp = $(tail -1 file.txt); echo $ tmp> file.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)纯粹的外壳(不是dd)。

正如刘易斯·鲍姆斯塔克所说,它不喜欢你写的是相同的文件名。

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

 tail -1 file.txt > file2.txt; mv file2.txt file.txt 
 echo "$(tail -1 file.txt)" > file.txt 

只是为了这种情况下可以使用

  cat <file.txt |  (rm file.txt; tail -1> file.txt) 

这将打开“file.txt”就在“(…)”与subshel​​l连接“cat”之前。 “rm file.txt”会在子shell打开它的“尾部”之前从磁盘上删除引用,但是内容仍然可以通过打开的描述符被传递给“cat”,直到它关闭stdin。 所以你最好确定这个命令将会完成,否则“file.txt”的内容将会丢失

它似乎不喜欢你写回到相同的文件名的事实。 如果你做了以下工作:

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

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