Bash输出重定向的问题
我试图删除文件中除最后一行以外的所有行,但是以下命令不起作用,尽管file.txt不为空。
$cat file.txt |tail -1 > file.txt $cat file.txt
为什么会这样呢?
解决方案
我们似乎不喜欢将其写回到相同的文件名。如果我们执行以下操作,它将起作用:
$cat file.txt | tail -1 > anotherfile.txt
从文件通过管道重定向回到同一文件是不安全的;如果在tail
开始读取第一阶段之前设置管道的最后阶段时,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特定的,但是在回声不大的情况下有效-例如,当最后一行包含" --version"时)。
最后,可以使用moreutils中的海绵:
tail -1 file.txt | sponge file.txt
正如Lewis Baumstark所说,我们不喜欢将其写入相同的文件名。
这是因为外壳程序在运行" cat file.txt"之前会打开" file.txt"并将其截断以进行重定向。所以,你必须
tail -1 file.txt > file2.txt; mv file2.txt file.txt
tail -1> file.txt
将覆盖文件,导致cat读取一个空文件,因为在执行管道中的任何命令之前都会进行重写。
在执行" cat"之前,Bash已经打开" file.txt"进行写入,清除了其内容。
通常,不要在同一条语句中写入正在读取的文件。如上所述,可以通过写入其他文件来解决此问题:
$cat file.txt | tail -1 >anotherfile.txt $mv anotherfile.txt file.txt
或者使用moreutils中的海绵之类的实用程序:
$cat file.txt | tail -1 | sponge file.txt
这是可行的,因为海绵等待打开输入文件之前要等它的输入流结束。
当我们将命令字符串提交给bash时,它将执行以下操作:
- 创建一个I / O管道。
- 启动" / usr / bin / tail -1",从管道中读取并写入file.txt。
- 启动" / usr / bin / cat file.txt",写入管道。
到"猫"开始阅读时," file.txt"已被" tail"截断。
这就是Unix和shell环境设计的全部内容,并且一直追溯到原始的Bourne shell。 '这是一个功能,而不是一个错误。
tmp = $(tail -1 file.txt); echo $ tmp> file.txt;
我们可以使用sed删除文件中除最后一行以外的所有行:
sed -i '$!d' file
- -i告诉sed将文件替换到位;否则,结果将写入STDOUT。
- $是与文件最后一行匹配的地址。
- d是删除命令。在这种情况下,它用!取反,因此所有与地址不匹配的行都将被删除。
这在Linux shell中效果很好:
replace_with_filter() { local filename=""; 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 | (rm file.txt; tail -1 > file.txt)
这将在子目录"(...)"中连接" cat"之前打开" file.txt"。 " rm file.txt"将从磁盘删除引用,然后subshell将其打开以写入" tail",但是内容仍可通过打开的描述符获得,该描述符传递给" cat",直到关闭stdin。因此,我们最好确保此命令将完成,否则" file.txt"的内容将丢失
echo "$(tail -1 file.txt)" > file.txt