Я допустил ошибку и свалил файлы в один каталог. К счастью, я могу отсортировать их по имени файла: ''' 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg ''' Где#немного или2в данном конкретном случае это информация о местоположении, так сказать. Диапазон составляет 0-9, и все имена файлов имеют одинаковую длину, так что это число всегда будет в одной и той же позиции имени файла (26-й символ, включая пробелы, с подчеркиваниями по бокам). Я нашел эту замечательную ссылку: команда для поиска файлов путем поиска только части их имен?
Однако я не могу передать вывод в команду move. Я попытался зациклить вывод в переменную, но это тоже не сработало:
for f in find . -name '*_0_*' ; do mv "$f" /destination/directory ; done
Судя по этой ссылке, я, возможно, поставил * или некоторые кавычки не в том месте:mv: невозможно stat Такого файла или каталога нет в скрипте оболочки.
Тем не менее, у меня много каталогов, и я хотел бы отсортировать их в идентичную структуру каталогов где-нибудь в другом месте:
-Flowers (to be sorted) -Flowers-buds -Flowers-stems
-Roses -Roses -Roses
buds.jpg ===> buds.jpg ===> stems.jpg
stems.jpg
petals.jpg
-Daisies -Daisies -Daisies
buds.jpgs buds.jpg stems.jpg
stems.jpg
petals.jpg
-Tulips -Tulips -Tulip
buds.jpgs buds.jpg stems.jpg
stems.jpg
petals.jpg
...и многое другое на основе этого числа (#). Это практично сделать в bash? Я запускаю MacOS в терминале с установленным coreutils, поэтому инструменты должны вести себя как GNU linux, а не darwin (BSD).
решение1
Вы можете сделать это с помощью find
и mv
, но это не самый простой подход. Вы на macOS, так чтозшпредустановлен. Zsh поставляется с удобным инструментом для массового переименования файлов, который называетсяzmv
. Запустите zsh
в терминале, затем в zsh выполните что-то вроде:
autoload zmv
zmv -n '[^_]#_([0-9])_*' '/elsewhere/${1}/${f}'
Пояснения:
-n
говоритzmv
, чтобы отображалось то, что он будет делать, но на самом деле ничего не делает. Когда вы будете довольны тем, что он показывает, запустите команду снова без-n
.- Первый невариантный аргумент — этошаблон подстановочных знаков.
zmv
будет действовать в отношении соответствующих файлов (и игнорировать несоответствующие файлы). [^_]#
означает ноль или более символов, отличных от_
. Zsh дает вам доступ к тем же подстановочным знакам, что и bash, и многим другим.#
— это функция, доступная только в zsh (в bash она доступна с другим синтаксисом), которая означает «любое количество предыдущих символов». Шаблон[^_]#_[0-9]_*
соответствует любому имени файла, содержащему одну цифру между двумя подчеркиваниями и без других подчеркиваний перед этой цифрой.- Скобки вокруг
[0-9]
делают цифру доступной, как$1
в заменяющем тексте. - В тексте замены можно использовать символы
${1}
,${2}
и т. д. для ссылки на заключенные в скобки фрагменты в исходном шаблоне, а также${f}
для ссылки на полное исходное имя.
Например, фрагмент выше перемещается 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg
в /elsewhere/2/2019-02-19 20.18.58.ndpi_2_2688_2240.jpg
. Ваш вопрос не описывает, что именно нужно переместить и куда именно, но вы должны иметь возможность получить то, что хотите, изменив команду выше. Если вы не можете понять, отредактируйте свой вопрос, добавив конкретные примеры того, что должно и не должно совпадать.
Если у вас есть файлы в подкаталогах, вы можете использовать */
в начале шаблона. Если вам нужно сопоставить каталог, чтобы ссылаться на него с помощью , вам нужно поместить скобки вокруг : вы не можете поместить скобки вокруг слеша. Если вы хотите пройти по каталогу рекурсивно и сопоставить файлы с любым долгом, используйте for${NUM}
*
**/
рекурсивная подстановка. В качестве исключения вам нужно использовать (**/)
для доступа к пути к каталогу в тексте замены как . В таких случаях часто проще не использовать скобки, а вместо этого использовать${NUM}
модификаторына ${f}
для извлечения частей исходного пути в целом. Например, выполните то же переименование, что и выше, но переместите файлы из текущего каталога в параллельную структуру под /elsewhere
, но с дополнительным уровнем 0
, 1
и т. д. непосредственно перед именем файла:
autoload zmv
zmv -n '**/[^_]#_([0-9])_*' '/elsewhere/${f:h}/${1}/${f:t}'
Если сложно сопоставить файлы по имени, можно использовать другой подход — сопоставить их по времени изменения¹. Если вы просто скопировали кучу файлов в каталог, который не менялся некоторое время, то новые файлы — это те, которые имеют недавнее время изменения. Вы можете сопоставить файлы по времени изменения в zsh с помощьюквалификатор globc
. Например, чтобы вывести список файлов, которые были изменены в последний раз за последний час:
ls -lctr *(ch-1)
Вместо того, чтобы искать время окончания, вы можете обратиться кНпоследние измененные файлы с квалификаторами glob oc
(для сортировки по возрастанию ctime) и (для сохранения только первых[1,N]
Н(соответствует). Например, если вы знаете, что только что переместили 42 файла в этот каталог и с тех пор ничего в нем не меняли:
ls -lctr *(oc[1,42])
Если вы хотите использовать квалификаторы glob с zmv
, вам нужно передать -Q
опцию. Например, чтобы переместить файлы в каталог на основе их имени, как указано выше, но игнорировать все файлы, которые не изменялись в течение последнего часа:
zmv -n -Q '[^_]#_([0-9])_*(ch-1)' '/elsewhere/${1}/${f}'
zmv
имеет некоторые меры безопасности для проверки того, что он не перезапишет файлы, и что нет никаких коллизий (два исходных файла с одинаковым именем назначения). Если у вас огромное количество файлов, эти меры безопасности могут занять время. Если zmv
слишком медленно и структура вашего переименования гарантирует, что не будет никаких коллизий, вы можете жестко закодировать конкретное переименование, которое вы делаете, в цикле. Zsh имеет тенденцию побеждать bash, даже если вы не используете, zmv
благодаря его более приятномуподстановкаирасширение параметрамеханизмы. Для производительности вы можетединамически загружатьмодуль, который содержитвстроенные функции для работы с файлами.
Например, чтобы переместить обычные файлы из текущего каталога в совершенно новую иерархию, если их имя содержит _0_
:
zmodload -m -F zsh/files 'b:zf_*'
for x in **/*_0_*(.); do
zf_mkdir -p /destination/directory/$x:h
zf_mv ./$x /destination/directory/$x
done
( :h
еще одна функция zsh:модификатор истории.)
Чтобы взять первое число, которое находится между двумя подчеркиваниями в имени файла, и поместить файлы в каталог, названный по этому числу, вам нужно извлечь это число. zmv
делает это автоматически для групп в скобках, здесь нам нужно сделать это вручную.
zmodload -m -F zsh/files 'b:zf_*'
for source in **/*_<->_*(.); do
suffix=${${source:t}#*_<->_}
n=${${source%$suffix}##*_}
destination=${source:h}/$n/${source:t}
zf_mkdir -p /destination/directory/$destination:h
zf_mv ./$source /destination/directory/$destination
done
Если вы хотите узнать о других методах массового переименования файлов, вы можете просмотретьвопросы, отмеченные rename
на этом сайте.
¹ Время изменения inode файла, называемое «временем изменения» или «ctime» для краткости, — это время последнего перемещения файла или последнего изменения его атрибутов, таких как разрешения. Оно отличается от времени изменения (mtime).
решение2
Оболочка разделяет ввод по пробелам. Вы можете использовать подстановку bash с рекурсивным, **
чтобы правильно разделить имена файлов:
shopt -s globstar
for f in **/*_0_*; do mv "$f" /dest/; done
Если все они направляются в одно и то же место, цикл не нужен:
shopt -s globstar
mv **/*_0_* /dest/
... или используйте find -exec
, который передает имена файлов напрямую из find в exec()
вызов, без участия оболочки:
find . -name '*_0_*' -exec mv {} /dest/ \;
Или используйте read
plus find -print0
для разделения по null. Это излишне для этой проблемы, полезно при глобализации и find -exec
не подходит для этой задачи:
while IFS= read -r -d ''; do mv "$REPLY" /dest/; done < <( find . -name '*_0_*' -print0 )
Чтобы изменить назначение верхнего уровня на основе имени файла, как в вашем примере, отрежьте части имени файла, используя ${var#remove_from_start}
и ${var%remove_from_end}
. Подстановочные знаки в шаблонах удаления удалят как можно меньше; чтобы удалить как можно больше, удвойте символ операции, как в ${…##…}
или ${…%%…}
:
shopt -s globstar
for f in **/*_0_*; do # Flowers/Roses/stems.jpg
file=${f##*/} # stems.jpg
base=${file%.*} # stems
path=${f%/*} # Flowers/Roses
toppath=${path%%/*} # Flowers
subpath=${path#*/} # Roses
dest=$toppath-$base/$subpath/ # Flowers-stems/Roses/
[[ -d $dest ]] || mkdir -p "$dest"
mv "$f" "$dest"
done