如何动态(递归地)将所有文件从 gzip 转换为 xz?

如何动态(递归地)将所有文件从 gzip 转换为 xz?

我有一个包含 gzip 压缩文件的目录树,如下所示:

basedir/a/file.dat.gz
basedir/b/file.dat.gz
basedir/c/file.dat.gz
etc.

如何使用单个命令将所有这些文件从 gzip 转换为 xz,而不将每个文件解压到磁盘?

解压缩到磁盘的简单两行代码如下所示:

find basedir/ -type f -name '*.dat.gz' -exec gzip -d {} \;
find basedir/ -type f -name '*.dat' -exec xz {} \;

第一个命令甚至可以更短:gunzip -r *

对于单个文件,即时转换很简单(尽管这不会替换 .gz 文件):

gzip -cd basedir/a/file.dat.gz | xz > basedir/a/file.dat.xz

由于 gzip 和 xz 自己处理扩展,我想说:

gunzip -rc * > xz

我看了find | xargs basename -s .gz { }一点,但没有找到可行的解决方案。

我可以写一个shell脚本,但我觉得应该有一个简单的解决方案。


编辑

感谢所有已经回答的人。我知道我们都喜欢“永远不会失败的命令™”。因此,为了简单起见:

  • 所有子目录仅包含数字、字母(不过,äöü)、下划线和减号。
  • 所有文件均命名为 file.dat[.n].gz,n 为正整数
  • 任何目录或文件在任何地方都不会有“.gz”(除了作为最终文件后缀)。
  • 这是这些目录包含的唯一内容。
  • 我控制命名并可以根据需要限制它。

使用简单的find -exec ...or ls | xargs,是否有命令可以将找到的文件名中的“.gz”即时替换为“.xz”?然后我可以写一些类似(伪)的东西:

find basedir/ -type f -name '*.gz' -exec [ gzip -cd {} | xz > {replace .gz by .xz} \; ]

答案1

find . -name '*.gz' -type f -exec bash -o pipefail -Cc '
  for file do
    gunzip < "$file" | xz > "${file%.gz}.xz" && rm -f "$file"
  done' bash {} +

防止-C覆盖现有文件并且不会遵循符号链接除了如果现有文件是非常规文件或指向非常规文件的链接,那么您不会丢失数据,除非您有例如 afile.gz和 afile.xz的符号链接/dev/null。为了防止这种情况,您可以使用zsh一些-execdir实现的功能find来进行良好的测量并避免一些竞争条件:

find . -name '*.gz' -type f -execdir zsh -o pipefail -c '
  zmodload zsh/system || exit
  for file do
    gunzip < "$file" | (
      sysopen -u 1 -w -o excl -- "${file%.gz}.xz" && xz) &&
      rm -f -- "$file"
  done' zsh {} +

或者在重新压缩失败时清理xz文件:

find . -name '*.gz' -type f -execdir zsh -o pipefail -c '
  zmodload zsh/system || exit
  for file do
    sysopen -u 1 -w -o excl -- "${file%.gz}.xz" &&
      if gunzip < "$file" | xz; then
        rm -f -- "$file"
      else
        rm -f -- "${file%.gz}.xz"
      fi
  done' zsh {} +

如果您希望它很短,并且准备好忽略其中一些潜在问题,那么zsh您可以这样做

for f (./**/*.gz(D.)) {gunzip < $f | xz > $f:r.xz && rm -f $f}

答案2

我喜欢简单的for循环...

for file in basedir/*/*.gz
do
    gzip -cd < "$file" | xz > "${file%%.gz}.xz"
done

...至少,如果您的目录结构足够规则且简单的话。如果你必须穿越到未知的深度,或者在文件选择上有额外的条件,你仍然必须坚持find或类似。

答案3

find basedir/ -type f -name '*.dat.gz'|while read -r line; do
 gzip -cd "$line" | xz > ${line%.gz}.xz
 rm "$line"
done

答案4

您可以使用 find 和 parallel 来完成此操作

parallel -0 'gzip -cd '{}' | xz > '{.}'.xz; rm '{}'' < <(find basedir -iname \*gz -print0)

已完成步骤:

  • 递归查找所有以 gz 结尾的文件(不区分大小写)
  • 来自进程替换的标准输入
  • 并行 gzip foo.gz | xz > {foo}.xz; rm foo.gz
    • {.} 从 foo.gz 中删除 .gz (以我的理解)

相关内容