Переименование большого количества файлов изображений с помощью bash

16

Мне нужно переименовать ок. 70000 файлов. Например: от sb_606_HBO_DPM_0089000до sb_606_dpm_0089000и т. Д.

Диапазон номеров идет от 0089000до 0163022. Это только первая часть имени, которая должна измениться. все файлы находятся в одном каталоге и пронумерованы последовательно (последовательность изображений). Числа должны остаться без изменений.

Когда я пытаюсь сделать это в bash, меня смущает, что «список аргументов слишком длинный».

Редактировать:

Сначала я попытался переименовать один файл с помощью mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Затем я попытался переименовать диапазон (на прошлой неделе я узнал, как переместить загрузку файлов, поэтому я подумал, что тот же синтаксис может сработать для переименования файлов ...). Я думаю, что попробовал следующее (или что-то подобное):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
богатый
источник
4
Для рецензентов : я не думаю, что это дубликат; большинство ответов CLI на другой вопрос не будут работать здесь из-за большого количества файлов, сталкивающихся с ARG_MAXпределом оболочки . Поскольку этот вопрос явно требует решения для командной строки, (возможно, равные) решения с графическим интерфейсом, как и в другом вопросе, также не совпадают.
десерт
1
Я не думаю, что это обман, потому что нормально иметь больше одного вопроса о переименовании файлов. Пожалуйста, давайте не будем закрывать конкретные вопросы с общими ресурсами, которые на самом деле не отвечают на них ...
Zanna
1
@rich Если вы можете явно отредактировать, какую команду вы пробовали, было бы понятнее, что это не обман. (Это показывает нам, что вы знаете об этом подходе.) Приветствия.
Sparhawk
2
Богатый, твой вопрос не обманщик, потому что это особый вопрос. Не беспокойся об этом. Что еще более важно, после того, как вопрос получил ряд ответов с отголосками, его редактирование, вероятно, не является хорошей идеей, поскольку ваши изменения могут сделать существующие ответы менее действительными. Теперь я чувствую, что мой ответ должен объяснить, почему mv {1..2} {3..4}не работает, что является совершенно другой проблемой, чем ARG_MAX... Все остальные, кто ответил, вероятно, будут чувствовать то же самое! Так что, с моей точки зрения, я бы хотел, чтобы вы откатили последнее изменение и, если хотите, задали совершенно новый вопрос об использовании mvдиапазонов
Zanna
1
@Sparhawk ОП довольно ясно, из первой версии вопроса, написал, что проблема в argument list too longошибке. Больше нет необходимости уточнять, это явно не обман, так как нам нужен обходной путь для работы с ARG_MAX, а ответы в предложенном дубликате этого не делают.
тердон

Ответы:

25

Одним из способов является использование findс -exec, и +вариант. Это создает список аргументов, но разбивает список на столько вызовов, сколько необходимо для работы со всеми файлами без превышения максимального списка аргументов. Это подходит, когда все аргументы будут рассматриваться одинаково. Это в случае rename, хотя не с mv.

Вам может понадобиться установить Perl Rename:

sudo apt install rename

Тогда вы можете использовать, например:

find . -maxdepth 1 -exec rename -n 's/_HBO_DPM_/_dpm_/' {} +

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

Занна
источник
11

Я собираюсь предложить три варианта. Каждая из них - простая однострочная команда, но я предоставлю варианты для более сложных случаев, в основном, в случае, если файлы для обработки смешиваются с другими файлами в той же директории.

MMV

Я бы использовал команду mmv из одноименного пакета :

mmv '*HBO_DPM*' '#1dpm#2'

Обратите внимание, что аргументы передаются в виде строк, поэтому расширение оболочки не происходит в оболочке. Команда получает ровно два аргумента, а затем находит соответствующие файлы внутри, без жестких ограничений на количество файлов. Также обратите внимание, что в приведенной выше команде предполагается, что все файлы, соответствующие первому глобу, должны быть переименованы. Конечно, вы можете быть более конкретным:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Если у вас есть файлы за пределами запрошенного диапазона номеров в одном и том же каталоге, вам, возможно, будет лучше, если циклические числа указаны ниже в этом ответе. Однако вы также можете использовать последовательность вызовов mmv с подходящими шаблонами:

mmv 'sb_606_HBO_DPM_0089*'       'sb_606_dpm_0089#1'    # 0089000-0089999
mmv 'sb_606_HBO_DPM_009*'        'sb_606_dpm_009#1'     # 0090000-0099999
mmv 'sb_606_HBO_DPM_01[0-5]*'    'sb_606_dpm_01#1#2'    # 0100000-0159999
mmv 'sb_606_HBO_DPM_016[0-2]*'   'sb_606_dpm_016#1#2'   # 0160000-0162999
mmv 'sb_606_HBO_DPM_01630[01]?'  'sb_606_dpm_01630#1#2' # 0163000-0163019
mmv 'sb_606_HBO_DPM_016302[0-2]' 'sb_606_dpm_016302#1'  # 0163020-0163022

зацикливание чисел

Если вы хотите избежать установки чего-либо или вам нужно выбрать по диапазону номеров, избегая совпадений за пределами этого диапазона, и вы готовы ждать 74 023 вызовов команд, вы можете использовать простой цикл bash:

for i in {0089000..0163022}; do mv sb_606_HBO_DPM_$i sb_606_dpm_$i; done

Это работает особенно хорошо здесь, так как в последовательности нет пробелов. В противном случае вы можете проверить, существует ли исходный файл.

for i in {0089000..0163022}; do
  test -e sb_606_HBO_DPM_$i && mv sb_606_HBO_DPM_$i sb_606_dpm_$i
done

Обратите внимание, что в отличие for ((i=89000; i<=163022; ++i))от скобки расширение обрабатывает лидирующие нули с момента выпуска Bash пару лет назад. На самом деле изменение, которое я запросил, так что я рад видеть варианты его использования.

Дальнейшее чтение: Расширение Brace на информационных страницах Bash, особенно часть о {x..y[..incr]}.

перебирать файлы

Другой вариант будет заключаться в цикле по подходящему глобу, а не просто в цикле по целому диапазону. Что-то вроде этого:

for i in *HBO_DPM*; do mv "$i" "${i/HBO_DPM/dpm}"; done

Опять же, это один mvвызов на файл. И снова цикл проходит по длинному списку элементов, но весь список не передается в качестве аргумента подпроцессу, а обрабатывается внутренне с помощью bash, поэтому ограничение не вызовет проблем.

Дальнейшее чтение: Расширение параметров оболочки на информационных страницах Bash, документирование ${parameter/pattern/string}среди прочего.

Если вы хотите ограничить диапазон номеров указанным вами, вы можете добавить проверку для этого:

for i in sb_606_HBO_DPM_+([0-9]); do
  if [[ "${i##*_*(0)}" -ge 89000 ]] && [[ "${i##*_*(0)}" -le 163022 ]]; then
    mv "$i" "${i/HBO_DPM/dpm}"
  fi
done

Здесь ${i##pattern}удаляет самый длинный префикс соответствия patternиз $i. Этот самый длинный префикс определяется как что-либо, затем подчеркивание, затем ноль или более нулей. Последний написан как *(0)расширенный шаблон глобуса, который зависит от устанавливаемой extglobопции . Удаление начальных нулей важно, чтобы число считалось основанием 10, а не основанием 8. +([0-9])Аргумент in loop представляет собой еще один расширенный глобус, соответствующий одной или нескольким цифрам, на тот случай, если у вас есть файлы, которые начинаются одинаково, но не заканчиваются на число.

MVG
источник
Спасибо! Это сработало как мечта: я в {0089000..0163022}; do mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; готово - мне пришлось добавить расширение имени файла, чтобы оно заработало, но оно сделало то, что я хотел, и я даже понимаю синтаксис. Спасибо @MvG
богатым
@rich: Рад, что смог помочь - и вам, и, надеюсь, будущим посетителям. Не забудьте принять самый полезный ответ. Вы всегда можете поменять эту галочку в будущем, если появится что-то лучшее.
MvG
10

Один из способов обойти ARG_MAXограничение - использовать встроенную в bash оболочку printf:

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Ex.

rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long

но

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
rename(sb_606_HBO_DPM_0089000, sb_606_dpm_0089000)
.
.
.
rename(sb_606_HBO_DPM_0163022, sb_606_dpm_0163022)
steeldriver
источник
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

findв текущем каталоге .для всех файлов -type fи переименуйте найденный файл $1с заменой HBO_DPMна dmp один за другим-exec ... \;

заменить echoс mvвыполнить переименование.

αғsнιη
источник
6

Вы могли бы написать небольшой скрипт на Python, что-то вроде:

import os
for file in os.listdir("."):
    os.rename(file, file.replace("HBO_DPM", "dpm"))

Сохраните это как текстовый файл как rename.pyв папке, в которой находятся файлы, затем с терминалом в этой папке:

python rename.py
Каменный череп
источник
6

Вы можете сделать это файл за файлом (это может занять некоторое время) с

sudo apt install util-linux  # if you don't have it already
for i in *; do rename.ul HBO_DPM dpm "$i"; done

Как и Perl, renameиспользуемый в других ответах, rename.ulтакже имеет опцию -nили --no-actдля тестирования.

muclux
источник
Я отредактировал ваш комментарий об ответе Занны, пожалуйста, отредактируйте ответ Занны или оставьте комментарий.
fosslinux
@ubashu, который не был комментарием к моему ответу - он имел в виду -nфлаг, который я использовал для тестирования, и предполагал, что его можно использовать rename.ulтакже.
Занна
3

Я вижу, что никто не пригласил моего лучшего друга sedна вечеринку :). Следующий forцикл достигнет вашей цели:

for i in sb_606_HBO_DPM*; do
  mv "$i" "$(echo $i | sed 's/HBO_DPM/dpm/')";
done

Существует множество инструментов для такой работы, выберите наиболее понятный для вас. Этот простой и легко изменяемый, чтобы соответствовать тем или иным целям ...

andrew.46
источник
Конечно, не очень актуально в данном конкретном случае, но это не удастся, если какое-либо из имен файлов будет содержать символы новой строки. Я упоминаю об этом, поскольку большинство (все?) Других ответов являются надежными и могут иметь дело с произвольными именами файлов или работать только по схеме именования файлов OP.
Тердон
... переводы строк, пробелы, символы подстановки, ... некоторых из них можно избежать, заключив $iв кавычки в подстановке команд, но нелегкий способ обработать завершающий перевод строки в имени файла.
Муру
3

Так как мы даем варианты, вот подход Perl. cdв целевой каталог и запустите:

perl -e 'foreach(glob("sb_*")){rename $_, s/_HBO_DPM_/_dpm_/r}'

объяснение

  • perl -e: запустить скрипт, указанный -e.
  • foreach(glob){}: запустите все, что есть в { }каждом результате глобуса.
  • glob("sb_*"): возвращает список всех файлов и каталогов в текущем каталоге, чьи имена совпадают с глобусом оболочки sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: perl magic. $_это специальная переменная, которая содержит каждый элемент, который мы повторяем (в foreach). Итак, здесь будет найден каждый файл. s/_HBO_DPM_/_dpm_/заменяет первое вхождение _HBO_DPM_с _dpm_. Он запускается $_по умолчанию, поэтому он будет запускаться для каждого имени файла. В /rозначает «применить эту замену копию целевой строки (имя файла) и возвращает модифицированную строку. renameДелает то , что можно было ожидать: он переименовывает файлы Так что все это будет переименовать имя текущего файла (. $_) К себе с _HBO_DPM_заменено на _dpm_.

Вы можете написать то же самое, что и расширенный (и более читаемый скрипт):

#! /usr/bin/env perl
use strict;
use warnings;

foreach my $fileName (glob("sb_*")){
  ## Copy the name to a new variable
  my $newName = $fileName;
  ## change the copy. $newName is now the changed version
  $newName =~ s/_HBO_DPM_/_dpm_/;
  ## rename
  rename $fileName, $newName;
}
terdon
источник
1

В зависимости от вида переименования, которое вы планируете, использование vidir с многострочным редактированием может быть удовлетворительным.
В вашем конкретном случае вы можете выделить все строки в текстовом редакторе и удалить часть " HBO" имен файлов за несколько нажатий клавиш.

kraymer
источник
да, ви имеет возможность найти и заменить.
Jasen
2
Не могли бы вы расширить свой ответ и привести пример, как достичь цели ОП vidir?
десерт