Попытка написать сценарий оболочки, который продолжает тестировать сервер удаленно, но он продолжает падать в операторе else при выходе

9

Здесь я пытаюсь написать сценарий оболочки, который продолжает тестировать мой сервер и отправлять мне электронные письма, когда он отключается.

Проблема в том, что когда я выхожу из ssh-соединения, несмотря на то, что он запускается с ним &в конце команды, например ./stest01.sh &, он автоматически попадает в else и продолжает непрерывно отправлять мне письма, пока я снова не захожу и не уничтожаю его.

#!/bin/bash
while true; do
    date > sdown.txt ;
    cp /dev/null pingop.txt ;
    ping -i 1 -c 1 -W 1 myserver.net > pingop.txt &
    sleep 1 ;
    if
        grep "64 bytes" pingop.txt ;
    then
        :
    else
        mutt -s "Server Down!" myemail@address.com < sdown.txt ;
        sleep 10 ;
    fi
done
Vasconcelos1914
источник
1
Я не эксперт по bash, но что делает двоеточие :? Было бы разумно, если бы это была точка с запятой ;...
Нед64
3
@ Ned64 The :ничего не делает. Это то, для чего он предназначен. Здесь, вместо того, чтобы перевернуть тест, они используют его, чтобы сделать no-op раньше else.
Кусалананда
@Kusalananda Хорошо, спасибо. Я подумал, что это может быть опечатка, которая может объяснить проблему.
Нед64
1
Я также запутался, почему можно попытаться оставить скрипт оболочки запущенным после выхода из системы. Разве таймеры cron или systemd не будут лучшим выбором для этого?
Клифф Армстронг

Ответы:

20

Когда GNU grepпытается записать свой результат, он потерпит неудачу с ненулевым состоянием выхода, потому что ему некуда записать вывод, потому что соединение SSH разорвано.

Это означает, что ifутверждение всегда принимает elseветвь.

Чтобы проиллюстрировать это (это не совсем то, что происходит в вашем случае, но оно показывает, что происходит, если GNU grepне может записать свой вывод):

$ echo 'hello' | grep hello >&- 2>&-
$ echo $?
2

Здесь мы grepдля строки, которая echoпроизводит, но мы закрываем оба выходных потока для grepтого, чтобы он не мог нигде записать. Как видите, состояние выхода GNU grep- 2, а не 0.

Это относится к GNU grep, grepв системах BSD не будет вести себя так же:

$ echo 'hello' | grep hello >&- 2>&-    # using BSD grep here
$ echo $?
0

Чтобы исправить это, убедитесь, что скрипт не генерирует вывод. Вы можете сделать это с exec >/dev/null 2>&1. Кроме того, мы должны использовать grepего с -qопцией, так как мы вообще не заинтересованы в том, чтобы увидеть вывод из него (это, как правило, также ускорит процесс, так grepкак не нужно анализировать весь файл, но в этом случае это очень мало разница в скорости так как файл очень маленький).

Короче говоря:

#!/bin/sh

# redirect all output not redirected elsewhere to /dev/null by default:
exec >/dev/null 2>&1

while true; do
    date >sdown.txt

    ping -c 1 -W 1 myserver.net >pingop.txt

    if ! grep -q "64 bytes" pingop.txt; then
        mutt -s "Server Down!" myemail@address.com <sdown.txt
        break
    fi

    sleep 10
done

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

#!/bin/sh

exec >/dev/null 2>&1

while true; do
    if ! ping -q -c 1 -W 1 myserver.net; then
        date | mutt -s "Server Down!" myemail@address.com
        break
    fi

    sleep 10
done

В обоих вариантах сценария, приведенного выше, я выбираю выход из цикла при невозможности связаться с хостом, просто чтобы минимизировать количество отправляемых писем. Вместо этого вы можете заменить на breakeg sleep 10mили что-то, если вы ожидаете, что сервер в итоге снова заработает.

Я также немного подправил параметры, используемые с, так pingкак -i 1не имеет особого смысла -c 1.

Короче (если вы не хотите, чтобы он продолжал отправлять электронные письма, когда хост недоступен):

#!/bin/sh

exec >/dev/null 2>&1

while ping -q -c 1 -W 1 myserver.net; do
    sleep 10
done

date | mutt -s "Server Down!" myemail@address.com

Поскольку задание cron выполняется каждую минуту (будет продолжаться отправка писем каждую минуту, если сервер продолжает не работать):

* * * * * ping -q -c 1 -W 1 >/dev/null 2>&1 || ( date | mail -s "Server down" myemail@address.com )
Кусалананда
источник
Использование >&-закроет fd (как в случае, дескриптор файла 1 закрыт), в то время как закрытие соединения SSH будет иметь другой эффект (дескриптор файла будет все еще рядом, но не связан ни с чем на другой стороне.) Я думаю, что точка все еще стоит, что означает, что GNU grep завершает работу с ненулевым значением, если он пытается записать вывод, и это не удается. Да, лучшее решение - это просто проверить состояние выхода ping напрямую.
filbranden
4
Возможно, было бы безопаснее просто перенаправить все в / из / dev / null для всего скрипта, добавив exec </dev/null >/dev/null 2>&1в начале. Таким образом, если, например, pingрешит написать что-то в stderr, это не вызовет проблем.
Гордон Дэвиссон
@GordonDavisson Я действительно не вижу причины, чтобы вытащить стандартный ввод /dev/nullотсюда, но я разобрался с выводом. Спасибо за предложение.
Кусалананда