Почему find в Linux пропускает ожидаемые результаты, когда используется -o?

Вопрос или проблема

Почему в Linux (Debian 8)

touch 1.cpp 1.h
find . -name "*.cpp" -o -name "*.h" -exec echo {} \;

выводит только 1.h, тогда как

find . -name "*.cpp" -o -name "*.h"

выводит оба? Это ошибка или особенность?

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

Кажется, что часть -exec сгруппирована вместе со вторым -name "*.h".

Поэтому, чтобы это работало правильно, вам нужно добавить скобки, как ниже:

find . '(' -name '*.cpp' -o -name '*.h' ')' -exec echo {} ';'

Помните: скобки должны быть заключены в кавычки или экранированы обратной косой чертой, чтобы предотвратить их интерпретацию как специальные символы оболочки.

Кроме того, вы можете объединить несколько расширений в одно, используя -regex:

find . ! -regex ".*\.\(cpp\|h\)" -exec echo {} \;

Ни то, ни другое. Это синтаксис опций, который “неправильный”. find выполняет выражения последовательно. Следовательно, он сначала оценивает первое выражение (-name "*.cpp"), а затем встречает флаг -o. Если первое выражение истинно, find не продолжит оценивать второе (-name "*.h" -exec echo {} \;), а просто ничего не делает. Видите ли, вся часть после -o является одним выражением. Поэтому это выполняется только для файлов, которые соответствуют второму выражению. Именно поэтому вы видите только файл 1.h, который проходит только через второе выражение. Смотрите справочную страницу find:

   expr1 -o expr2
          Или; expr2 не оценивается, если expr1 истинно.

Почему это полезно? Рассмотрим следующее:

find /path -exec test_file_for_something {} -print \; -o -name "xyz" -exec ls -l {} \;

В этом выражении find файл передается в test_file_for_something в качестве параметра. Теперь, в зависимости от кода возвращаемого значения этой команды, первое выражение истинно (тогда выполняется -print и на этом всё заканчивается) или ложное (тогда оценивается второе выражение после флага -o). И если оно истинно (имя xyz), то выполняется -exec.

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

find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;

Попробуйте окружить свои критерии поиска скобками следующим образом:

find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;

Другие ответы сосредоточены на поведении -o, и они правы, но это лишь половина истории. Другие половина – это поведение неявного -print.

В некоторых обстоятельствах find добавляет неявный -print к выражению. Это происходит, когда выражение не содержит ни -exec, ни -ok, ни (явного) -print, ни -print0. В зависимости от реализации find могут быть другие примеры, которые подавляют неявный -print (например, -execdir). expression, который вызывает это поведение, заставляет find преобразовывать команду такую как:

find paths expression

в:

find paths \( expression \) -print

(где обратные косые черты защищают ( и ) от интерпретации вашей оболочкой). Скобки здесь, так работает неявный -print.

-name "*.cpp" -o -name "*.h", которое вы использовали, является таким expression, так что ваше:

find . -name "*.cpp" -o -name "*.h"

эквивалентно:

find . \( -name "*.cpp" -o -name "*.h" \) -print

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

Теперь, если вы хотите использовать -exec … вместо неявного -print, то вам нужно сохранить скобки и ввести их явно:

find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;

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

Вот как работает find с его операторами.

Смотрите http://linux.die.net/man/1/find раздел ОПЕРАТОРЫ

ОПЕРАТОРЫ

Перечисленные в порядке убывания приоритета: ( expr ) Принудительное указание приоритета.
Поскольку скобки являются специальными для оболочки, вам обычно нужно их
заключать в кавычки. Многие примеры в этой странице справки используют обратные косые черты
для этой цели: ‘(…)’ вместо ‘(…)’. ! expr Истинно, если expr ложно. Этот символ также обычно требует защиты от
интерпретации оболочкой.

-not expr То же самое, что и ! expr, но не соответствует POSIX. expr1 expr2 Два выражения подряд считаются объединёнными с подразумеваемым “и”;
expr2 не оценивается, если expr1 ложно. expr1 -a expr2 То же самое, что expr1
expr2. expr1 -and expr2 То же самое, что expr1 expr2, но не соответствует POSIX.
expr1 -o expr2 Или; expr2 не оценивается, если expr1 истинно. expr1 -or
expr2 То же самое, что expr1 -o expr2, но не соответствует POSIX. expr1 , expr2
Список; оба expr1 и expr2 всегда оцениваются. Значение expr1
отбрасывается; значение списка является значением expr2. Оператор запятой
может быть полезен для поиска нескольких различных типов
вещей, но обходя иерархию файловой системы только один раз. Действие -fprintf
можно использовать, чтобы перечислить различные совпадения в несколько
различных выходных файлов.

Это должно дать вам желаемый результат:

find . \( -name ‘*.cpp’ -o -name ‘*.h’ \) -exec echo {} \;

Ваша команда делает это (команда не сработает, просто чтобы показать логику):

find . -name ‘*.cpp’ (-o -name ‘*.h’ -exec echo {} \;)

Ответ или решение

Вопрос о том, почему команда find в Linux (Debian 8) пропускает ожидаемые результаты при использовании оператора -o, на самом деле касается синтаксиса и логики, заложенной в этот инструмент. Давайте разберёмся с этой проблемой.

Проблема с использованием оператора -o

Когда вы используете команду

find . -name "*.cpp" -o -name "*.h" -exec echo {} \;

ожидается, что команда выведет и 1.cpp, и 1.h. Однако, на практике, на выходе вы увидите только 1.h. Причина в том, как find обрабатывает логические выражения.

Команда find по умолчанию работает последовательно, оценивая каждое выражение в порядке их записи. Когда find встречает первый результат, соответствующий выражению -name "*.cpp", он приостанавливает дальнейшую обработку, если это выражение возвращает true. Из-за этого часть -exec echo {} после оператора -o не вызывается, если предыдущая часть была истинной. Это объясняет, почему вы видите только 1.h в выводе.

Примеры и пояснения

Рассмотрим несколько примеров. Применяя оператор -o, вы должны учитывать, что если первое выражение истинно, второе не будет оцениваться. Например, если вы сделаете:

find . -name "*.cpp" -o -name "*.h"

это будет эквивалентно:

find . \( -name "*.cpp" -o -name "*.h" \) -print

Поскольку в данном случае find автоматически добавляет -print после условия, вы видите оба файла. Однако в вашем первом примере, -exec перегружает логику. В результате вы не видите ожидаемого результата для *.cpp.

Как избежать этой проблемы

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

find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;

Здесь скобки гарантируют, что find сначала оценит обе части условия, а затем выполнит команду -exec для файлов, которые удовлетворяют хотя бы одной части (либо *.cpp, либо *.h).

Дополнительные советы

При использовании команд find обратите внимание на следующие нюансы:

  1. Явные скобки: Не забывайте использовать скобки для группировки условий при наличии операторов -o и -a. Это предотвращает недоразумения и ошибки выполнения.
  2. Имплицитный -print: Помните о том, что, если вы не используете -exec, find добавляет -print автоматически. В случае явного указания -exec, вам необходимо управлять выполнением самостоятельно.
  3. Строгость синтаксиса: Будьте аккуратны с синтаксисом и используйте экранирование, чтобы избежать проблем с интерпретацией командной оболочкой.

Заключение

Проблема с тем, что команда find пропускает ожидаемые результаты при использовании оператора -o, обусловлена особенностями обработки логических выражений. Явное использование скобок поможет избежать путаницы и обеспечить корректное выполнение вашей команды. Рекомендуется всегда учитывать порядок операторов и их влияние на выполнение команд find.

Оцените материал
Добавить комментарий

Капча загружается...