Как я могу использовать bash's if test и находить команды вместе?

25

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

Файлы журнала хранятся в следующем формате:

/var/log/crashes/app-2012-08-28.log
/var/log/crashes/otherapp-2012-08-28.log

Я хочу, чтобы оператор if возвращал значение true только в том случае, если для определенного приложения есть журнал сбоев, который был изменен за последние 5 минут. Команда, findкоторую я бы использовал:

find /var/log/crashes -name app-\*\.log -mmin -5

Я не уверен, как правильно включить это в ifутверждение. Я думаю, что это может сработать:

if [ test `find /var/log/crashes -name app-\*\.log -mmin -5` ] then
 service myapp restart
fi

Есть несколько областей, где мне неясно:

  • Я посмотрел на флаги if, но я не уверен, какой из них, если таковые имеются, мне следует использовать.
  • Нужна ли мне testдиректива, или я должен просто обработать результаты команды find напрямую, или, может быть, использовать find... | wc -lвместо этого счетчик строк?
  • Не на 100% необходимо ответить на этот вопрос, но testдля проверки на коды возврата возвращаются команды? И они вроде невидимы - вне stdout/ stderr? Я прочитал manстраницу, но мне все еще неясно, когда использовать testи как ее отладить.
УХО
источник
Реальный ответ на общий случай заключается в использовании find ... -exec. Также см. Пример команд в разделе « Почему циклическая обработка вывода find является плохой практикой»?
Wildcard
@Wildcard - к ​​сожалению, это не решает общий случай: оно не работает, если существует более одного совпадения, и действие должно выполняться только один раз, и оно не работает, если вам нужно выполнить действие, когда есть нет совпадений. Первый может быть решен с помощью ... -exec command ';' -quit, но я не верю, что есть какое-то решение для второго, кроме анализа результата. Кроме того, в любом случае основная проблема с синтаксическим анализом результата find(т. Е. Невозможность отличить разделители от символов в именах файлов) не применима, так как в этих случаях вам не нужно находить разделители.
Жюль

Ответы:

27

[и testявляются синонимами ( за исключением [требует ]), так что вы не хотите использовать [ test:

[ -x /bin/cat ] && echo 'cat is executable'
test -x /bin/cat && echo 'cat is executable'

testвозвращает нулевое состояние выхода, если условие истинно, иначе ненулевое. Это может быть фактически заменено любой программой, чтобы проверить ее состояние выхода, где 0 указывает на успех, а ненулевое указывает на сбой:

# echoes "command succeeded" because echo rarely fails
if /bin/echo hi; then echo 'command succeeded'; else echo 'command failed'; fi

# echoes "command failed" because rmdir requires an argument
if /bin/rmdir; then echo 'command succeeded'; else echo 'command failed'; fi

Однако все вышеприведенные примеры только проверяют состояние завершения программы и игнорируют вывод программы.

Для find, вам нужно будет проверить, был ли сгенерирован какой-либо вывод. -nтестирует непустую строку:

if [[ -n $(find /var/log/crashes -name "app-*.log" -mmin -5) ]]
then
    service myapp restart
fi

Полный список тестовых аргументов доступен при вызове help testв bashкомандной строке.

Если вы используете bash(а не sh), вы можете использовать [[ condition ]], что ведет себя более предсказуемо, когда в вашем состоянии есть пробелы или другие особые случаи. В противном случае это обычно то же самое, что и использование [ condition ]. Я использовал [[ condition ]]в этом примере, как я делаю, когда это возможно.

Я также изменился `command`на $(command), который также обычно ведет себя аналогично, но лучше с вложенными командами.

МРБ
источник
echoможет потерпеть неудачу: попробуйте echo 'oops' > /dev/full.
Дероберт
Этот ответ бьет все вокруг корня проблемы, но изящно избегает упоминания точно, что это такое.
Багамат
9

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

Это было бы что-то вроде этого:

if [ $(find /var/log/crashes -name 'app-*.log' -mmin -5 | wc -l) -gt 0 ]; then
    ...
fi

test(aka [) не проверяет коды ошибок команд, у него есть специальный синтаксис для выполнения тестов, а затем завершается с кодом ошибки 0, если тест прошел успешно, или 1 в противном случае. Это ifтот, который проверяет код ошибки команды, которую вы передаете ему, и выполняет ее тело на основе этого.

Смотрите man test(или help test, если вы используете bash), и help if(так же).

В этом случае wc -lбудет выводиться число. Мы используем testопцию, -gtчтобы проверить, больше ли это число, чем 0. Если это так, test(или [) вернется с кодом выхода 0. ifбудет интерпретировать этот код выхода как успешный, и он выполнит код внутри своего тела.

ангус
источник
1

Это было бы

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)" ]; then

или

if test -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)"; then

Команды testи [ … ]являются точно синонимами. Разница лишь в их имени и в том, что последний аргумент [требует закрытия ]. Как всегда, используйте двойные кавычки вокруг подстановки команд, в противном случае выходные данные findкоманды будут разбиты на слова, и здесь вы получите синтаксическую ошибку, если существует более одного совпадающего файла (и если аргументов нет, значение [ -n ]true в то время как вы хотите, [ -n "" ]который является ложным).

В ksh, bash и zsh, но не в ash, вы также можете использовать [[ … ]]различные правила синтаксического анализа: [это обычная команда, а [[ … ]]другая конструкция синтаксического анализа. Вам не нужны двойные кавычки внутри [[ … ]](хотя они не больно). Вам все еще нужно ;после команды.

if [[ -n $(find /var/log/crashes -name app-\*\.log -mmin -5) ]]; then

Это может быть неэффективно: если в файле много файлов /var/log/crashes, команда find изучит их все. Вы должны сделать find stop, как только он найдет совпадение или вскоре после этого. С GNU find (не встроенный Linux, Cygwin) используйте -quitосновной.

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print -quit)" ]; then

С другими системами, труба findв, headпо крайней мере, выходит вскоре после первого матча (find умрет от сломанной трубы).

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print | head -n 1)" ]; then

(Вы можете использовать, head -c 1если ваша headкоманда поддерживает это.)


Или используйте zsh.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then
Жиль "ТАК - перестань быть злым"
источник
1

Это должно работать

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi
Стивен Пенни
источник
0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

это подходящее решение здесь.

-exec service myapp restart ';'вызывает findкоманду, которую вы хотите запустить напрямую, вместо того, чтобы интерпретировать что-либо.

-quitвызывает findвыход после обработки команды, предотвращая, таким образом, повторное выполнение команды, если окажется, что несколько файлов соответствуют критериям.

Жюль
источник