Ограничить POSIX найти на определенной глубине?

15

Недавно я заметил, что спецификации POSIX дляfind не включают -maxdepthосновной.

Для тех, кто не знаком с ним, цель -maxdepthпервичного состоит в том, чтобы ограничить, сколько уровней глубоко findопустится. -maxdepth 0приводит к обработке только аргументов командной строки; -maxdepth 1будет обрабатывать результаты только внутри аргументов командной строки и т. д.

Как я могу получить поведение, эквивалентное -maxdepthпервичному, не относящемуся к POSIX, используя только указанные POSIX опции и инструменты?

(Примечание: конечно, я могу получить эквивалент -maxdepth 0, просто используя -pruneв качестве первого операнда, но это не распространяется на другие глубины.)

Wildcard
источник
@StevenPenny, подход FreeBSD -depth -2, -depth 1... можно рассматривать как лучший, чем подход GNU -maxdepth/-mindepth
Стефан Шазелас
@ StéphaneChazelas в любом случае - POSIX find должен иметь один или другой; еще это покалечено
Стивен Пенни
1
По крайней мере для -maxdepth/ -mindepthесть разумные альтернативы (обратите внимание, что -pathэто недавнее дополнение к POSIX). Альтернативы для -timexyили -mtime -3m(или -mmin -3) намного более громоздки. Некоторые любят -execdir/ не -deleteимеют надежной альтернативы.
Стефан Шазелас
2
@ StevenPenny, не стесняйтесь регистрировать тикет на austingroupbugs.net, чтобы запросить его добавление. Я видел, как что-то добавлялось без необходимости спонсора, когда было веское оправдание. , Вероятно , лучше курс действий будет получить как много реализаций добавить первую так POSIX бы просто указать существующую , который , как правило , менее спорным.
Стефан Шазелас
@ StéphaneChazelas в моем случае я просто назвал файлы напрямую, но спасибо; Я мог бы подать билет, если это произойдет снова
Стивен Пенни

Ответы:

7

Вы можете использовать, -pathчтобы соответствовать заданной глубине и обрезать там. Например

find . -path '*/*/*' -prune -o -type d -print

будет maxdepth 1, как *соответствует ., */*соответствует ./dir1и */*/*соответствует, ./dir1/dir2который сокращается. Если вы используете абсолютный начальный каталог, вам нужно добавить ведущий /к нему -path.

meuh
источник
Хм, сложно. Не могли бы вы просто удалить один слой /*из конца шаблона, вынуть -oоператор и получить тот же результат?
Wildcard
Нет, потому что *совпадает /, так что режиссер a/b/c/d/eподойдет -path */*, к сожалению.
Мех
Но a/b/c/d/eникогда не будет достигнуто , потому -pruneчто будет применяться к a/b....
Wildcard
1
Извините, я неправильно прочитал это -pruneи -oбыл удален. Если вы сохраняете -pruneпроблему, проблема заключается в том, что */*они не будут сопоставляться с чем-либо на уровне выше maxdepth, например, с одним каталогом a.
Мех
11

Подход @ meuh неэффективен, так как он -maxdepth 1все еще позволяет findчитать содержимое каталогов на уровне 1, чтобы впоследствии игнорировать их в противном случае. Он также не будет работать должным образом с некоторыми findреализациями (включая GNU find), если некоторые имена каталогов содержат последовательности байтов, которые не образуют допустимые символы в локали пользователя (например, для имен файлов в другой кодировке символов).

find . \( -name . -o -prune \) -extra-conditions-and-actions

это более канонический способ реализации GNU -maxdepth 1(или FreeBSD -depth -2).

Однако, как правило, -depth 1вы хотите ( -mindepth 1 -maxdepth 1), поскольку вы не хотите учитывать .(глубина 0), и тогда это еще проще:

find . ! -name . -prune -extra-conditions-and-actions

Ибо -maxdepth 2это становится:

find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

И вот где вы бежите в недопустимых проблемах характера.

Например, если у вас есть каталог с именем, Stéphaneно éон закодирован в кодировке iso8859-1 (он же latin1) (0xe9 байт), как это было наиболее распространенным в Западной Европе и Америке до середины 2000-х годов, то этот байт 0xe9 не является допустимый символ в UTF-8. Таким образом, в локалях UTF-8 *подстановочный знак (с некоторыми findреализациями) не будет совпадать, так Stéphaneкак *равен 0 или более символов, а 0xe9 не является символом.

$ locale charmap
UTF-8
$ find . -maxdepth 2
.
./St?phane
./St?phane/Chazelas
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith
$ find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St?phane/Chazelas/age
./St?phane/Chazelas/gender
./St?phane/Chazelas/address
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith

Мой find(когда вывод идет на терминал) отображает этот недопустимый байт 0xe9, как ?указано выше. Вы можете видеть, что St<0xe9>phane/Chazelasне было pruned.

Вы можете обойти это, выполнив:

LC_ALL=C find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

Но обратите внимание, что это влияет на все настройки локали findи любого приложения, которое он запускает (например, с помощью -execпредикатов).

$ LC_ALL=C find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St??phane
./St??phane/Chazelas
./John
./John/Smith

Теперь я действительно понимаю, -maxdepth 2но обратите внимание на то, ??как символ é во втором Стефане, правильно закодированный в UTF-8, отображается как байты 0xc3 0xa9 (рассматриваемые как два отдельных неопределенных символа в локали C) в кодировке é UTF-8. непечатаемые символы в локали C.

И если бы я добавил a -name '????????', я бы получил неправильный Стефан (тот, который закодирован в iso8859-1).

Чтобы применить к произвольным путям вместо ., вы должны сделать:

find some/dir/. ! -name . -prune ...

для -mindepth 1 -maxdepth 1или:

find some/dir/. \( ! -path '*/./*/*' -o -prune \) ...

для -maxdepth 2.

Я бы все равно сделал:

(cd -P -- "$dir" && find . ...)

Во-первых, потому что это делает пути короче, что снижает вероятность появления слишком длинных путей или слишком длинных списков аргументов, но также помогает обойти тот факт, что findне может поддерживать произвольные аргументы пути (кроме как -fс FreeBSD find), так как он будет подавлен значения $dirкак !или -print...


-oВ сочетании с отрицанием является обычным трюком для запуска двух независимых наборов -condition/ -actionв find.

Если вы хотите запускать -action1на собрании файлов -condition1и независимо -action2на собрании файлов -condition2, вы не можете сделать:

find . -condition1 -action1 -condition2 -action2

Как -action2будет запускаться только для файлов, которые удовлетворяют обоим условиям.

Также:

find . -contition1 -action1 -o -condition2 -action2

Как -action2не будет работать для файлов, которые отвечают обоим условиям.

find . \( ! -condition1 -o -action1 \) -condition2 -action2

работает так же, как \( ! -condition1 -o -action1 \)и для истины для каждого файла. Это предполагает -action1, что действие (например -prune, -exec ... {} +) всегда возвращает true . Для таких действий -exec ... \;может возвращаться значение false , вы можете добавить другое, -o -somethingгде -somethingэто безопасно, но возвращает true, как -trueв GNU findили -links +0или -name '*'(хотя обратите внимание на проблему с недопустимыми символами выше).

Стефан Шазелас
источник
1
Когда-нибудь я столкнусь с кучей китайских файлов и буду очень рад, что прочитал ваши многочисленные ответы о локали и действительных символах. :)
Wildcard
2
@Wildcard, вы (и даже в большей степени китаец) чаще сталкиваетесь с именами файлов на британском, французском ..., чем с именами файлов на китайском языке, так как имена файлов на китайском языке чаще кодируются в UTF-8, чем имена файлов алфавитных скриптов это обычно может быть покрыто однобайтовой кодировкой, которая была нормой до относительно недавнего времени. Существуют и другие многобайтовые кодировки, которые охватывают китайский иероглиф, но я ожидаю, что китайцы перешли бы на UTF-8 раньше, чем жители Запада, поскольку у этих кодировок есть ряд неприятных проблем. Смотрите также редактирование для примера.
Стефан Шазелас
0

Я столкнулся с проблемой, когда мне нужен был способ ограничения глубины при поиске по нескольким путям (а не просто .).

Например:

$ find dir1 dir2 -name myfile -maxdepth 1

Это привело меня к альтернативному подходу с использованием -regex. Суть это:

-regex '(<list of paths | delimited>)/<filename>'

Итак, вышесказанное будет:

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/myfile' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/myfile' # MacOS BSD

Без имени файла:

$ find dir1 dir2 -name myfile -maxdepth 1 # GNU

-regex '(<list of paths | delimited>)/<anything that's not a slash>$'

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/[^/]*$' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/[^/]*$' # MacOS BSD

Наконец, для -maxdepth 2регулярного выражения изменяется на:'(dir1|dir2)/([^/]*/){0,1}[^/]*$'

Алисса Х
источник
1
Этот вопрос требует стандартного (как в POSIX) решения. Также -maxdepthбудет работать с несколькими путями поиска.
Кусалананда