Расширяющаяся звездочка в 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[@]}" \) или так).

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

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