Расширяющаяся звездочка в bash
Я пытаюсь бежать find
и исключить несколько каталогов, перечисленных в массиве. Я нахожу странное поведение при расширении, которое вызывает у меня проблемы:
~/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-символ включается как часть имени файла в find, и это тоже не работает. Мне интересно, почему выше не работает, и что будет работать? Я пытаюсь избежать использования eval
если возможно, но в настоящее время я не вижу пути обойти это.
Примечание. Это очень похоже на: поиск каталогов с помощью find в bash с использованием списка исключений, однако опубликованные решения этого вопроса, похоже, имеют проблемы, перечисленные выше.
2 ответа
Безопасный подход заключается в явном построении массива:
#!/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" )
будет работать нормально!
Проблема здесь в том, что цитаты, которые вы используете на "%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, не работают.
Чтобы сделать то, что вы хотите здесь, я считаю, что вам нужно создать массив аргументов поиска вручную.
sD=(-path /dev/null)
for dir in "${skipDirs}"; do
sD+=(-o -path "$dir")
done
а затем разверните "${sD[@]}" на find
командная строка (-not \( "${sD[@]}" \)
или так).
И да, я считаю, что это делает ответ, на который вы ссылаетесь, неправильным (хотя другой ответ может сработать (для файлов без пробелов и т. Д.) Из-за происходящей косвенности массива.