我试图删除除最后一行以外的文件的所有行,但以下命令不起作用,虽然file.txt不为空。
$cat file.txt |tail -1 > file.txt $cat file.txt
为什么这样?
通过管道将文件重定向回相同的文件是不安全的; 如果在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
在'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时,它会执行以下操作:
当“猫”开始阅读时,'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”就在“(…)”与subshell连接“cat”之前。 “rm file.txt”会在子shell打开它的“尾部”之前从磁盘上删除引用,但是内容仍然可以通过打开的描述符被传递给“cat”,直到它关闭stdin。 所以你最好确定这个命令将会完成,否则“file.txt”的内容将会丢失
它似乎不喜欢你写回到相同的文件名的事实。 如果你做了以下工作:
$cat file.txt | tail -1 > anotherfile.txt
tail -1 > file.txt
会覆盖你的文件,导致cat读取一个空文件,因为重写将在你的管道中的任何命令执行之前发生。