如何过滤tar文件的内容,在管道中生成另一个tar文件?


13

考虑一个来自外部系统的tar文件,该文件包含一些目录,这些目录具有我想要保留的各种属性,例如权限,mtimes等。如何以常规用户(不是root用户)的身份轻松获取这些文件的子集?

寻找类似的东西:

tar -f some.tar.gz --subset subdir/ | ssh remote@system tar xvz

保留此tar归档文件中的主要属性(所有权,组,模式,mtime)也很重要。tar文件中的其他属性(例如扩展标头关键字)呢?

如果此子目录包含大文件,则避免使用临时目录的解决方案有很多好处。

Answers:


14

bsdtar(基于libarchive)可以将tar(和其他一些归档文件)从stdin过滤到stdout。例如,它只能传递与模式匹配文件名,并且可以s/old/new/重命名。它已经打包用于大多数发行版,例如bsdtar在Ubuntu中。

sudo apt-get install bsdtar   # or aptitude, if you have it.

# example from the man page:
bsdtar -c -f new.tar --include='*foo*' @old.tgz
#create new.tar containing only entries from old.tgz containing the string ‘foo’
bsdtar -czf - --include='*foo*' @-  # filter stdin to stdout, with gzip compression of output.

请注意,输入/输出有多种压缩格式供选择,因此您不必自己手动通过gunzip / lz4进行管道传输。您可以将-stdin与@tarfile语法一起使用,和/或-将stdout像平常一样使用。


我的搜索还找到了此流式tar修改工具,该工具似乎希望您使用javascript定义所需的存档更改。(我认为整个事情都是用js编写的)。

https://github.com/mafintosh/tar-stream


1
太好了,不知道@original.tar使用bsdtar可以实现这种方法。似乎也可以使用扩展属性和压缩,</var/cache/pacman/pkg/libuv-1.7.0-1-x86_64.pkg.tar.xz bsdtar -czf - --include='usr/share/*' @- | tar tvz(由于某种原因,空选择会产生一系列零字节,但这对我来说不是主要问题)。
Lekensteyn 2015年

1
根据我的测试,s/old/new/ 不适用于使用@ old.tgz的旧档案文件,仅适用于实际文件,直接从文件系统进行归档。真的很遗憾,因为这对我来说是最有用的用例。
巴特

4

最简单的方法是复制整个档案。我认为您不想这样做,因为它太大了。

常用的命令行工具(tarpax)不支持复制存档的成员到另一个存档。

如果您不需要保留所有权,建议您使用FUSE文件系统。您可以使用archivemount将归档文件挂载为文件系统。对源归档执行此操作,然后在已挂载的文件系统上运行tar。

archivemount some.tar.gz mnt
cd mnt
tar -cz subdir | ssh example.com tar -xz
fusermount -u mnt

另外,您可以使用AVFS

mountavfs
cd ~/.avfs$PWD/some.tar.gz\#
tar -cz subdir | ssh example.com tar -xz

或者,您可以tar在原始归档文件上运行并通过SSHFS提取到远程计算机。

sshfs example.com: mnt
cd mnt
tar -xf /path/to/some.tar.gz subdir
fusermount -u mnt

但是,如果您需要保留所有权,那么所有这些方法都很麻烦。它们都涉及提取到本地计算机上的文件,因此该文件的所有权必须是预期的远程所有权。这需要以root用户身份运行,并且如果文件由名称或ID在本地计算机和远程主机之间不同的帐户拥有,则可能无法获得预期的结果。

Python的tarfile库提供了一种操作tar成员的相当简单的方法,因此您可以将它们从一个tar文件拖曳到另一个tar文件。它支持POSIX标准格式(ustar,pax)以及某些GNU扩展。这是未经测试的Python脚本,该脚本在其标准输入上读取tar文件(可能使用gzip或bzip2压缩),并在其标准输出上写入用bzip2压缩的tar文件。如果源成员以传递给脚本的参数开头,则将对其进行复制。

#!/usr/bin/env python2
import sys, tarfile
source = tarfile.open(fileobj=sys.stdin)
destination = tarfile.open(fileobj=sys.stdout, mode='w:bz2')
for info in source:
    if info.name.startswith(sys.argv[1]):
        destination.addfile(info)
destination.close()

被调用为

tar_filter <some.tar.gz subdir/ | ssh example.com tar -xj

1
bsdtar(基于libarchive)可以动态过滤tar存档,请参阅我的答案。
彼得·科德斯

任务是从固件映像中提取数据,因此所有权/组成员身份确实很重要。python方法可以工作。
Lekensteyn

0

另一种无特权的方法是使用该fakeroot程序假装允许您更改所有权。当其他tar属性丢失时,它会保留模式,mtime和uid / gid。这些命令创建一个临时目录,提取文件的子集,最后创建一个新的存档:

mkdir tmp
<some.tar.gz \
fakeroot -- sh -c 'cd tmp && tar -xzf- subdir/ && tar -czf- subdir' |
   ssh remote@system tar -xzvf-
rm -rf tmp

0

GNU tar确实有一个--delete选择:

$ tar -c a b c | tar --delete a | tar -t
b
c

这样,您可以通过指定包含在输出中的内容来获取输入tar的子集。

不幸的是我无法使用该--exclude选项--delete,因此看来您首先需要获取-t要删除的显式列表(),然后将其传递给另一个调用tar

$ tar --delete --no-recursion `tar -t --exclude subdir <some.tar` <some.tar | ssh ...

或者,如果列表太长或太复杂,也可以将其存储在外部文件中:

$ tar -t --exclude subdir <some.tar >to_delete.lst
$ tar --delete --no-recursion -T to_delete.lst <some.tar | ssh ...

-1

据我所知,该tar命令不能使用tar格式作为输入和输出。您将必须以某种方式在本地解压缩文件,然后再次使用tar即时创建tarfile,如下所示(-意味着使用标准输入/输出而不是文件):

tar cf - subdir/ | ssh remote@system 'cd extractdir && tar xvf -'

请注意,tar能够直接在另一个tarfile中提取tarfile是一个有趣的主意...


没有root,这将丢失我明确想要保留的所有所有权/组信息。
Lekensteyn

1
您应该编辑问题以包括您对主机没有超级用户访问权限。
Uriel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.