就地提取 tar 存档

就地提取 tar 存档

我这里有一点困惑...

我需要将大约 70 GB 的文件从一台服务器移动至另一台服务器,因此我决定将它们打包并发送存档,这是最快的方法。

然而,接收服务器在接收到 tar 存档后只剩下 5 GB 的空间。

有什么方法可以“就地”提取 tar?提取后我不需要保留存档,所以我想知道是否可以这样做。

编辑:需要注意的是,档案已经发送,我想避免通过其他方法重新发送。

答案1

% tar czf - stuff_to_backup | ssh backupmachine tar xvzf -

其翻译为:

  • 将“stuff_to_backup”压缩到 stdout
  • 通过 ssh 登录“backupmachine”
  • 在“backupmachine”上运行“tar”,并解压来自标准输入的内容

我个人会使用“rsync over ssh”来传输内容,因为如果连接中断,您可以继续传输内容:

% rsync -ar --progress -e 'ssh' 'stuff_to_backup' user@backupmachine:/backup/

这会将“stuff_to_backup”中的所有内容传输到“backupmachine”上的“backup”文件夹。如果连接中断,只需重复该命令。如果“stuff_to_backup”中的某些文件发生变化,请重复该操作,只有差异部分会被传输。

答案2

如果另一台机器有 ssh,我建议您使用 rsync 作为另一种不使用 tar 文件的替代方案:

rsync -avPz /some/dir/ user@machine:/some/other/dir/

并小心领先/

编辑更新

好吧,我知道如果您无法删除它并重新开始使用 rsync,那么现在这是一个很大的麻烦。我可能会尝试从 tar 中进行选择性提取和删除。

选择性萃取:

$ tar xvf googlecl-0.9.7.tar googlecl-0.9.7/README.txt
googlecl-0.9.7/README.txt

选择性删除:

$ tar --delete --file=googlecl-0.9.7.tar googlecl-0.9.7/README.txt

但是,看起来您将花费大量时间为此编写脚本......

答案3

基本上,您需要的是将文件导入 tar,然后在进行过程中“砍掉”前面的部分。

在 StackOverflow 上,有人问如何截断文件前端,但这似乎是不可能的。你仍然可以用一种特殊的方式用零填充文件的开头,这样文件就变成了稀疏文件,但我不知道该怎么做。不过,我们可以截断文件的末尾。但 tar 需要向前读取档案,而不是向后读取。

解决方案 1

间接层可以解决所有问题。首先就地反转文件,然后向后读取(这将导致向前读取原始文件)并在读取过程中截断反转文件的末尾。

您需要编写一个程序(c、python 等)来逐块交换文件的开头和结尾,然后将这些块通过管道传输到 tar,同时逐块截断文件。这是解决方案 2 的基础,可能更容易实现。

解决方案 2

另一种方法是将文件就地分割成小块,然后在提取时删除这些块。下面的代码块大小为 1 兆字节,请根据需要进行调整。块越大速度越快,但在拆分和提取过程中会占用更多中间空间。

拆分文件 archive.tar:

archive="archive.tar"
chunkprefix="chunk_"
# 1-Mb chunks :
chunksize=1048576

totalsize=$(wc -c "$archive" | cut -d ' ' -f 1)
currentchunk=$(((totalsize-1)/chunksize))
while [ $currentchunk -ge 0 ]; do
    # Print current chunk number, so we know it is still running.
    echo -n "$currentchunk "
    offset=$((currentchunk*chunksize))
    # Copy end of $archive to new file
    tail -c +$((offset+1)) "$archive" > "$chunkprefix$currentchunk"
    # Chop end of $archive
    truncate -s $offset "$archive"
    currentchunk=$((currentchunk-1))
done

将这些文件导入 tar (注意,我们需要第二个终端中的 chunkprefix 变量):

mkfifo fifo
# In one terminal :
(while true; do cat fifo; done) | tar -xf -
# In another terminal :
chunkprefix="chunk_"
currentchunk=0
while [ -e "$chunkprefix$currentchunk" ]; do
    cat "$chunkprefix$currentchunk" && rm -f "$chunkprefix$currentchunk"
    currentchunk=$((currentchunk+1))
done > fifo
# When second terminal has finished :
# flush caches to disk :
sync
# wait 5 minutes so we're sure tar has consumed everything from the fifo.
sleep 300
rm fifo
# And kill (ctrl-C) the tar command in the other terminal.

由于我们使用命名管道 ( mkfifo fifo),因此您不必一次管道所有块。如果空间真的很紧张,这会很有用。您可以按照以下步骤操作:

  • 将最后的 10Gb 块移动到另一个磁盘,
  • 从你仍然拥有的块开始提取,
  • while [ -e … ]; do cat "$chunk…; done循环结束时(第二个终端):
  • 不要停止tar命令,不要删除 fifo(第一个终端),但sync为了以防万一,你可以运行
  • 将一些您知道已完成的提取文件(tar 不会停滞等待数据完成提取这些文件)移动到另一个磁盘,
  • 将剩余的块移回,
  • 通过再次运行线路来恢复提取while [ -e … ]; do cat "$chunk…; done

当然这就是全部上伏尔吉,你需要先在虚拟存档上检查一切是否正常,因为如果你犯了错误,那么数据就再见了

您永远不会知道第一个终端(tar)是否实际上已完成对 fifo 内容的处理,因此,如果您愿意,您可以运行它,但您将无法无缝地与另一个磁盘交换块:

chunkprefix="chunk_"
currentchunk=0
while [ -e "$chunkprefix$currentchunk" ]; do
    cat "$chunkprefix$currentchunk" && rm -f "$chunkprefix$currentchunk"
    currentchunk=$((currentchunk+1))
done | tar -xf -

免责声明

请注意,要使所有这些工作正常进行,您的 shell、tail 和 truncate 必须能够正确处理 64 位整数(为此,您不需要 64 位计算机或操作系统)。我的计算机可以,但如果您在没有这些要求的系统上运行上述脚本,你将丢失 archive.tar 中的所有数据

并且,无论发生什么其他情况,您都会丢失 archive.tar 中的所有数据,因此请确保您已备份数据。

答案4

我将一个 3TB 的 tar 文件传输到一个 4TB 的远程驱动器,这花了 24 小时,所以我也不想重复这个过程。这是我最终使用的脚本。它创建了一个 tar 文件列表,并以相反的顺序提取它们。

希望 tar 每次删除文件时都会“类似”地截断 tar 文件,而不是覆盖它。不幸的是,情况似乎并非如此,提取/删除循环非常慢。

某些人可能仍然会发现这个脚本对于较小的文件(几 GB)很有用。

#!/usr/bin/env bash

set -e
set -u

if [ "$#" -ne 1 ]; then
  echo "Usage $0 <path-to.tar>"
  exit 1
fi

TAR_FILE_PATH="$1"
TAR_FILE_CONTENTS_PATH=$( mktemp )

echo "Using temporary file: '$TAR_FILE_CONTENTS_PATH'"

if [ ! -f "$TAR_FILE_PATH" ]; then
  echo "File doesn't exist '$TAR_FILE_PATH'"
  exit 1
fi

# Create list of files in the tar file. Reverse the line order
tar --list -f "$TAR_FILE_PATH" | tac > "$TAR_FILE_CONTENTS_PATH"

# Go over each file in "$TAR_FILE_CONTENTS_PATH" and first extract it, then delete it from the archive
while IFS= read -r THE_FILE; do
  echo "Extracting '$THE_FILE'"
  tar --extract -f "$TAR_FILE_PATH" "$THE_FILE"
  tar --delete -f "$TAR_FILE_PATH" "$THE_FILE"
done < "$TAR_FILE_CONTENTS_PATH"

rm "$TAR_FILE_CONTENTS_PATH"

相关内容