在 shell 中重命名具有多个(包括复合)扩展名的文件集

在 shell 中重命名具有多个(包括复合)扩展名的文件集

我有一个文件集列表,其中有很多扩展名,但名称唯一。

filename-1.foo.001
...
filename-1.foo.020
filename-1.foo.baz
filename-1.foo.bar1-2.baz
...
filename-1.foo.bar7-8.baz

another_filename.foo.001
another_filename.foo.002
...
another_filename.foo.009
another_filename.foo.baz
another_filename.foo.bar1-2.baz
another_filename.foo.bar3-4.baz
another_filename.foo.bar4-5.baz
another_filename.foo.bar7-8.baz

yet.a.different.file.name.foo.001
yet.a.different.file.name.foo.002
...
yet.a.different.file.name.foo.287
yet.a.different.file.name.foo.baz
yet.a.different.file.name.foo.bar1-2.baz
yet.a.different.file.name.foo.bar3-4.baz
yet.a.different.file.name.foo.bar4-5.baz
yet.a.different.file.name.foo.bar7-8.baz

moreFileNaming.foo.001
...
moreFileNaming.foo.009
moreFileNaming.foo.baz
moreFileNaming.foo.bar1-2.baz
moreFileNaming.foo.bar3-4.baz
moreFileNaming.foo.bar4-5.baz
moreFileNaming.foo.bar7-8.baz

我想使用 的输出来重命名它们,openssl rand -hex 8以获得每组的随机文件名,如下所示:

9874f7187c914ea9.foo.001
...
9874f7187c914ea9.foo.020
9874f7187c914ea9.foo.baz
9874f7187c914ea9.foo.bar1-2.baz
...
9874f7187c914ea9.foo.bar7-8.baz

2f54a0b6528e3927.foo.001
2f54a0b6528e3927.foo.002
...
2f54a0b6528e3927.foo.009
2f54a0b6528e3927.foo.baz
2f54a0b6528e3927.foo.bar1-2.baz
2f54a0b6528e3927.foo.bar3-4.baz
2f54a0b6528e3927.foo.bar4-5.baz
2f54a0b6528e3927.foo.bar7-8.baz

71ad0aa90148b2f5.foo.001
71ad0aa90148b2f5.foo.002
...
71ad0aa90148b2f5.foo.287
71ad0aa90148b2f5.foo.baz
71ad0aa90148b2f5.foo.bar1-2.baz
71ad0aa90148b2f5.foo.bar3-4.baz
71ad0aa90148b2f5.foo.bar4-5.baz
71ad0aa90148b2f5.foo.bar7-8.baz

3721323156e921b5.foo.001
...
3721323156e921b5.foo.009
3721323156e921b5.foo.baz
3721323156e921b5.foo.bar1-2.baz
3721323156e921b5.foo.bar3-4.baz
3721323156e921b5.foo.bar4-5.baz
3721323156e921b5.foo.bar7-8.baz

我已经尝试过for name (*.(<->|baz|bar<->.baz) result=$(openssl rand -hex 16) && mv $name $result(这可能无法正常工作,因为它是多次迭代前的),但是当它起作用时,它会给出每个文件一个随机名称,我只是希望每组都保留相同的名称,只是随机且大小相同。 Sha1sum 或任何其他方法也可以。

我该如何实现这个目标?特别是对于文件.foo.bar-*.baz?

如果我们删除 foo

3721323156e921b5.001
...
3721323156e921b5.009
3721323156e921b5.baz
3721323156e921b5.bar1-2.baz
3721323156e921b5.bar3-4.baz
3721323156e921b5.bar4-5.baz
3721323156e921b5.bar7-8.baz

也会好的。还有一些问题:

  1. 如何从文件开头到 .foo 定位?
  2. 如何循环创建的变量,例如result=$(openssl rand -hex 8)以便在重命名中使用它,并且仅当一组完成时,才能再次分配它以循环它直到下一组完成等?

谢谢!

答案1

这个问题有几个部分:

  1. 将每个文件名分解为基本部分和扩展名。
  2. 对每个名称的基本部分应用一致的转换。
  3. 根据所选的基础部件转换重命名文件,保留扩展名。

1. 分解文件名

从您的示例名称中尚不完全清楚您认为文件名的基本部分是什么。分隔符显然是一个点,但在像这样的示例中yet.a.different.file.name.foo.bar1-2.baz,哪个点?您提到使用 的尝试*.(<->|baz|bar<->.baz),它不会将foobar1-2视为扩展。允许它们作为扩展的一个调整是.(foo|<->|baz|bar<->(|-<->).baz)。然后你可以按如下方式破坏文件名$f

setopt extended_glob
base=${f%%(.(<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}

或者,如果可以将基定义为第一个之前的所有内容(并且不包括第一个).foo.,则分解会更简单:

base=${f%*.foo.*}; extensions=${f#$base}

2. 应用一致的转换

如果你想应用确定性变换,你只需每次重新计算即可。例如,您可以通过使用密钥获取名称的 MAC(每次使用相同的密钥)来获得伪随机结果。

secret=$(openssl rand -hex 32)
for … # Loop over the files as per (3.), set $base and $extensions as per (1.)
  new_base=${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}

ps(注意:如果其他用户在运行时运行,则秘密密钥将对其他用户可见openssl。我认为这在您的情况下不是问题,但未来的读者应该注意这一点。)

如果您想应用随机变换,您需要记住每个碱基映射到什么。有两种方法可以做到这一点:

  • 您可以按碱基对文件进行分组,然后一次处理一个碱基。
  • 您可以逐个处理文件,但请记住每个碱基映射到什么,并且仅在尚未看到碱基时才生成新映射。

第二种方法比较简单,第一种方法没有什么特别的优势,所以我只展示第二种方法。

建立一个关联数组将碱基映射到新碱基。

typeset -A mapping
mapping=()
for … # Loop over the files as per (3.), set $base and $extensions as per (1.)
  if ((!$+mapping[$base])); then
    mapping[$base]=$(openssl rand -hex 8)
  fi
  new_base=$mapping[$base]

3. 重命名文件

Zsh 附带了一个非常有用的工具来重命名文件:zmv。您想要执行的转换非常复杂,zmv 不会使它变得微不足道:文件名分解和转换都需要额外的工作。即使在您的情况下,zmv 也有一些小优势。特别是,如果发生冲突,zmv 会出错(由于随机因素,除非使用较短的长度,否则极不可能发生)。但由于名字转换比较困难,使用zmv比较别扭,简单的循环更容易写。

这是使用随机名称的完整片段。

setopt extended_glob
typeset -A mapping
mapping=()
for f in *.(foo|<->|baz|bar<->(|-<->).baz); do
  base=${f%%(.(foo|<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}
  if ((!$+mapping[$base])); then
    mapping[$base]=$(openssl rand -hex 8)
  fi
  new_base=$mapping[$base]
  mv -i -- $f $new_base$extensions
done

这是使用给定值 的确定性名称的完整片段$secret

setopt extended_glob
secret=$(openssl rand -hex 32)
for f in *.(foo|<->|baz|bar<->(|-<->).baz); do
  base=${f%%(.(foo|<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}
  new_base=${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}
  mv -i -- $f $new_base$extensions
done

zmv这是用于确定性情况的单行代码,使用第一个.foo.标记基数的末尾。该-w标志有助于分解。

autoload zmv
secret=$(openssl rand -hex 32)
zmv -w '*.foo.*' '${"$(openssl dgst -sha256 -hmac $secret <<<$1)"[-16,-1]}.foo.$2'

在随机情况下使用 zmv 比较棘手,因为我们需要保留从一个转换步骤到下一个转换步骤的信息。我们可以将一些代码打包到命令替换中,zmv … '$(…; if …; then mapping[$base]=…; …)'因为对 的赋值mapping将位于命令替换子 shell 内,因此只会在子 shell 内产生影响。但是,我们可以使用条件参数赋值${name=word}mapping[$base],仅在未设置时进行设置。

typeset -A mapping; mapping=()
zmv -w '*.foo.*' '${mapping[${1}]=$(openssl rand -hex 16)}.foo.$2'

将 zmv 与不利用 的分解结合使用.foo(如上面 (1.​​) 中更复杂的示例)会产生更复杂的代码。仅出于示例目的,这里有一个针对确定性情况的 zmv 调用,用作base存储基本名称的临时变量。它用于${name::=word}在参数扩展期间分配给变量,并${…}[0]从扩展中抑制该部分([0]从第 0 个字符获取子字符串,该子字符串不存在,因为 zsh 从 1 开始对数组元素和字符串字符进行编号;类似的东西[2,1]也可以工作)。

zmv  '*.(<->|baz|bar<->.baz)' '${${base::=${f%%(.(<->|baz|bar<->(|-<->).baz))#}}[0]}${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}.${f#$base}'

答案2

你可以这样做:

autoload -Uz zmv # best in ~/.zshrc
typeset -A rand
zmv '(*).foo(.*)' '${rand[$1]=$(openssl rand -hex 8)}$2'

或者'(*)(.foo.*)'不掉落.foo

要首先测试,请将-n选项(试运行)添加到zmv.

zmv是一个作为自动加载功能实现的批量重命名工具。

第一个参数是扩展的 glob 模式,第二个参数是经过单词扩展的字符串,用于确定如何使用$1, ... 引用模式中$2相应的 s 来删除文件。(...)

${rand[$1]=$(cmd)}上面设置了关联数组的成员钥匙.foo.if的输出最右边的左边cmd之前未设置,这确保您始终获得给定值的相同值钥匙

相关内容