Вопрос или проблема
Почему в 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
обратите внимание на следующие нюансы:
- Явные скобки: Не забывайте использовать скобки для группировки условий при наличии операторов
-o
и-a
. Это предотвращает недоразумения и ошибки выполнения. - Имплицитный
-print
: Помните о том, что, если вы не используете-exec
,find
добавляет-print
автоматически. В случае явного указания-exec
, вам необходимо управлять выполнением самостоятельно. - Строгость синтаксиса: Будьте аккуратны с синтаксисом и используйте экранирование, чтобы избежать проблем с интерпретацией командной оболочкой.
Заключение
Проблема с тем, что команда find
пропускает ожидаемые результаты при использовании оператора -o
, обусловлена особенностями обработки логических выражений. Явное использование скобок поможет избежать путаницы и обеспечить корректное выполнение вашей команды. Рекомендуется всегда учитывать порядок операторов и их влияние на выполнение команд find
.