为什么 cat 会改变二进制文件的内容?

Why does cat change the contents of binary files?

显然,运行 cat 对二进制文件和回显内容似乎不起作用。这是我制作的简单脚本:

#!/bin/sh

CONTENTS=$(cat "")
mv "" ""
echo "$CONTENTS" > ""

出于某种原因,当我这样做时,这似乎搞砸了:

script first.pptx second.pptx

在 运行 之后,新的 first.pptx 文件可以正常打开,但是新的 second.pptx 文件可能是无效格式或其他格式。

为什么会发生这种情况,我该如何解决?

构造 $(cat "") 将在替换值之前去除文件中所有尾随的换行符(所以这就是 CONTENTS 最终的结果)。

echo "$CONTENTS" 将在第一个 NUL 字符处截断内容,并附加一个换行符。

因此,如果文件没有恰好以一个换行符结尾,则内容会略有变化。如果它有任何 NUL 字符,它将被截断。

这里证明 cat 不会 改变值的内容,并且你试图实现的脚本虽然没有实用价值,但实际上 可以写成bash:

#!/bin/bash

declare -a arr=( )
{
    while IFS= read -r -d '' s; do
        arr+=( "$s" )
    done
    arr+=( "$s" )
} < <(cat "")  ## aside: this would be more efficiently just <"" without the cat

mv "" ""

{
    printf '%s[=10=]' "${arr[@]:0:${#arr[@]}-1}"
    printf '%s' "${arr[@]:${#arr[@]}-1}"
} >""

现在,这是如何工作的?

  • arr是一个shell数组;每个元素都是一个 C 字符串。
  • while IFS= read -r -d '' s 将输入文件中的 NUL-delimited 字符串增量读取到 s。只有 returns 为真,而这些字符串为 NUL-terminated;当一个字符串没有 NUL 终止符时,变量 s 仍然被填充,但是 read 命令 returns 为假。 [有关 while read 成语的更多信息,请参阅 BashFAQ #001
  • ...因此,数组的最后一个元素包含 最终 NUL 之后的内容。
  • printf '%s[=18=]' ... 在格式字符串(在 ... 区域)之后发出每个参数,后跟一个 NUL 定界符。 ${#arr[@]} 扩展为数组 arr 中的条目数,并且 ${arrayname[@]:SEEK:COUNT} 在跳过第一个 [=25= 之后扩展为数组 arrayname 中的 COUNT 项] 项目;因此,${arr[@]:0:${#arr[@]}-1} 扩展到数组 arr 中除最后一项以外的所有项,打印这些每个后跟一个 NUL。
  • 最终的 printf 在最后的 NUL 之后发出尾随的内容——从最后一个数组条目开始。

如果 运行 这样,您将观察到输入文件的 md5sums 交换,即使它们是包含 NUL 值的二进制文件。因此,你的问题前提是错误的:cat 改变二进制文件的内容。

尝试使用临时文件执行相同的操作。

cat < $file1 > $tempFile
cat < $file2 > $file1
cat < $tempFile > $file2
rm $tempFile