Поиск каталогов с помощью команды find в bash с использованием списка исключений
Теперь, прежде чем думать, "это было сделано раньше", пожалуйста, продолжайте читать.
Как и большинство людей, пытающихся сделать скрипт find bash, вы в конечном итоге жестко программируете скрипт в однострочной команде, но заканчиваете тем, что редактировали его в течение следующих месяцев / лет так часто, что вам хотелось бы, чтобы в итоге вы сделали все правильно первый раз.
Я пишу небольшую программу резервного копирования прямо сейчас, чтобы сделать резервные копии каталогов и нужно найти их, по списку каталогов, которые должны быть исключены. Проще сказать, чем сделать. Позвольте мне установить сцену:
#!/bin/bash
BasePath="/home/adesso/baldar"
declare -a Iggy
Iggy=( "/cgi-bin"
"/tmp"
"/test"
"/html"
"/icons" )
IggySubdomains=$(printf ",%s" "${Iggy[@]}")
IggySubdomains=${IggySubdomains:1}
echo $IggySubdomains
exit 0
Теперь в конце этого вы получите /cgi-bin,/tmp,/test,/html,/icons. Это доказывает, что концепция работает, но теперь, чтобы продвинуться немного дальше, мне нужно использовать find для поиска BasePath и поиска только один уровень глубоко для всех подкаталогов и исключить список подкаталогов в массиве...
Если я наберу это вручную, это будет:
find /var/www/* \( -path '*/cgi-bin' -o -path '*/tmp' -o -path '*/test' -o -path '*/html' -o -path '*/icons' \) -prune -type d
И если мне захочется войти в каждый подкаталог и сделать то же самое... Надеюсь, вы поняли мою точку зрения.
То, что я пытаюсь сделать, кажется возможным, но у меня есть небольшая проблема, printf ",% s" не нравится мне, используя все эти опции find -path или -o. Значит ли это, что мне нужно снова использовать eval?
Я пытаюсь использовать силу bash здесь, а не некоторые для цикла. Любой конструктивный вклад будет оценен.
3 ответа
Попробуйте что-то вроде
find /var/www/* \( -path "${Iggy[0]}" $(printf -- '-o -path "*%s" ' "${Iggy[@]:1}") \) -prune -type d
и посмотрим, что получится.
РЕДАКТИРОВАТЬ: добавил ведущий * для каждого пути, как в вашем примере.
И вот полное решение, основанное на вашем описании.
#!/usr/bin/env bash
basepath="/home/adesso/baldar"
ignore=("/cgi-bin" "/tmp" "/test" "/html" "/icons")
find "${basepath}" -maxdepth 1 -not \( -path "*${ignore[0]}" $(printf -- '-o -path "*%s" ' "${ignore[@]:1}") \) -not -path "${basepath}" -type d
Подкаталоги $basepath, исключая перечисленные в $ignore, предполагают, что по крайней мере два в $ignore (исправление несложно).
Существующие ответы являются ошибочными, если даны имена каталогов, которые содержат буквенные пробелы. Безопасной и надежной практикой является использование петли. Если ваша задача использовать "мощь bash" - я бы сказал, что надежное решение является более мощным, чем ошибочное.:)
BasePath="/home/adesso/baldar"
declare -a Iggy=( "/cgi-bin" "/tmp" "/test" "/html" "/icons" )
find_cmd=( find "$BasePath" '(' )
## This is the conventional approach:
# for x in "${Iggy[@]}"; do
# find_cmd+=( -path "*${x}" -o )
#done
## This is the unconventional, only-barely-safe approach
## ...used only to avoid looping:
printf -v find_cmd_str ' -path "*"%q -o ' "${Iggy[@]}"
find_cmd_str=${find_cmd_str%" -o "}
eval "find_cmd+=( $find_cmd_str )"
find_cmd=( "${find_cmd[@]:0:${#find_cmd[@]} - 1}"
# and add the suffix
find_cmd+=( ')' -prune -type d )
# ...finally, to run the command:
"${find_cmd[@]}"
FIND="$(which find --skip-alias)"
BasePath="/home/adesso/baldar"
Iggy=( "/cgi-bin"
"/tmp"
"/test"
"/html"
"/icons" )
SubDomains=( $(${FIND} ${BasePath}/* -maxdepth 0 -not \( -path "*${Iggy[0]}" $(printf -- '-o -path "*%s" ' "${Iggy[@]:1}") \) -type d) )
echo ${SubDomains[1]}
Благодаря @Sorpigal у меня есть решение. В итоге я вложил подстановку команд, чтобы использовать скрипт в cron, и, наконец, добавил определение Array для всего этого. Известной проблемой может быть каталог, содержащий пробел в имени. Это, однако, было решено, поэтому, стараясь сделать его простым, я думаю, что это отвечает на мой вопрос.