Переименовывать наборы файлов с несколькими (включая составные) расширениями в оболочке

Переименовывать наборы файлов с несколькими (включая составные) расширениями в оболочке

У меня есть список наборов файлов с большим количеством расширений, но уникальными именами.

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-*.баз ?

Если мы сбросим фу

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), которая не будет рассматривать fooили bar1-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будет внутри подоболочки подстановки команды и, следовательно, будет иметь эффект только внутри подоболочки. Однако мы можем использовать условное назначение параметра${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инструмент пакетного переименования, реализованный как автозагружаемая функция.

Первый аргумент — это расширенный шаблон глобуса, а второй аргумент — строка, которая подвергается расширению слов, определяющему, как удаляются файлы с помощью $1, $2..., ссылающихся на соответствующие (...)s в шаблоне.

${rand[$1]=$(cmd)}выше задает элемент ассоциативного массива дляключто, что осталось от самого правого .foo.выхода, cmdесли ранее было не установлено, что гарантирует, что вы всегда получите одно и то же значение заданногоключ.

Связанный контент