Пакетное переименование файлов с помощью Bash

101

Как Bash может переименовать серию пакетов, чтобы удалить их номера версий? Я играл с обоими exprи%% безрезультатно.

Примеры:

Xft2-2.1.13.pkg становится Xft2.pkg

jasper-1.900.1.pkg становится jasper.pkg

xorg-libXrandr-1.2.3.pkg становится xorg-libXrandr.pkg

Джереми Л
источник
1
Я намерен использовать это регулярно, как одноразовый многоразовый скрипт. В любой системе, которую я буду использовать, будет bash, поэтому я не боюсь очень удобных башизмов.
Джереми Л.
В более общем плане см. Также stackoverflow.com/questions/28725333/…
tripleee

Ответы:

191

Вы можете использовать функцию расширения параметров bash

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Кавычки необходимы для имен файлов с пробелами.

Richq
источник
1
Мне он тоже нравится, но в нем используются специфичные для bash функции ... Обычно я не использую их в пользу "стандартного" sh.
Diego Sevilla
2
Для одноразовых интерактивных вещей я всегда использую это вместо sed-fu. Если бы это был сценарий, да, избегайте башизмов.
richq 02
Я обнаружил одну проблему с кодом: что произойдет, если файлы уже были переименованы и я запустил его снова? Я получаю предупреждения за попытку перейти в отдельный подкаталог.
Джереми Л.
1
Чтобы избежать ошибок повторного запуска, вы можете использовать тот же шаблон в части « .pkg», т.е. «for i in * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; готово ". Но ошибки достаточно безобидные (переход в тот же файл).
richq 02
3
Не решение для bash, но если у вас установлен perl, он предоставляет удобную команду переименования для пакетного переименования, просто сделайтеrename "s/-[0-9.]*//" *.pkg
Руфус
33

Если все файлы находятся в одном каталоге, последовательность

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

сделаю вашу работу. Команда sed создаст последовательность команд mv , которые затем можно передать оболочке. Лучше всего сначала запустить конвейер без конечной точки, | shчтобы убедиться, что команда выполняет то, что вы хотите.

Для рекурсии через несколько каталогов используйте что-то вроде

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Обратите внимание, что в sed последовательность группировки регулярных выражений представляет собой скобки, которым предшествует обратная косая черта, \(и \), а не одиночные скобки (и ).

Диомидис Спинеллис
источник
Простите мою наивность, но разве отсутствие скобок с помощью обратной косой черты не заставит их рассматривать как буквальные символы?
devios1 07
2
В sed последовательность группировки регулярных выражений - (, а не одиночная скобка.
Диомидис Спинеллис,
у меня не сработало, буду рад получить вашу помощь .... Суффикс файла FROM добавляется к файлу TO: $ find. -тип d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> ВЫВОД >>>> mv: переименуйте ./base/src/main/java/com/anysoftkeyboard/base/dictionaries в ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : Нет такого файла или каталога
Виталий Пом
Кажется, вам не хватает обратной косой черты в скобках.
Диомидис Спинеллис
1
Не используйте lsв скриптах. Первое разнообразие может быть достигнуто с printf '%s\n' * | sed ...помощью некоторого творчества, и вы, вероятно, также сможете избавиться от него sed. Bash printfпредлагает '%q'код формата, который может пригодиться, если у вас есть имена файлов, содержащие буквальные метасимволы оболочки.
tripleee
10

Сделаю примерно так:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

Предполагается, что все ваши файлы находятся в текущем каталоге. Если нет, попробуйте использовать find, как посоветовал выше Хавьер.

РЕДАКТИРОВАТЬ : Кроме того, в этой версии не используются какие-либо специфические для bash функции, как в других выше, что приводит к большей переносимости.

Диего Севилья
источник
Все они находятся в одном каталоге. Что вы думаете об ответе rq?
Джереми Л.
Мне нравится не использовать специфичные для bash функции, а использовать вместо них более стандартный sh.
Диего Севилья
1
Я предположил, что это было для быстрого переименования, а не для написания кроссплатформенного переносимого корпоративного приложения следующего поколения ;-)
richq
1
Ха-ха, достаточно справедливо ... Просто от такого человека, который думает о портативности, как я :)
Диего Севилья
Этот не учитывает третий пример: xorg-libXrandr (в имени используется дефис).
Джереми Л.
5

Вот POSIX, почти эквивалент принятого в настоящее время ответа . Это заменяет ${variable/substring/replacement}расширение параметров Bash на расширение, доступное в любой Bourne-совместимой оболочке.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

Расширение параметра ${variable%pattern}дает значение variableс patternудаленным суффиксом . (Также есть ${variable#pattern}возможность удалить приставку.)

Я сохранил подшаблон -[0-9.]*из принятого ответа, хотя, возможно, он вводит в заблуждение. Это не регулярное выражение, а шаблон глобуса; поэтому это не означает «тире, за которым следует ноль или более цифр или точек». Вместо этого оно означает «тире, за которым следует число или точка, за которыми следует что угодно». «Все» будет самым коротким, а не самым длинным совпадением. (Bash предлагает ##и %%для обрезки самого длинного префикса или суффикса, а не самого короткого.)

тройной
источник
5

Мы можем предположить, что sedон доступен на любом * nix, но мы не можем быть уверены, что он поддерживает sed -nгенерацию команд mv. ( ПРИМЕЧАНИЕ:sed это делает только GNU .)

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

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

использование

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Создание целевых папок:

Поскольку mvне создает целевые папки автоматически, мы не можем использовать нашу первоначальную версию sedrename.

Это довольно небольшое изменение, поэтому было бы неплохо добавить эту функцию:

Нам понадобится служебная функция abspath(или абсолютный путь), поскольку в bash нет этой сборки.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Получив это, мы можем создать целевую папку (и) для шаблона sed / rename, который включает новую структуру папок.

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

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Вот полный скрипт с поддержкой папок ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Конечно, это все еще работает, когда у нас нет определенных целевых папок.

Если мы хотим поместить все песни в папку, ./Beethoven/мы можем сделать это:

использование

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Бонусный раунд ...

Используя этот скрипт для перемещения файлов из папок в одну папку:

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

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Примечание о шаблонах регулярных выражений sed

В этом скрипте применяются обычные правила шаблона sed, эти шаблоны не являются PCRE (Perl-совместимые регулярные выражения). Вы можете использовать расширенный синтаксис регулярных выражений sed, используя любой из них sed -rили в sed -E зависимости от вашей платформы.

См. POSIX-совместимый man re_formatдля полного описания базовых и расширенных шаблонов регулярных выражений sed.

окодо
источник
4

Я считаю, что переименование - гораздо более простой инструмент для такого рода вещей. Я нашел это на Homebrew для OSX

Для вашего примера я бы сделал:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

'S' означает замену. Форма - s / searchPattern / replace / files_to_apply. Для этого вам нужно использовать регулярное выражение, что требует небольшого изучения, но оно того стоит.

Саймон Катан
источник
Я согласен. В редких случаях можно использовать forи sedт. Д., Но гораздо проще и быстрее использовать, renameесли вы часто делаете это.
argentum2f
Это вы избегаете менструации?
Big Money
Проблема в том, что это не переносимо; не только не на всех платформах есть renameкоманда; кроме того, у некоторых будут разные rename команды с разными функциями, синтаксисом и / или параметрами. Это похоже на renameдовольно популярный Perl ; но ответ должен прояснить это.
тройняшек
3

лучше использовать sedдля этого что-то вроде:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

выяснение регулярного выражения оставлено как упражнение (как и работа с именами файлов, которые включают пробелы)

Хавьер
источник
Спасибо: в именах не используются пробелы.
Джереми Л.
1

Кажется, это работает при условии, что

  • все заканчивается на $ pkg
  • номер вашей версии всегда начинается с "-"

удалите .pkg, затем удалите - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Стив Б.
источник
Некоторые пакеты содержат дефис в названии (см. Третий пример). Это влияет на ваш код?
Джереми Л.
1
Вы можете запускать несколько выражений sed, используя -e. Напр sed -e 's/\.pkg//g' -e 's/-.*//g'.
tacotuesday
Не анализируйте lsвывод и не заключайте переменные в кавычки. См. Также shellcheck.net для диагностики распространенных проблем и антипаттернов в сценариях оболочки.
Tripleee
1

Мне нужно *.txtбыло переименовать несколько файлов, как .sqlв одной папке. ниже работал у меня:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Кумар Машалкар
источник
2
Вы можете улучшить свой ответ, абстрагируя его от фактического варианта использования OP.
dakab
Это имеет несколько проблем, а также явную синтаксическую ошибку. Для начала см. Shellcheck.net .
tripleee
0

Спасибо за ответы. У меня тоже была какая-то проблема. Перемещение файлов .nzb из очереди в файлы .nzb. В именах файлов были пробелы и другой мусор, и это решило мою проблему:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Он основан на ответе Диомидиса Спинеллиса.

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

Джерри Джейкобс
источник
Следует отметить, что это верно только для GNU sed. В BSD sed не будет интерпретировать эту -nопцию таким же образом.
ocodo