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

Mar*_*ngs 7 pipe gzip recursive conversion xz

我有一个带有 gzip 文件的目录树,如下所示:

basedir/a/file.dat.gz
basedir/b/file.dat.gz
basedir/c/file.dat.gz
etc.
Run Code Online (Sandbox Code Playgroud)

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

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

find basedir/ -type f -name '*.dat.gz' -exec gzip -d {} \;
find basedir/ -type f -name '*.dat' -exec xz {} \;
Run Code Online (Sandbox Code Playgroud)

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

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

gzip -cd basedir/a/file.dat.gz | xz > basedir/a/file.dat.xz
Run Code Online (Sandbox Code Playgroud)

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

gunzip -rc * > xz
Run Code Online (Sandbox Code Playgroud)

我看了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} \; ]
Run Code Online (Sandbox Code Playgroud)

Sté*_*las 10

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

-C防止覆盖现有文件并不会跟随符号连接,除了如果退出的文件是一个非正规的文件或到非正规文件的链接,这样你就不会丢失数据,除非你有一个实例file.gz和一个file.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 {} +
Run Code Online (Sandbox Code Playgroud)

或者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 {} +
Run Code Online (Sandbox Code Playgroud)

如果您希望它简短,并准备忽略其中一些潜在问题,则zsh可以这样做

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


Ipo*_*cer 0

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