Мой подход:
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
в общем случае, хотя. Я включил его в свой ответ, потому что я нашел этот экспериментальный подход интересным.