tar:文件在读取时已更改

109

我正在使用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的警告以防止它停止后续备份?


编辑-2:可能是原因

正如我上面所说的,在旧电脑上bash shell脚本可以正常工作。与旧电脑相比,msys版本不同。tar命令的版本也不同。在旧电脑上,tar是1.13.19,在新电脑上是1.23。我复制了旧的tar命令,但没有复制它的依赖项msys-1.0.dll到新电脑上,并将其重命名为tar_old。我还在shell脚本中更新了tar命令并运行了脚本。然后一切正常。因此,问题似乎是tar命令。我确定在压缩时没有任何文件被更改。这是新版本tar命令的一个错误吗?我不知道。


编辑-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返回不同值时存在一个bug:https://bugzilla.redhat.com/show_bug.cgi?id=1058526 - Arie Skliarouk
在使用 Windows Docker 挂载的卷上使用 tar 时遇到了问题。将 tar 工具替换为 pax 对我有用。 - Andreas
10个回答

107

我也遇到了tar消息“changed as we read it”。对于我来说,这些消息出现在我在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。 - jaskho
22
1 结尾的 tar 扩展名:"如果在创建、追加或更新选项中使用了tar,这个退出代码表示一些文件在被归档时被更改,因此生成的归档文件不包含完全相同的文件集副本。" 这种行为太糟糕了,会破坏管道并且无法停止。 facepalm - Otheus
注意 @Otheus set +e - Ryan Brodie
2
@RyanBrodie 我之前考虑过使用 set -o pipefail; tar ... | gzip 这种方式。但我撤回了这个想法;它不会终止整个管道,因为退出被推迟到执行结束时才会发生。 - Otheus
2
这不是一个警告,而是一个错误。警告不会导致非零的退出代码。 - Hi-Angel
1
@Otheus,它以非零代码退出是有效的事实。在处理过程中更改了文件。通常创建存档时,您需要确保其完整性。如果您有通过管道运行的自动化进程可能会出现问题,您可以将其转换为脚本,并执行适合您情况的任何错误处理。 - Ro Achterberg

97

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

问题是由于在运行命令后创建xyz.tar.gz时,目录.被更改。有两种解决方案:

解决方案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

9
在方案二中,只需要将 --exclude=archive.tar.gz 放在其他选项 -zvcf 之前即可,它能够很好地工作。 - Kaj Kandler
1
它没有起作用,我收到了相同的警告。 - The Human Cat
2
第二种解决方案的关键点是touch archive.tar.gz,如果在运行tar命令时它不存在,那么它将无法工作。 - DarkSuniuM

43

如果您想要调试这样的问题,您需要提供make规则或者至少是您所调用的tar命令。如果没有命令可以查看,我们如何知道命令存在哪些问题?

然而,99%的情况下,像这样的错误意味着您正在尝试将tar文件创建在要放入tar文件的目录内。因此,当tar尝试读取目录时,它会发现tar文件作为目录的成员,开始读取它并将其写入tar文件,因此在它开始读取tar文件和完成读取tar文件之间,tar文件已经发生了变化。

例如,像这样的内容:

tar cf ./foo.tar .

无法“停止”这个过程,因为它本身并没有错误。只需在创建时将您的tar文件放在其他位置,或者找到另一种方法(例如使用--exclude)来忽略tar文件即可。


我在原帖中添加了更多细节,请查看。 - warem
根据这里的信息,我不知道哪里出了问题。 但是,我对在Windows或cygwin上工作知之甚少…… 我确实知道,与基于POSIX的文件系统相比,Windows文件系统在多个程序访问同一文件方面要困难得多。 但这似乎与您的情况没有直接关系。 我所能建议的就是,在您的规则中删除@,检查make打印出的命令是否正确,并查看tar正在尝试创建的文件(v选项的输出),以确保没有任何神秘的东西。 - MadScientist

22
这里有一个一行代码,用于忽略tar的退出状态,如果为1的话。无需像sandeep's script那样设置set +e。如果tar的退出状态为0或1,则此一行代码将以退出状态0返回。否则,它将以退出状态1返回。这与sandeep's script不同,后者会保留原始的退出状态值(如果不等于1)。

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


为什么你不想保留原始的退出状态? - ATLief
我最常见的用例是在Jenkins中使用shell脚本。Jenkins默认使用errexit选项运行脚本,即任何失败的命令都会立即导致脚本退出。当您运行例如系统测试并希望Jenkins作业一直运行到结束并报告所有失败的测试而不是在系统测试失败时退出时,这是一个问题。 - Fabian Ritzmann
1
这只是忽略了返回代码为1,它并没有解决tar压缩在中途失败的问题...当你得到一个1时,通常是tar未能完成,因此其输出归档将不会包含所有源数据,仅有部分数据。 - Scott Stensland

6
为了增强Fabian的一行代码,让我们假设我们只想忽略退出状态 1,但如果退出状态是其他任何值,则保留退出状态。
tar -czf sample.tar.gz dir1 dir2 || ( export ret=$?; [[ $ret -eq 1 ]] || exit "$ret" )

这个命令可以一行完成和Sandeep脚本相同的所有操作。

5

对我来说,只需要使用一个外部目录作为输出位置,问题就解决了。

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

1
如果将新的tar文件放在你正在压缩的同一文件夹中,它会改变lol。 - Daniel W.
看起来它正在父文件夹中创建tar文件。 - Jeremy

1

tar 的退出代码是受限制的,因此您不会得到太多信息。

您可以假设 ec=1 是安全忽略的,但它可能会出错 - 例如在其他帖子中的 gzip 示例(来自外部程序的退出代码)。

文件更改为我们读取它 错误 / 警告的原因可能各不相同。

  • 目录内的日志文件。
  • 尝试备份相同目录中的 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

0

我不确定这是否适合您,但我注意到在管道模式下,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

2
这是因为默认情况下,管道内部会忽略退出代码。启用它们的最佳实践是使用“set -o pipefail”。 - Sergey

0

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

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

-1

答案非常简单:在同一目录中“打包”tar文件时不要保存它。

只需执行:tar -cvzf resources/docker/php/php.tar.gz .

最终,

它将打包当前目录并将其保存到另一个目录中。

这很容易,小伙子们


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接