【问题标题】:tar: file changed as we read ittar:文件在我们读取时发生了变化
【发布时间】:2013-12-17 14:29:13
【问题描述】:

我正在使用maketar 进行备份。执行 makefile 时,tar 命令显示file changed as we read it。在这种情况下,

  • 出现警告时 tar 包正常
  • 但它会停止以下备份的 tar 命令
  • 显示警告的文件实际上并没有改变——出现警告真的很奇怪
  • 显示警告的文件是随机出现的,我的意思是,每次我运行我的 makefile 时,显示警告的文件都不同
  • --ignore-failed-read 没有帮助。我在 MinGW 中使用 tar 1.23
  • 我刚把我的电脑换成WIN7 64位。该脚本在旧的 WIN7 32 位中运行良好。但 tar 版本不如 1.23 新。

如何停止 tar 的警告以在警告后停止备份?


Edit-2:这可能是原因

如上所述,bash shell 脚本在我的旧计算机上运行良好。与旧电脑相比,msys 版本不同。 tar 命令的版本也是如此。在旧计算机中,tar 为 1.13.19,在新计算机中为 1.23。我复制了旧的 tar 命令而不将其依赖项 msys-1.0.dll 复制到新计算机并将其重命名为 tar_old。而且我还更新了 shell 脚本中的 tar 命令并运行该脚本。然后一切正常。所以,问题似乎出在 tar 命令上。我确信去皮重时没有任何文件更改。这是新版本中 tar 命令的错误吗?我不知道。


Edit-1:添加更多细节

备份由 bash shell 脚本调用。它扫描目标目录并构建 makefile 然后调用 make 以使用 tar 命令进行备份。下面是一个典型的由 bash shell 脚本构建的 makefile。

#--------------------------------------------
# backup VC
#--------------------------------------------
# the program for packing
PACK_TOOL=tar

# the option for packing tool
PACK_OPTION=cjvf

# M$: C driver
WIN_C_DIR=c:

# M$: D driver
WIN_D_DIR=d:

# M$: where the software is
WIN_PRG_DIR=wuyu/tools
# WIN_PRG_DIR=

# where to save the backup files
BAKDIR=/home/Wu.Y/MS_bak_MSYS

VC_FRAMEWORK=/home/Wu.Y/MS_bak_MSYS/tools/VC/VC_framework.tar.bz2
VC_2010=/home/Wu.Y/MS_bak_MSYS/tools/VC/VC_2010.tar.bz2

.PHONY: all

all: $(VC_FRAMEWORK) $(VC_2010)

$(VC_FRAMEWORK): $(WIN_C_DIR)/$(WIN_PRG_DIR)/VC/Framework/*
    @$(PACK_TOOL) $(PACK_OPTION) "$@" --ignore-failed-read /c/$(WIN_PRG_DIR)/VC/Framework
$(VC_2010): $(WIN_C_DIR)/$(WIN_PRG_DIR)/VC/VS2010/*
    @$(PACK_TOOL) $(PACK_OPTION) "$@" --ignore-failed-read /c/$(WIN_PRG_DIR)/VC/VS2010

如您所见,tar 包存储在 ~/MS_bak_MSYS/tools/VC/VC_2010.tar.bz2 中。我在 ~/qqaa 中运行脚本。 ~/MS_bak_MSYS 被排除在 tar 命令之外。因此,我正在创建的 tar 文件不在我试图放入 tar 文件的目录中。这就是为什么我对出现警告感到奇怪的原因。

【问题讨论】:

  • 看起来您正在使用 Windows 设置,因此与您无关。然而,当底层文件系统是 glusterfs 时,我们也会遇到类似的问题。当 lstat 和 fstat 返回不同的值时,似乎存在错误:bugzilla.redhat.com/show_bug.cgi?id=1058526
  • 在 windows docker 挂载的卷上使用 tar 时遇到了这个问题。将tar 实用程序替换为pax 对我有用。

标签: makefile tar


【解决方案1】:

我还遇到了 tar 消息“在我们阅读时已更改”。对我来说,这些消息发生在我在 bitbake 构建环境中制作 Linux 文件系统的 tar 文件时。这个错误是偶发的。

对我来说,这不是因为从同一目录创建 tar 文件。我假设在 tar 文件创建过程中实际上有一些文件被覆盖或更改。

该消息是一个警告,它仍然会创建 tar 文件。我们仍然可以通过设置选项来抑制这些警告消息

--warning=no-file-changed

(http://www.gnu.org/software/tar/manual/html_section/warnings.html )

在警告消息情况下,tar 返回的退出代码仍然为“1”: http://www.gnu.org/software/tar/manual/html_section/Synopsis.html

因此,如果我们从脚本中的某个函数调用 tar 文件,我们可以像这样处理退出代码:

set +e 
tar -czf sample.tar.gz dir1 dir2
exitcode=$?

if [ "$exitcode" != "1" ] && [ "$exitcode" != "0" ]; then
    exit $exitcode
fi
set -e

【讨论】:

  • 我有同样的问题,这个答案通过让我能够解决它来“解决”我的问题。谢谢@sandeep。
  • Tar exts with 1: "如果 tar 给出了 `--create'、`--append' 或 `--update' 选项,此退出代码意味着某些文件在存档,因此生成的存档不包含文件集的确切副本。”这是一种令人瞠目结舌的不良行为——它会杀死一个管道并且没有办法阻止它。 掌心
  • 注意@Otheus set +e
  • @RyanBrodie 我是按照set -o pipefail; tar ... | gzip 的思路思考的。但我收回了;它不会杀死整个管道,因为退出被推迟到执行结束。
【解决方案2】:

虽然它很晚,但我最近遇到了同样的问题。

问题是因为在运行命令后创建 xyz.tar.gz 时,dir . 正在发生变化。有两种解决方案:

解决方案 1: tar 不会介意存档是在. 内的任何目录中创建的。无法在工作空间之外创建存档可能是有原因的。通过创建一个临时目录来解决它,将存档放置为:

mkdir artefacts
tar -zcvf artefacts/archive.tar.gz --exclude=./artefacts .
echo $?
0

解决方案 2: 这个我喜欢。在运行 tar 之前创建归档文件:

touch archive.tar.gz
tar --exclude=archive.tar.gz -zcvf archive.tar.gz .
echo $?
0

【讨论】:

  • 在解决方案 2 中,只需将 --exclude=archive.tar.gz 放在另一个选项 -zvcf 之前,它实际上工作得很好。
【解决方案3】:

如果您需要帮助调试此类问题,您需要提供 make 规则或至少提供您调用的 tar 命令。如果没有命令可以查看,我们如何查看命令有什么问题?

但是,在 99% 的情况下,此类错误意味着您在尝试放入 tar 文件的目录中创建 tar 文件。因此,当 tar 尝试读取目录时,它发现 tar 文件是目录的成员,开始读取它并将其写入 tar 文件,因此在它开始读取 tar 文件和完成之间读取 tar 文件,tar 文件发生了变化。

例如:

tar cf ./foo.tar .

没有办法“阻止”它,因为它没有错。只需在创建 tar 文件时将其放在其他位置,或找到另一种方式(使用 --exclude 或其他方式)来省略 tar 文件。

【讨论】:

  • 我在原帖中添加了更多细节。请检查。
  • 根据这里的信息,我不知道出了什么问题。但是,我对使用 Windows 或 cygwin 知之甚少……我知道 Windows 文件系统在使用 WRT 多个程序访问同一文件时比基于 POSIX 的文件系统更困难。但这似乎与您的情况没有直接关系。我所能建议的就是删除规则中的 @ 并检查 make 正在打印的命令以确保它是正确的,并查看 tar 试图创建的文件(来自 v 选项的输出)以确保没有什么神秘的。
【解决方案4】:

如果 tar 退出状态为 1,则这里是忽略 tar 退出状态的单行代码。不需要像 sandeep's script 中的 set +e。如果 tar 退出状态为 0 或 1,则此单行程序将以退出状态 0 返回。否则,它将以退出状态 1 返回。这与 sandeep's script 不同,如果与 sandeep's script 不同,则保留原始退出状态值1.

tar -czf sample.tar.gz dir1 dir2 || [[ $? -eq 1 ]]

【讨论】:

  • 为什么不希望保留原始退出状态?
  • 我最常见的用例是 Jenkins 中的 shell 脚本。 Jenkins 默认运行带有errexit 选项集的脚本,即任何失败的命令都会导致脚本立即退出。当你运行时这是一个问题,例如系统测试,并希望 Jenkins 作业运行到最后并报告所有失败的测试,而不是在系统测试失败时退出。
【解决方案5】:

加强Fabian的单线;假设我们只想忽略退出状态 1,但如果是其他状态,则保留退出状态:

tar -czf sample.tar.gz dir1 dir2 || ( export ret=$?; [[ $ret -eq 1 ]] || exit "$ret" )

这在一行中完成了 Sandeep 脚本所做的所有事情。

【讨论】:

    【解决方案6】:

    只需使用外部目录作为输出,就为我解决了问题。

    sudo tar czf ./../31OCT18.tar.gz ./
    

    【讨论】:

    • 如果将新的 tar 放在您正在压缩的文件夹中,它会改变 lol
    • 看起来它正在父文件夹中创建 tar
    【解决方案7】:

    tar 的退出代码受到限制,因此您无法获得太多信息。 您可以假设 ec=1 可以安全忽略,但它可能会出错 - 即其他帖子中的 gzip- 示例(外部程序的退出代码)。

    file changed as we read it 错误/警告的原因可能会有所不同。

    • 目录中的日志文件
    • 正在写入您尝试备份的同一目录中的 tar 文件。

    可能的解决方法可能涉及:

    • 排除已知文件(日志文件、tar文件等)
    • 确保将日志文件写入其他目录

    这可能涉及很多,因此您可能仍想只运行 tar 命令,最好安全地忽略一些错误/警告。

    为此,您必须:

    • 保存tar 输出。
    • 保存退出代码
    • 根据已知警告和错误检查输出,与tar 自己的忽略不同。
    • 有条件地将另一个退出代码传递给管道中的下一个程序。

    在 OP 的情况下,这必须包含在脚本中并以 PACK_TOOL 运行。

    # List of errors and warnings from "tar" which we will safely ignore.
    # Adapt to your findings and needs
    IGNORE_ERROR="^tar:.*(Removing leading|socket ignored|file changed as we read it)"
    
    # Save stderr from "tar"
    RET=$(tar zcf $BACKUP --exclude Cache --exclude output.log --exclude "*cron*sysout*" $DIR 2>&1)
    EC=$?  # Save "tar's" exit code
    echo "$RET"
    if [ $EC -ne 0 ]
    then
      # Check the RET output, remove (grep -v) any errors / warning you wish to ignore
      REAL_ERRORS=$(echo "$RET" | grep "^tar: " | grep -Ev "${IGNORE_ERROR:?}")
      # If there is any output left you actually got an error to check
      if [ -n "$REAL_ERRORS" ]
      then
          echo "ERROR during backup of ${DIR:?} to ${BACKUP:?}"
      else
          echo "OK backup of (warnings ignored) ${DIR:?}"
          EC=0
      fi
    else
      echo "OK backup of ${DIR:?}"
    fi
    

    【讨论】:

      【解决方案8】:

      它通过添加 20 秒的简单睡眠超时对我有用。 如果您的源目录仍在写入,则可能会发生这种情况。因此,休眠以便备份完成,然后 tar 应该可以正常工作。这也帮助我获得了正确的退出状态。

      sleep 20
      tar -czf ${DB}.${DATE}.tgz ./${DB}.${DATE}
      

      【讨论】:

        【解决方案9】:

        我不确定它是否适合您,但我注意到tar 在管道模式下更改/删除的文件不会失败。明白我的意思。

        测试脚本:

        #!/usr/bin/env bash
        set -ex
        tar cpf - ./files | aws s3 cp - s3://my-bucket/files.tar
        echo $?
        

        手动删除随机文件...

        输出:

        + aws s3 cp - s3://my-bucket/files.tar
        + tar cpf - ./files
        tar: ./files/default_images: File removed before we read it
        tar: ./files: file changed as we read it
        + echo 0
        0
        

        【讨论】:

        • 这是因为默认情况下管道内部会忽略退出代码。通过“set -o pipefail”启用它们是一个很好的做法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-25
        • 2017-01-08
        • 2019-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多