シェルで複数の(複合)拡張子を持つファイルセットの名前を変更する

シェルで複数の(複合)拡張子を持つファイルセットの名前を変更する

拡張子は多数あるが名前は一意であるファイル セットのリストがあります。

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。これはあなたのケースでは問題にならないと思いますが、将来の読者はこれに注意する必要があります。)

ランダム変換を適用する場合は、各ベースが何にマップされるかを覚えておく必要があります。それには 2 つの方法があります。

  • ファイルをベースごとにグループ化し、一度に 1 つのベースを処理できます。
  • ファイルを 1 つずつ処理することもできますが、各ベースが何にマップされているかを記憶し、ベースがまだ表示されていない場合にのみ新しいマッピングを生成します。

2 番目の方法の方が簡単で、1 番目の方法には特に利点がないため、2 番目の方法のみを示します。

構築する連想配列ベースを新しいベースにマッピングします。

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を使用するのは、1つの変換ステップから次の変換ステップに情報を保持する必要があるため、より複雑です。コマンド置換にいくつかのコードを詰め込むだけで済みます。これは、zmv … '$(…; if …; then mapping[$base]=…; …)'割り当てがmappingコマンド置換サブシェル内にあるため、サブシェル内でのみ効果があるためです。ただし、条件付きパラメータ割り当てを使用できます。${name=word}mapping[$base]設定されていない場合にのみ設定します。

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

上記 (1.) のより複雑な例のように、を利用しない分解で zmv を使用すると、.fooはるかに複雑なコードになります。例として、 をbase一時変数として使用して基本名を格納する、決定論的なケースでの zmv 呼び出しを示します。これは、${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にオプション (dry-run)を追加しますzmv

zmv自動ロード可能な関数として実装されたバッチ名前変更ツールです。

最初の引数は拡張 glob パターンであり、2 番目の引数は、パターン内の対応する を参照する$1、 ...を使用してファイルを削除する方法を決定する単語拡張を受ける文字列です。$2(...)

${rand[$1]=$(cmd)}上記は連想配列のメンバーを設定します.foo.出力の右端の左側にあるものがcmd以前に設定されていない場合、常に同じ値が得られるようになります。

関連情報