2015-02-24 4 views
3

Я пытаюсь запустить find и исключить несколько каталогов, перечисленных в массиве. Я нахожу некоторые странное поведение, когда она расширяется, хотя, что вызывает у меня вопросы:Расширяемая звездочка в bash

~/tmp> skipDirs=("./dirB" "./dirC") 
~/tmp> bars=$(find . -name "bar*" -not \(-path "${skipDirs[0]}/*" $(printf -- '-o -path "%s/\*" ' "${skipDirs[@]:1}") \) -prune); echo $bars 
./dirC/bar.txt ./dirA/bar.txt 

Это не пропустить dirC как я Уолд ожидал. Проблема заключается в том, что печать расширяет котировки вокруг "./dirC".

~/tmp> set -x 
+ set -x 
~/tmp> bars=$(find . -name "bar*" -not \(-path "${skipDirs[0]}/*" $(printf -- '-o -path "%s/*" ' "${skipDirs[@]:1}") \) -prune); echo $bars 
+++ printf -- '-o -path "%s/*" ' ./dirC 
++ find . -name 'bar*' -not '(' -path './dirB/*' -o -path '"./dirC/*"' ')' -prune 
+ bars='./dirC/bar.txt 
./dirA/bar.txt' 
+ echo ./dirC/bar.txt ./dirA/bar.txt 
./dirC/bar.txt ./dirA/bar.txt 

Если я пытаюсь удалить кавычки в $(print..), то * получает расширена сразу, что также дает неправильные результаты. Наконец, если я удалю кавычки и попытаюсь избежать *, тогда escape-символ \ будет включен как часть имени файла в поиске, и это тоже не сработает. Мне интересно, почему это не работает, и что будет работать? Я стараюсь избегать использования eval, если это возможно, но в настоящее время я не вижу пути вокруг него.

Примечание: Это очень похоже на: Finding directories with find in bash using a exclude list, однако опубликованные решения этого вопроса, похоже, имеют проблемы, перечисленные выше.

+0

Небольшое изменение той же проблемы http://stackoverflow.com/q/28682013/45375 – mklement0

ответ

5

Сейф подход для построения массива в явном виде:

#!/bin/bash 

skipdirs=("./dirB" "./dirC") 

skipdirs_args=(-false) 
for i in "${skipdirs[@]}"; do 
    args+=(-o -type d -path "$i") 
done 

find . \! \(\("${skipdirs_args[@]}" \) -prune \) -name 'bar*' 

Я немного изменить логику в вашей находкой, так как у вас была небольшая ошибка (логика) в есть: ваша команда была:

find -name 'bar*' -not stuff_to_prune_the_dirs 

Как работает find? он будет анализировать дерево файлов и когда он найдет файл (или каталог), который соответствует bar*, тогда он применит часть -not .... Это действительно не то, что вы хотите! ваш -prune никогда не будет применяться!

Посмотрите на это вместо:

find . \! \(-type d -path './dirA' -prune \) 

Здесь find полностью подрезать каталог ./dirA и распечатать все остальное. Теперь это все, что вы хотите применить фильтр -name 'bar*'! заказ очень важен! есть большая разница между этим:

find . -name 'bar*' \! \(-type d -path './dirA' -prune \) 

и это:

find . \! \(-type d -path './dirA' -prune \) -name 'bar*' 

Первый не работает, как ожидается, на всех! Второй - в порядке.

Примечания.

  • Я использую \! вместо -not в \! является POSIX, -not это расширение не указано POSIX. Вы будете утверждать, что -path не является POSIX, поэтому не имеет значения, использовать -not. Это деталь, используйте все, что вам нравится.
  • Вам пришлось использовать какой-то грязный трюк, чтобы создавать свои команды, чтобы пропустить ваш каталог, так как вам приходилось рассматривать первый термин отдельно от другого. Путем инициализации массива с -false, я не должен рассматривать какие-либо термины специально.
  • Я указываю -type d, так что я уверен, что обрезаю каталоги.
  • Поскольку моя обрезка действительно относится к каталогам, мне не нужно включать подстановочные знаки в мои условия исключения. Это смешно: ваша проблема, которая, по-видимому, связана с подстановочными знаками, которые вы не можете обработать, полностью исчезает, когда вы используете find соответственно, как описано выше.
  • Конечно, метод, который я дал, действительно применяется с подстановочными знаками. Например, если вы хотите, чтобы исключить/подрезать все подкаталоги называемых baz внутри подкаталогов называемых foo, в skipdirs массива дается

    skipdirs=("./*/foo/baz" "./*/foo/*/baz") 
    

    будет работать нормально!

4

Проблема в том, что котировки, которые вы используете на "%s/*", не делают то, что вы думаете.

То есть вы считаете, что вам нужны котировки на "%s/*", чтобы предотвратить появление результатов из printf, однако это не то, что происходит. Попробуйте одно и то же без разделителя каталогов и с файлами, которые начинаются и заканчиваются двойными кавычками, и вы увидите, что я имею в виду.

$ ls 
"dirCfoo" 
$ skipDirs=("dirB" "dirC") 
$ printf '%s\n' -- -path "${skipDirs[0]}*" $(printf -- '-o -path "%s*" ' "${skipDirs[@]:1}") 
-path 
dirB* 
-o 
-path 
"dirCfoo" 
$ rm '"dirCfoo"' 
$ printf -- '%s\n' -path "${skipDirs[0]}*" $(printf -- '-o -path "%s*" ' "${skipDirs[@]:1}") 
-path 
dirB* 
-o 
-path 
"dirC*" 

Посмотрите, что я имею в виду? Кавычки не обрабатываются специально оболочкой. Просто случается, что вам не нравится.

Этот вопрос является частью того, почему такие вещи, как то, что обсуждается в http://mywiki.wooledge.org/BashFAQ/050, не работают.

Чтобы сделать то, что вы хотите здесь, я считаю, что вам нужно создать массив аргументов find вручную.

sD=(-path /dev/null) 
for dir in "${skipDirs}"; do 
    sD+=(-o -path "$dir") 
done 

, а затем расширить "$ {Sd [@]}" на find командной строки (-not \("${sD[@]}" \) или около того).

И да, я считаю, что это делает ответ вы связаны с неправильным (хотя другой ответ мог бы работать (для непробельных и т.д. файлы) из-за косвенности массива, что происходит.

+1

Престижность для объяснения квотирование вопроса. Позвольте мне попробовать резюме: Подстановка команды _unquoted_, поэтому ее вывод подлежит расширению оболочки, включая подталкивание. Двойные кавычки становятся _literal_ частью жетонов, которые они заключают, - они не анализируются как строка с двойными кавычками оболочкой (это может сделать только 'eval'). Итак, globbing _is_ применяется к _literal_ '" * "', что будет _typically_ не соответствовать никому (так как большинство имен файлов не заключены в буквальные двойные кавычки). Что в конечном итоге идет не так, так это то, что буквальные двойные кавычки передаются в 'find' _as часть аргумента_. – mklement0

Смежные вопросы