Как использовать опцию «-prune» для «find» в sh?

219

Я не совсем понимаю пример, приведенный в man findстатье. Может ли кто-нибудь дать мне несколько примеров и объяснений? Можно ли в нем объединить регулярное выражение?


Более подробный вопрос таков:

Написать сценарий оболочки changeall, который имеет интерфейс, как changeall [-r|-R] "string1" "string2". Он найдет все файлы с суффиксом .h, .C, .ccили .cppи изменить все вхождения string1в string2. -rОпция для того, чтобы оставаться только в текущем каталоге или включать его в.

НОТА:

  1. В нерекурсивном случае lsНЕ допускается, мы можем использовать только findи sed.
  2. Я пытался, find -depthно это не было поддержано. Вот почему я задавался вопросом, -pruneможет ли помочь, но не понял пример из man find.

РЕДАКТИРОВАТЬ 2: Я делал задание, я не задавал вопрос в больших деталях, потому что я хотел бы закончить его сам. Поскольку я уже сделал это и сдаю, теперь я могу сформулировать весь вопрос. Кроме того, мне удалось закончить задание без использования -prune, но все равно хотел бы изучить его.

derrdji
источник

Ответы:

438

Что меня смущает, так -pruneэто то, что это действие (как -print), а не тест (как -name). Он изменяет список «дел», но всегда возвращает true .

Общий шаблон для использования -pruneтаков:

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

Вы почти всегда хотите -o(логическое ИЛИ) сразу после -prune, потому что эта первая часть теста (вплоть до включительно -prune) вернет false для того, что вам действительно нужно (то есть: того, что вы не хотите исключать).

Вот пример:

find . -name .snapshot -prune -o -name '*.foo' -print

Это найдет файлы "* .foo", которые не находятся в каталогах ".snapshot". В этом примере -name .snapshotсоставляет [conditions to prune], и -name '*.foo' -printесть [your usual conditions]и [actions to perform].

Важные замечания :

  1. Если все, что вы хотите сделать, это распечатать результаты, которые вы могли бы использовать для пропуска -printдействия. Как правило, вы не хотите делать это при использовании -prune.

    Поведение поиска по умолчанию - «и» все выражение с -printдействием, если -pruneв конце нет никаких действий, кроме (по иронии судьбы). Это означает, что написание этого:

    find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS

    эквивалентно написанию этого:

    find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS

    Это означает, что он также распечатает имя каталога, который вы удаляете, что обычно не то, что вы хотите. Вместо этого лучше явно указать -printдействие, если вы этого хотите:

    find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
  2. Если ваше «обычное состояние» совпадает с файлами, которые также соответствуют вашему состоянию обрезки, эти файлы не будут включены в выходные данные. Это можно исправить, добавив -type dпредикат в условие обрезки.

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

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS

    Это не будет включать .gitignoreв вывод. Вот исправленная версия:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS

Дополнительный совет: если вы используете версию GNU find, страница texinfo для findсодержит более подробное объяснение, чем ее man-страница (как это верно для большинства утилит GNU).

Лоуренс Гонсалвес
источник
6
это не на 100% очевидно в вашем тексте (но поскольку вы печатаете только '* .foo', это не конфликтует), но часть -prune также не будет печатать ничего (не только каталоги) с именем ".snapshot". то есть -pruneне только работает с каталогами (но для каталогов он также предотвращает вход в каталоги, соответствующие этому условию, то есть здесь каталоги, соответствующие этому -name .snapshot).
Оливье Дюлак
12
и +1 для вас за хорошо сделанное объяснение (и особенно важное примечание). Вы должны сообщить об этом разработчикам поиска (так как на странице руководства не объясняется «чернослив» для нормальных людей ^^ Мне потребовалось много попыток, чтобы понять это, и я не увидел побочного эффекта, о котором вы нас предупреждали)
Оливье Дюлак
2
@OlivierDulac Это очень хороший момент, когда вы можете удалить файлы, которые хотите сохранить. Я обновил ответ, чтобы уточнить это. -pruneкстати, это не само по себе. Проблема в том, что оператор или «замыкает накоротко», и / или имеет более низкий приоритет, чем и. Конечным результатом является то, что если файл с именем вызван, .snapshotон будет соответствовать первому -name, -pruneзатем ничего не будет делать (но вернет true), а затем или возвращает true, поскольку его левый аргумент был true. Действие (например:) -printявляется частью его второго аргумента, поэтому у него никогда нет шансов выполнить.
Лоуренс Гонсалвес
3
+1 наконец выяснил, зачем мне -printв конце, теперь я могу перестать добавлять \! -path <pattern>в дополнение к-prune
Miserable Variable
6
Обратите внимание, что «-o» - это сокращение от «-or», которое (хотя и не POSIX-совместимое) читается более четко.
йо
27

Обычно родной способ, которым мы делаем вещи в Linux, и способ, которым мы думаем, слева направо.
Таким образом, вы бы сначала написать и написать то, что вы ищете:

find / -name "*.php"

Затем вы, вероятно, нажали Enter и поняли, что получаете слишком много файлов из каталогов, которые вы не хотите. Давайте исключим / media, чтобы избежать поиска ваших подключенных дисков.
Теперь вы должны просто добавить следующее к предыдущей команде:

-print -o -path '/media' -prune

итоговая команда:

find / -name "*.php" -print -o -path '/media' -prune

............... | <--- Включить ---> | .................... | <- -------- Исключить ---------> |

Я думаю, что эта структура намного проще и соответствует правильному подходу

AmitP
источник
3
Я не ожидал бы, что это будет эффективно - я бы подумал, что он сначала оценит левое предложение перед черносливом, но, к моему удивлению, быстрый тест, кажется, предполагает, что findон достаточно умен, чтобы -pruneсначала обработать предложение. Хм, интересно.
artfulrobot
Я никогда не думал, что за почти десять лет использования GNU найти! Спасибо вам за это! Это определенно изменит то, о чем я думаю -pruneс этого момента .
Фелипе Альварес
3
@artfulrobot Это действительно сначала обрабатывает? Я бы подумал, что он входит /media, заметив, что он не *.phpвызывается, и затем проверил, находится ли он в данный момент внутри /media, увидев, что он есть, и, следовательно, пропустив все это поддерево. Это все еще слева направо, это просто не имеет значения, пока обе проверки не пересекаются.
PhK
26

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

$ find . -printf "%y %p\n"    # print the file type the first time FYI
d .
f ./test
d ./dir1
d ./dir1/test
f ./dir1/test/file
f ./dir1/test/test
d ./dir1/scripts
f ./dir1/scripts/myscript.pl
f ./dir1/scripts/myscript.sh
f ./dir1/scripts/myscript.py
d ./dir2
d ./dir2/test
f ./dir2/test/file
f ./dir2/test/myscript.pl
f ./dir2/test/myscript.sh

$ find . -name test
./test
./dir1/test
./dir1/test/test
./dir2/test

$ find . -prune
.

$ find . -name test -prune
./test
./dir1/test
./dir2/test

$ find . -name test -prune -o -print
.
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

$ find . -regex ".*/my.*p.$"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test/myscript.pl

$ find . -name test -prune -regex ".*/my.*p.$"
(no results)

$ find . -name test -prune -o -regex ".*/my.*p.$"
./test
./dir1/test
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test

$ find . -regex ".*/my.*p.$" -a -not -regex ".*test.*"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py

$ find . -not -regex ".*test.*"                   .
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2
Приостановлено до дальнейшего уведомления.
источник
если вы также «touch ./dir1/scripts/test» (т. е. у вас есть «тестовый» файл, а не dir, в этом распечатанном subdir), он не будет напечатан с помощью find . -name test -prune -o -print: iow, -pruneэто действие, которое также работает над файлами
Оливье Дюлак
10

Добавление к советам, данным в других ответах (у меня нет представителя для создания ответов) ...

При объединении -pruneс другими выражениями в поведении есть небольшая разница в зависимости от того, какие выражения используются.

Пример @Laurence Gonsalves найдет файлы "* .foo", которые не находятся в каталогах ".snapshot": -

find . -name .snapshot -prune -o -name '*.foo' -print

Тем не менее, это несколько другое сокращение, возможно, непреднамеренно, также перечислит .snapshotкаталог (и любые вложенные каталоги .snapshot): -

find . -name .snapshot -prune -o -name '*.foo'

Причина (в соответствии с man-страницей в моей системе): -

Если данное выражение не содержит ни одного из основных параметров -exec, -ls, -ok или -print, данное выражение эффективно заменяется на:

(данное_экспрессия) -принт

То есть второй пример является эквивалентом ввода следующего, тем самым изменяя группировку терминов:

find . \( -name .snapshot -prune -o -name '*.foo' \) -print

По крайней мере, это было видно на Solaris 5.10. Пользуясь различными разновидностями * nix около 10 лет, я только недавно искал причину, по которой это происходит.

CRW
источник
Спасибо, что обратили внимание на разницу между использованием -pruneс и без -print!
McW
3

Чернослив не рекурсивно при любом переключении каталога.

Со страницы руководства

Если -depth не задано, верно; если файл является каталогом, не спускайтесь в него. Если задана -depth, ложь; нет эффекта.

В основном это не будет спускаться ни в какие подкаталоги.

Возьмите этот пример:

У вас есть следующие каталоги

  • / Главная / test2
  • / Главная / test2 / test2

Если вы запускаете find -name test2:

Он вернет оба каталога

Если вы запускаете find -name test2 -prune:

Он вернет только / home / test2, поскольку не попадет в / home / test2 для поиска / home / test2 / test2

AdamW
источник
не на 100% верно: это «делать сокращение при сопоставлении условия, и, если это каталог, вынуть его из списка дел, то есть не вводить также». -prune также работает с файлами.
Оливье Дюлак
2

Я не эксперт в этом (и эта страница была очень полезна вместе с http://mywiki.wooledge.org/UsingFind )

Только что был замечен -pathпуть, который полностью соответствует строке / пути, которая идет сразу послеfind ( .в этих примерах), где as -nameсоответствует всем базовым именам.

find . -path ./.git  -prune -o -name file  -print

блокирует каталог .git в вашем текущем каталоге ( как вы нашли в . )

find . -name .git  -prune -o -name file  -print

рекурсивно блокирует все подкаталоги .git.

Обратите внимание, что ./ это чрезвычайно важно! -pathдолжен соответствовать путь, привязанный к . или к чему-либо, пришедшему сразу после поиска, если вы получаете совпадения с ним (с другой стороны или ' -o'), который, вероятно, не удаляется! Я наивно не знал об этом, и это заставило меня использовать -path, когда замечательно, когда вы не хотите удалять все подкаталоги с одним и тем же базовым именем: D

sabgenton
источник
Обратите внимание, что если вы делаете, find bla/то говорите, что вам понадобится -path bla/.git (или если *вместо этого вы пихаете a вперед, он будет вести себя как -name)
sabgenton
1

Показать все, включая самого dir, но не скучное содержимое:

find . -print -name dir -prune
девон
источник
0

Если вы прочитаете здесь все хорошие ответы, то я понимаю, что все следующее дает одинаковые результаты:

find . -path ./dir1\*  -prune -o -print

find . -path ./dir1  -prune -o -print

find . -path ./dir1\*  -o -print
#look no prune at all!

Но последнее займет намного больше времени, так как все еще ищет все в dir1. Я полагаю, что реальный вопрос заключается в том, как -orвыводить нежелательные результаты, фактически не ища их.

Так что я думаю, что обрезка означает не приличные прошлые матчи, а пометить как выполненные ...

http://www.gnu.org/software/findutils/manual/html_mono/find.html «Это, однако, не связано с действием действия« -prune »(которое только предотвращает дальнейшее снижение, оно не гарантирует мы игнорируем этот элемент). Вместо этого этот эффект связан с использованием '-o'. Поскольку левая часть условия "или" успешно выполнена для ./src/emacs, нет необходимости оценивать правую hand-side ('-print') вообще для этого конкретного файла. "

sabgenton
источник
0

findстроит список файлов. Он применяет предикат, который вы предоставили каждому, и возвращает те, которые прошли.

Эта идея, -pruneозначающая исключение из результатов, меня действительно смутила. Вы можете исключить файл без удаления:

find -name 'bad_guy' -o -name 'good_guy' -print  // good_guy

Все, что -pruneнужно, это изменить поведение поиска. Если текущее совпадение является каталогом, оно говорит: «Эй find, этот файл, который вы только что сопоставили, не спускайтесь в него» . Он просто удаляет это дерево (но не сам файл) из списка файлов для поиска.

Это должно быть названо -dont-descend.

seeker_of_bacon
источник
0

Есть довольно много ответов; некоторые из них слишком сложны для теории. Я уйду, почему мне нужно было подрезать один раз, так что, может быть, объяснение типа «в первую очередь / пример» кому-то пригодится :)

проблема

У меня была папка с примерно 20 каталогами узлов, каждый из которых имел свой node_modulesкаталог, как и ожидалось.

Как только вы попадаете в любой проект, вы видите каждый ../node_modules/module. Но вы знаете, как это. Почти у каждого модуля есть зависимости, так что то, на что вы смотрите, больше похоже наprojectN/node_modules/moduleX/node_modules/moduleZ...

Я не хотел тонуть со списком с зависимостью зависимости ...

Зная -d n/ -depth n, это не помогло бы мне, так как каталог main / first node_modules, который я хотел получить для каждого проекта, был на разной глубине, например:

Projects/MysuperProjectName/project/node_modules/...
Projects/Whatshisname/version3/project/node_modules/...
Projects/project/node_modules/...
Projects/MysuperProjectName/testProject/november2015Copy/project/node_modules/...
[...]

Как я могу получить первый список путей, заканчивающихся на первом, node_modulesи перейти к следующему проекту, чтобы получить то же самое?

Войти -prune

Когда вы добавите -prune, у вас все равно будет стандартный рекурсивный поиск. Каждый «путь» анализируется, и каждая находка выплевывается и findпродолжает копаться, как хороший парень. Но это рытье для большего, node_modulesчего я не хотел.

Таким образом, разница в том, что в любом из этих разных путей, -pruneвы findперестанете копать дальше вниз по этому конкретному проспекту, когда он найдет ваш предмет. В моем случае node_modulesпапка.

Карлес Алколея
источник