2

Сценарий, приведенный ниже, помещает "подчеркивание" вместо "пробел" во всех именах файлов, которые находятся в определенной папке. У меня проблемы с созданием сценария оболочки, который помещает "подчеркивание" вместо "пробел" в имена всех подпапок и файлов, содержащихся в них, а не только в папке.

У кого-нибудь есть советы, как мне это сделать?

Вот мой код:

#!/bin/bash
ls | while read -r FILE; do
  mv -v "$FILE" `echo $FILE | tr ' ' '_'`
done

4 ответа4

3

Вместо этого используйте утилиту rename , например

rename "s/ /_/g" *

Сценарий переименования файлов на основе Perl с множеством полезных встроенных функций.


Для рекурсивного переименования попробуйте:

rename "s/ /_/g" **/*.*

где ** - опция глобализации Bash (включается с помощью shopt -s globstar).

В качестве альтернативы используйте find , например

find . -type f -execdir rename "s/ /_/g" {} ';'
3

Используйте find для поиска в dirs и subdirs:

while IFS='' read -r -d '' fname ; do
   nname="${fname##*/}"
   mv -v -n "${fname}"  "${fname%/*}/${nname//[[:space:]]/_}"
done < <(find "$(pwd)"  -name "* *" -type f  -print0)

find "$(pwd)" -type f -print0 - печатает все найденные пути к файлам в текущем каталоге и подкаталогах.

При подстановке процесса выходные данные команды find отправляются в цикл while, где она читает переменную fname.

nname="${fname##*/}" - извлекает имя файла из пути

"${fname%/*}" - извлекает путь

"${nname//[[:space:]]/"_"}" - заменяет пробелы в имени файла на _

"${fname%/*}/${nname//[[:space:]]/"_"}" - путь / новый_файл

0

В bash (не уверен в производных и sh !) Вы можете использовать подстановку переменных и т.д. в переменных

FILE="file name"
touch "$FILE"
mv -v "$FILE" "${FILE// /_/}"

ОДНАКО ЭТО, по крайней мере, не касается других пробелов (табуляция и т.д.)

0

Мой подход:

find . -depth -name "* *" -execdir bash -c 'pwd; for f in "$@"; do mv -nv "$f" "${f// /_}"; done' dummy {} +

Многострочная версия для удобства чтения:

find . -depth -name "* *" -execdir \
   bash -c '
      pwd
      for f in "$@"; do
          mv -nv "$f" "${f// /_}"
      done
   ' dummy {} +

Объяснение:

  • find . -name "* *" находит объекты, которые необходимо переименовать. Обратите внимание, что find очень гибок в своих тестах, поэтому, если вы хотите (например) переименовать только каталоги, начните с find . -depth -type d -name "* *" .
  • -execdir выполняет данный процесс (bash) в каталоге, в котором находится объект, поэтому любой путь, переданный {} , всегда имеет вид ./bar , а не ./foo/bar . Это означает, что нам не нужно заботиться обо всем пути. Недостатком является то, что mv -v не будет показывать вам путь, поэтому я добавил pwd только для информации (вы можете опустить его, если хотите).
  • bash позволяет нам использовать синтаксис "${baz// /_}" .
  • -depth гарантирует, что следующее не произойдет: команда find переименовывает каталог (если применимо), а затем пытается обработать его содержимое по старому пути.
  • {} + может кормить bash несколькими объектами (вопреки синтаксису {} \;). Мы перебираем их с помощью for f in "$@" . Дело не в том, чтобы запускать отдельный процесс bash для каждого объекта, поскольку создание нового процесса обходится дорого. Я думаю, что мы не можем легко избежать запуска отдельных mv s; тем не менее, сокращение количества вызовов bash кажется хорошей оптимизацией (pwd является встроенным в bash и не стоит нам процесса). Однако -execdir ... {} + не будет передавать файлы из разных каталогов вместе. Используя -exec ... {} + вместо -execdir ... {} + мы можем еще больше сократить количество процессов, но тогда нам нужно заботиться о путях, а не только о именах файлов (сравните этот другой ответ , кажется, делать достойную работу, но while read замедляет его). Это вопрос скорости против (относительной) простоты. Мое решение с -exec ниже.
  • dummy раз перед тем, как {} станет $0 внутри нашего bash . Нам нужен этот фиктивный аргумент, потому что "$@" эквивалентно "$1" "$2" ... (не "$0" "$1" ...). Таким образом, все, что передано {} , доступно позже как "$@" .

Более сложная, слегка оптимизированная версия (различные трюки ${...} взяты из другого ответа):

find . -depth -name "* *" -exec \
   bash -c '
      for f in "$@"; do
          n="${f##*/}"
          mv -nv "$f" "${f%/*}/${n// /_}"
      done
   ' dummy {} +

Другой (экспериментальный!) Подход предполагает vidir. Хитрость в том, что vidir использует $EDITOR который может не быть интерактивным редактором:

find . -name "* *" | EDITOR='sed -i s/\d32/_/g' vidir -

Предостережения:

  • Это не удастся для имен файлов / каталогов со специальными символами (например, новые строки).
  • Мы не можем использовать s/ /_/g напрямую, \d32 - это обходной путь.
  • Из-за того, как работает vidir , такой подход будет сложным, если вы захотите заменить цифру или табуляцию.
  • Здесь vidir работает с путями, а не только с именами файлов (базовыми именами), поэтому переименование только файлов (т.е. не каталогов) может быть затруднено.

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .