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

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