Подстановочный знак: путь возврата

Я пытаюсь переименовать каждый access.log.gz файл (примечание: точное имя файла, а не "начинающийся с" или "содержащий"), найденный в /home/*/logs в date-access.log.gz в той же папке.

for file in /home/*/logs/access.log.gz
do
   mv -v "$file" /home/*/logs/old/`date +\%G-\%m-\%d`"-access.log.gz"
done

Конечно получаю /home/*/logs/2014-09-08-access.log.gz - No such file or directory, Как я могу позвонить * пути назад? Каждый файл должен быть переименован и перемещен в /old/ subdir исходной папки. Я пробовал с

mv -v "$file" /home/`$1`/logs/old/`date +\%G-\%m-\%d`"-access.log.gz"

... но не сработало.

3 ответа

Решение

Использование find ( Как я могу использовать команду find более эффективно?).

find /home/ -ipath */logs/access.log.gz

Это должно найти все интересующие вас файлы и распечатать их. Вы можете включить это в свой сценарий, но, вероятно, легче find сделать всю тяжелую работу.

find /home/ -ipath */logs/access.log.gz -execdir mv "{}" "old/`date +\%G-\%m-\%d`-access.log.gz" \;

Особенности find мы используем здесь:

  • -ipath соответствует шаблонам в найденных именах файлов. Вы также можете использовать -name если вы точно знали, что в каждом подпути есть только один файл access.log.gz (-name access.log.gz).
  • -execdir выполняет данную команду для каждого найденного файла из подкаталога, содержащего соответствующий файл. Это эквивалентно выполнению cd где каждый файл, а затем запустив команду там. В этом случае это экономит много логики манипуляции пути. Найденный файл заменен на {}. Также обратите внимание, что "старый" задается как относительный путь (как вы сказали, что old/ существует в logs/)

Просто разберите и соберите $file когда вы перебираете подходящие файлы: ${file%/*} дает вам файл $ со всем после последнего / удалены, и ${file##*/} дает вам файл $ со всем до последнего / удален; вместе взятые ${file%/*}/old/$date-${file##*/} даст вам "новое" имя файла, соответствующее $file,

Предположительно, дата не изменится в ходе выполнения вашего скрипта, поэтому нет смысла вызывать date для каждого файла; скорее, сохраните дату в переменной один раз, а затем используйте ее во время цикла: date=$(date '+%G-%m-%d'), (Обратите внимание, что современные сценарии должны использовать $(...) а не клюшки. Также обратите внимание, что % не является "специальным" символом и поэтому не нуждается в кавычках или экранировании, хотя в качестве хорошей практики вся строка формата должна быть заключена в кавычки.)

Если у вас есть новая версия Bash, вы можете полностью избежать подоболочки, используя printf -v date '%(%G-%m-%d)T' -1 вместо.

Вы также можете найти полезным использовать shopt -s globstar так что вы можете искать на произвольной глубине в каталогах, и shopt -s nullglob так что непревзойденный glob (подстановочный знак) расширяется до "ничего" вместо исходного шаблона glob.

Собрав все это вместе, мы получим:

shopt -s globstar nullglob
date=$( date '+%G-%m-%d' )
for file in /home/*/logs/**/access.log.gz
do
    target=${file%/*}/old/$date-${file##*/}
    mv -vi "$file" "$target"
done

Если у вас установлен zsh, используйте zmv, В скрипте:

#!/bin/zsh
autoload zmv
zmv '/home/*/logs/access.log.gz' '$f:h/`date +\%G-\%m-\%d`-$f:t'

В командной строке zsh введите autoload zmv в вашем ~/.zshrc и запустите эту последнюю строку в командной строке.

Другие вопросы по тегам