Tar: 文件在我们读取时发生了变化

在执行 makefile 时,tar 命令显示 file changed as we read it,

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

我如何停止焦油的警告,以停止我的备份后,警告?


编辑 -2 : 这可能是原因

如前所述,bash shell 脚本在我的旧计算机上运行良好。与老式计算机相比,msys版本有所不同。Tar 命令的版本也是如此。在旧计算机中,tar 是1.13.19,在新计算机中是1.23。我复制了旧的 tar 命令,而没有将其依赖项 msys-1.0.dll 复制到新计算机,并将其重命名为 tar _ old。我还更新了 shell 脚本中的 tar 命令并运行了该脚本。那就没事了。因此,问题似乎出在 tar 命令上。我确信在打孔时没有任何文件被更改。这是新版本中 tar 命令的 bug 吗?我不知道。


编辑 -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 中运行脚本。从 tar 命令中排除 ~/MS_bak_MSYS。因此,我创建的 tar 文件不在我试图放入 tar 文件的目录中。这就是为什么我觉得这个警告很奇怪。

151472 次浏览

If you want help debugging a problem like this you need to provide the make rule or at least the tar command you invoked. How can we see what's wrong with the command if there's no command to see?

However, 99% of the time an error like this means that you're creating the tar file inside a directory that you're trying to put into the tar file. So, when tar tries to read the directory it finds the tar file as a member of the directory, starts to read it and write it out to the tar file, and so between the time it starts to read the tar file and when it finishes reading the tar file, the tar file has changed.

So for example something like:

tar cf ./foo.tar .

There's no way to "stop" this, because it's not wrong. Just put your tar file somewhere else when you create it, or find another way (using --exclude or whatever) to omit the tar file.

I also encounter the tar messages "changed as we read it". For me these message occurred when I was making tar file of Linux file system in bitbake build environment. This error was sporadic.

For me this was not due to creating tar file from the same directory. I am assuming there is actually some file overwritten or changed during tar file creation.

The message is a warning and it still creates the tar file. We can still suppress these warning message by setting option

--warning=no-file-changed

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

Still the exit code return by the tar is "1" in warning message case: http://www.gnu.org/software/tar/manual/html_section/Synopsis.html

So if we are calling the tar file from some function in scripts, we can handle the exit code something like this:

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


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

Here is a one-liner for ignoring the tar exit status if it is 1. There is no need to set +e as in sandeep's script. If the tar exit status is 0 or 1, this one-liner will return with exit status 0. Otherwise it will return with exit status 1. This is different from sandeep's script where the original exit status value is preserved if it is different from 1.

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

Although its very late but I recently had the same issue.

Issue is because dir . is changing as xyz.tar.gz is created after running the command. There are two solutions:

Solution 1: tar will not mind if the archive is created in any directory inside .. There can be reasons why can't create the archive outside the work space. Worked around it by creating a temporary directory for putting the archive as:

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

Solution 2: This one I like. create the archive file before running tar:

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

To enhance Fabian's one-liner; let us say that we want to ignore only exit status 1 but to preserve the exit status if it is anything else:

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

This does everything sandeep's script does, on one line.

Simply using an outer directory for the output, solved the problem for me.

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

It worked for me by adding a simple sleep timeout of 20 sec. This might happen if your source directory is still writing. Hence put a sleep so that the backup would finish and then tar should work fine. This also helped me in getting the right exit status.

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

I am not sure does it suit you but I noticed that tar does not fail on changed/deleted files in pipe mode. See what I mean.

Test script:

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

Deleting random files manually...

Output:

+ 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

Exit codes for tar are restricted, so you don't get to much information. You can assume that ec=1 is safe to ignore, but it might trip - i.e. the gzip-example in other posts (exit code from external programs).

The reason for the file changed as we read it error/warning can be varying.

  • A log file inside the directory.
  • Writing to a tar file in the same directory you are trying to back up.
  • etc.

Possible workarounds can involve:

  • exclude known files (log files, tar-files, etc)
  • ensure log files are written to other directories

This can be quite involved, so you might want to still just run the tar command and preferably safely ignore some errors / warnings.

To do this you will have to:

  • Save the tar output.
  • Save the exit code
  • Check the output against known warnings and errors, not unlike tar's own ignore.
  • Conditionally pass another exit code to the next program in the pipe.

In OP's case this would have to be wrapped in a script and run as 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

Answer should be very simple: Don't save your tar file while "Taring" it in the same directory.

Just do: tar -cvzf resources/docker/php/php.tar.gz .

Eventually,

it will tar the current directory and save it to another directory.

That's easy peasy, lemon squeezy fellas