Bash输出重定向的问题

时间:2020-03-06 14:37:17  来源:igfitidea点击:

我试图删除文件中除最后一行以外的所有行,但是以下命令不起作用,尽管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"将从磁盘删除引用,然后subshel​​l将其打开以写入" tail",但是内容仍可通过打开的描述符获得,该描述符传递给" cat",直到关闭stdin。因此,我们最好确保此命令将完成,否则" file.txt"的内容将丢失

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