Пакетне перейменування файлів за допомогою 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
Я маю намір використовувати це регулярно, як сценарій одноразового використання - багато. Будь-яка система, яку я буду використовувати, матиме башти на ній, тому я не боюся башізмів, які дуже зручні.
Джеремі Л

У більш загальному дивіться також stackoverflow.com/questions/28725333 / ...
tripleee

Відповіді:


190

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

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

Цитати потрібні для імен файлів з пробілами.


1
Мені це теж подобається, але він використовує особливості bash ... Я зазвичай не використовую їх на користь "стандартних" ш.
Дієго Севілья

2
Для однорядних інтерактивних матеріалів, що викидаються, я завжди використовую це замість sed-fu. Якби це був сценарій, так, уникайте башизмів.
richq

Я знайшов одне питання з кодом: що трапиться, якщо файли вже були перейменовані, і я запускаю його знову? Я отримую застереження за спробу перейти до самого підкаталогу.
Джеремі Л

1
Щоб уникнути помилок повторного запуску, ви можете використовувати той самий шаблон у частині " .pkg", тобто "для i в * - [0-9.] .Pkg; зробіть mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; зроблено ". Але помилки досить нешкідливі (переміщення до одного файлу).
richq

3
Це не баш-рішення, але якщо у вас встановлено 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

Зверніть увагу , що в СЕД регулярний вираз групування послідовність дужки передує зворотної косої межі, \(і \), замість одиночних дужок (і ).


Пробачте мою наївність, але чи не уникнути дужок із зворотними косими рисами змусить їх ставитися як до буквальних символів?
devios1

2
У sed регулярна послідовність групування виразів є (а не однією дужкою.
Diomidis Spinellis

не працював для мене, буде радий отримати вашу допомогу .... Суфікс файлу FROM додається до файлу TO: $ find. -типу d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: перейменувати ./base/src/main/java/com/anysoftkeyboard/base/dic slova to ./base/src/main/java/com/effectedkeyboard/base/dic slova/dic slova : Немає такого файлу чи каталогу
Віталій Пом,

Здається, вам не вистачає зворотної косої риски в дужках.
Діомідіс Шпінеліс

1
Не використовуйте lsв сценаріях. Першого сорту можна досягти за допомогою, printf '%s\n' * | sed ...і з певною творчістю ви, можливо, sedтеж позбудетесь . Bash printfпропонує '%q'код формату, який може стати в нагоді, якщо у вас є назви файлів, які містять буквальні метахарактори оболонки.
трійка

10

Я зроблю щось подібне:

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

припускається, що весь ваш файл знаходиться в поточному каталозі. Якщо ні, спробуйте скористатися знахідкою, як порадив вище Хав'єр.

EDIT : Крім того, ця версія не використовує жодних базових функцій, як інші, що вище, що призводить до більшої портативності.


Усі вони в одному каталозі. Що ви думаєте про відповідь rq?
Джеремі Л

Мені подобається не використовувати функції bash, але натомість більш стандартні ш.
Дієго Севілья

1
Я припускав, що це було для швидкої перейменування, а не для написання крос-платформи наступного покоління, портативного Enterprise Application ;-)
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 -rабо sed -E залежно від вашої платформи.

Дивіться сумісність POSIX, man re_formatщоб отримати повний опис базових та розширених моделей регулярних викидів.


4

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

Для вашого прикладу я би зробив:

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

'S' означає замінник. Форма - s / searchPattern / заміна / files_to_apply. Для цього вам потрібно використовувати регулярний вираз, який потребує невеликого вивчення, але варто вартих зусиль.


Я згоден. Для випадкових речей використання forі sedт. Д. Прекрасно, але набагато простіше і швидше просто користуватися, renameякщо ви виявляєте, що це робите часто.
argentum2f

Це ти періоди втечі?
Великі гроші

Проблема в тому, що це не портативно; не тільки не всі платформи мають renameкоманду; крім того, деякі матимуть іншу rename команду з різними функціями, синтаксисом та / або параметрами. Це схоже на Perl, renameякий досить популярний; але відповідь справді повинна це прояснити.
трійка

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'.
такоту вівторок

Не аналізуйте lsвихід і не цитуйте змінні. Дивіться також shellcheck.net для діагностування поширених проблем та антишартов у скриптах оболонки.
трійка

1

У мене було кілька *.txtперейменованих файлів, як .sqlу тій же папці. нижче працював для мене:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done

2
Ви можете покращити свою відповідь, абстрагувавши її до фактичного випадку використання ОП.
дакаб

Це має безліч проблем, а також явну синтаксичну помилку. Дивіться shellcheck.net для початку.
трійка

0

Дякую за відповіді. У мене також були якісь проблеми. Переміщення файлів .nzb.queued у файли .nzb. У файлах файлів було пробіли та інша суть, і це вирішило мою проблему:

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

Він заснований на відповіді Діомідіс Шпінеліс.

Регекс створює одну групу для всього імені файлу та одну групу для частини перед .nzb.queued, а потім створює команду переміщення оболонки. З цитованими рядками. Це також уникає створення циклу в сценарії оболонки, оскільки це вже зроблено sed.


Слід зазначити, що це стосується лише GNU sed. На BSDs sed не буде трактувати -nваріант однаково.
окудо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.