【问题标题】:Deleting empty files in tar.gz file in bash在bash中删除tar.gz文件中的空文件
【发布时间】:2021-12-05 07:13:58
【问题描述】:

我有一个 tar.gz 文件,它包含 .yang 文件以及一些空的 .yang 文件。 所以我想进入 tar.gz 文件并只删除那些空文件 目前我正在使用:

for f in *.tar.gz
 do
    echo "Processing file $f"
    gzip -d "$f"
    find $PWD -size  0 -print -delete
    gzip -9 "${f%.*}"
    echo "******************************************"
done

但这不起作用可能是因为目前,我不在目录中,而是在 tar.gz 文件中。

还有其他方法吗?

【问题讨论】:

  • 为什么要标记两个截然不同的外壳? Bash 通常在 Linux 上,而 Powershell 通常在 Windows 上。
  • 你永远不会进入 tar 文件。要操作 tar 文件的内容,请将其解压缩,然后使用 tar --delete 从存档中删除内容。完成后,再次拉上拉链。参见 man tar。并且,请从您的问题中删除 powershell 标记。
  • 还有,只是好奇:gzip -9 应该做什么?
  • @user1934428 gzip -9 以最高压缩级别进行压缩。在这种情况下,它会重新 gzip 压缩包。如果find 命令正在执行 OP 想要的操作,那么所有这些都可能会起作用。所以主要问题是这个find 命令不会抑制压缩包中的空文件。
  • @RenaudPacalet:感谢您的解释。顺便说一句,无论如何,我在find 命令中看不到一点。解压后,我们只剩下一个 tar-ball。存档中的文件永远不会被提取。 IMO 应在 tar 中完成对空文件的搜索。如果我们有 GNU tar,这也许可以使用 --to-command 来完成,它设置环境变量 TAR_REALNAME 和 TAR_SIZE....

标签: bash powershell shell tar.gz


【解决方案1】:

您的 find 命令对您的 tarball 没有任何用处,因为它在当前目录中搜索和删除,而不是在 tarball 中。

所以我们需要先解压压缩包(tar -xf),删除空文件(find),然后重新打包(tar -czf)。作为一项安全措施,我们将在临时目录 (mktemp -d) 中工作并创建新的 tarball (*.tar.gz.new) 而不是覆盖旧的。由于您只想删除yang 空文件,我们还将使用更多查找选项。以下是针对 GNU tar 的,适配自己的 tar 版本(或者安装 GNU tar)。在使用它之前阅读接下来会发生什么,以防万一......

for f in *.tar.gz; do
    echo "Processing file $f"
    d="$(mktemp -d)"
    tar -xf "$f" -C "$d"
    find "$d" -type f -name '*.yang' -size 0 -print -delete
    tar -C "$d" -czf "$f.new" .
    rm -rf "$d"
    echo "******************************************"
done

但是您想要的比看起来更复杂,因为您的 tarball 可能包含您不允许使用的元数据(所有者、权限...)的文件。如果您以普通用户身份运行之前的内容, tar 将默默地更改此类文件和目录的所有权和权限。因此,在重新打包时,它们将具有修改的元数据。如果这是一个问题,并且您绝对想保留元数据,则基本上有两种选择:

  1. 使用fakeroot 或等效项假装您是root。
  2. 无需解压即可删除 tarball 中的文件。

要使用 fakeroot,只需在 fakeroot 环境中运行上述 bash 脚本:

$ fakeroot
# for f in *.tar.gz; do
# ...
# done
# exit

第二种解决方案(就地 tarball 版本)使用 GNU tar 和 GNU awk:

for f in *.tar.gz; do
    echo "Processing file $f"
    t="${f%.*}"
    gzip -cd "$f" > "$t"
    tar -tvf "$t" | awk -vORS=$"\0" '/^-.*\.yang$/ && $3==0 {
      match($0,/(\S+\s+){4}\S+\s/); print substr($0,RLENGTH+1)}' |
      xargs -0 -n1 tar -f "$t" --delete
    gzip -c9 "$t" > "$f.new"
    echo "******************************************"
done

解释:

我们使用 GNU tar --delete 选项直接删除 tarball 中的文件,无需解压,这可能更优雅(即使它也可能比基于 fakeroot 的解决方案慢)。

让我们先找到压缩包中的所有空文件:

$ tar -tvf foo.tar
drwx------ john/users        0 2021-10-18 14:26 ./
drwx------ john/users        0 2021-10-18 16:34 ./
-rw------- john/users        0 2021-10-18 16:34 ./nonyang
drwx------ john/users        0 2021-10-18 15:22 ./foo.yang/
-rw------- john/users        0 2021-10-18 16:01 ./empty.yang
-rw------- john/users        7 2021-10-18 15:22 ./nonempty.yang
-rw------- john/users        0 2021-10-18 16:01 ./filename with spaces.yang

如您所见,尺寸在第三列。目录名称有一个前导 d 和一个尾随 /。符号链接有一个前导 l。因此,通过只保留以- 开头并以.yang 结尾的行,我们可以消除它们。 GNU awk 可以进行这种双重过滤:

$ tar -tvf foo.tar | awk '/^-.*\.yang$/ && $3==0 {print}'
-rw------- john/users        0 2021-10-18 16:01 ./empty.yang
-rw------- john/users        0 2021-10-18 16:01 ./filename with spaces.yang

这比我们想要的要多,所以让我们只打印名称部分。我们首先使用match 函数(设置一个名为RLENGTH 的变量)测量前5 个字段的长度,包括空格,然后使用substr 删除它们:

$ tar -tvf foo.tar | awk '/^-.*\.yang$/ && $3==0 {
    match($0,/(\S+\s+){4}\S+\s/); print substr($0,RLENGTH+1)}'
./empty.yang
./filename with spaces.yang

我们可以尝试通过仅在第一行调用match 来进行一些优化,但我不能 100% 确定所有输出行都完全对齐,所以让我们在每一行调用它。

我们差不多完成了:只需将其传递给tar -f foo.tar --delete <filename>,一次一个名字。 xargs 可以为我们做到这一点,但还有最后一个技巧:由于文件名可以包含空格,我们必须使用另一个分隔符,这是在文件名中找不到的东西,例如 NUL 字符(ASCII 代码 0)。幸运的是 GNU awk 可以使用 NUL 作为输出记录分隔符 (ORS) 并且 xargs 具有 -0 选项来使用它作为输入分隔符。所以,让我们把所有这些放在一起:

$ tar -tvf foo.tar | awk -vORS=$"\0" '/^-.*\.yang$/ && $3==0 {
    match($0,/(\S+\s+){4}\S+\s/); print substr($0,RLENGTH+1)}' |
    xargs -0 -n1 tar -f foo.tar --delete
$ tar -tvf foo.tar
drwx------ john/users        0 2021-10-18 16:34 ./
-rw------- john/users        0 2021-10-18 16:34 ./nonyang
drwx------ john/users        0 2021-10-18 15:22 ./foo.yang/
-rw------- john/users        7 2021-10-18 15:22 ./nonempty.yang

在您的 for 循环内:

for f in *.tar.gz; do
    echo "Processing file $f"
    t="${f%.*}"
    gzip -cd "$f" > "$t"
    tar -tvf "$t" | awk -vORS=$"\0" '/^-.*\.yang$/ && $3==0 {
      match($0,/(\S+\s+){4}\S+\s/); print substr($0,RLENGTH+1)}' |
      xargs -0 -n1 tar -f "$t" --delete
    gzip -c9 "$t" > "$f.new"
    echo "******************************************"
done

请注意,我们必须在编辑之前解压缩 tarball,因为 GNU tar 无法编辑压缩的 tarball。

【讨论】:

  • 非常感谢先生如此简短的解释。对于 .tar.gz 中的 f;做 echo "Processing file $f" d="$(mktemp -d)" tar -xf "$f" -C "$d" find "$d" -type f -name '.yang' -大小 0 -print -delete tar -C "$d" -czf "$f.new" 。 rm -rf "$d" echo "******************************************* *" 完成此代码对我来说绝对没问题。我想问一下,因为新的 tar.gz 文件正在创建为 xyz.tar.gz.new,所以我希望新文件具有相同的名称,并且应该删除以前的文件。那么我该怎么做呢?
  • 嗯,rm 是一个非常简单的命令。尝试man rm 了解它。然后修改脚本以在解压缩后删除 (rm) 旧 tarball,并重新打包没有 new 扩展名的 tarball。尝试解决像这样的简单问题是最好的学习方式。
猜你喜欢
  • 1970-01-01
  • 2017-07-15
  • 2017-08-09
  • 2012-06-29
  • 2022-07-16
  • 2014-10-28
  • 1970-01-01
  • 2022-01-19
  • 2022-12-01
相关资源
最近更新 更多