URL: https://www.opennet.me/cgi-bin/openforum/vsluhboard.cgi
Форум: vsluhforumID9
Нить номер: 9711
[ Назад ]

Исходное сообщение
"Является ли скрипт корректным?"

Отправлено Raven , 28-Июл-13 19:22 
Есть такой скрипт, практическую задачу выполняет. Задача: проверять доступность сайта,
оповещать голосом, далее работать в цикле до изменения состояния и снова оповещать (только один раз).
Вопрос, является ли этот скрипт корректным с точки зрения программирования на bash?
Вроде бы нет рекурсивных вызовов?
#!/bin/bash
read INPUT
ip="$INPUT"
count=1
sett ()
{
result=$(ping -c ${count} ${ip} | tail -2 | head -1 | awk '{print $4}')
if [ "$result" = "1" ]; then
echo "The website is up" | festival --tts
sett1
else
echo "The website is down" | festival --tts
noset1
fi
}

noset1 ()
{
while true
do
result=$(ping -c ${count} ${ip} | tail -2 | head -1 | awk '{print $4}')
if [ "$result" != "1" ]; then
sleep 5
else
sett
fi
done
}

sett1 ()
{
while true
do
result=$(ping -c ${count} ${ip} | tail -2 | head -1 | awk '{print $4}')
if [ "$result" = "1" ]; then
sleep 5
else
sett
fi
done
}

sett


Содержание

Сообщения в этом обсуждении
"Является ли скрипт корректным?"
Отправлено skb7 , 29-Июл-13 00:44 
> Есть такой скрипт, практическую задачу выполняет. Задача: проверять доступность сайта,
> оповещать голосом, далее работать в цикле до изменения состояния и снова оповещать
> (только один раз).
> Вопрос, является ли этот скрипт корректным с точки зрения программирования на bash?

Считаю что скрипт плохой.
1. Не знаю как в баше, а в Си есть такое понятие -- стек вызовов, при вызове каждой функции сохраняется адрес возврата и аргументы, переданные в функцию. Следовательно, если вызывать достаточно большую цепочку функций, стек может переполниться, да и память расходуется под этот стек. У вас как раз такая ситуация -- может быть ситуация типа sett() -> sett1() -> sett() -> noset1() -> sett() -> sett1() -> .... (до бесконечности)
2. Много повторяющихся кусков кода
3. Вот этот кусок -- тавтология:


read INPUT
ip="$INPUT"

Почему не сделать сразу:

read ip

4. Лучше поток вывода ошибок утилиты ping перенаправлять в /dev/null, зачем вам в консоли куча текста "Network is unavailable".
5.
if [ "$result" = "1" ]; then

   Если переменная count будет больше единицы, этот код будет некорректен. Нужно сравнивать на равенство переменной count.

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


#!/bin/sh

ip=0
count=1
prev_is_site_up=0
cur_is_site_up=0

read_ip() {
    echo -n "Enter site IP: "
    read ip
    # TODO: add some IP address validation here
}

# Returns 1 if site is up, 0 -- site is down
test_site() {
    res=$(ping -c ${count} ${ip} | tail -2 | head -1 | awk '{print $4}') \
        2> /dev/null
    if [ "$res" = $count ]; then
        return 1
    else
        return 0
    fi
}

notify() {
    if [ $prev_is_site_up -eq 1 ]; then
        echo "The website is up" | festival --tts
    else
        echo "The website is down" | festival --tts
    fi
}

script_init() {
    test_site
    prev_is_site_up=$?
    notify
}

script_loop() {
    while :; do
        sleep 5
        test_site
        cur_is_site_up=$?
        if [ $cur_is_site_up -ne $prev_is_site_up ]; then
            prev_is_site_up=$cur_is_site_up
            notify
        fi
    done
}

read_ip
script_init
script_loop

Ну и последнее: может лучше сделать это с использованием cron?


"Является ли скрипт корректным?"
Отправлено Raven , 29-Июл-13 09:37 
skb7, спасибо вам, очень большое, вроде бы в целом понятно.

Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение, полученное в test_site?!

Про крон думал, конечно, только хотелось понять свои ошибки и найти правильный метод.  


"Является ли скрипт корректным?"
Отправлено skb7 , 30-Июл-13 01:29 
> Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение,
> полученное в test_site?!

Да, script_init() только раз вызывается. В $? содержится код возврата последней инструкции, т.е. да,  переменная prev_is_site_up принимает значение, которое вернула ф-ция test_site().


"Является ли скрипт корректным?"
Отправлено Raven , 31-Июл-13 04:06 
>> Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение,
>> полученное в test_site?!
> Да, script_init() только раз вызывается. В $? содержится код возврата последней инструкции,
> т.е. да,  переменная prev_is_site_up принимает значение, которое вернула ф-ция test_site().

Еще раз большое спасибо.


"Является ли скрипт корректным?"
Отправлено Raven , 12-Авг-13 10:57 
>> Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение,
>> полученное в test_site?!
> Да, script_init() только раз вызывается. В $? содержится код возврата последней инструкции,
> т.е. да,  переменная prev_is_site_up принимает значение, которое вернула ф-ция test_site().

Кстати, как бы эта задача решалась с крон? Он ведь каждый раз запускает скрипт заново
и обнуляет значения переменных. Или нужно делать лва скрипта?один инит, который запустится один раз запишет куда-то значение пременной..




"Является ли скрипт корректным?"
Отправлено Raven77 , 12-Авг-13 20:21 
Думал-думал. Пока придумал такое, может, не очень изящное решение
Делается 2 скрипта:
1. Запускается один раз при старте системы, проверяет сеть и записывает значение в файл с именем сс, например.
2. Проверят, допустим раз в 5 минут, вот, что получилось:

#!/bin/bash


test_site() {
    res=$(ping -c 1 opennet.ru | tail -2 | head -1 | awk '{print $4}') 2> /dev/null
    if [ "$res" = "1" ]; then
        return 1
    else
        return 0
    fi
}
notify() {
    if [ "$res" -eq "1" ]; then
        echo "The website is up" | festival --tts
        echo "1" > cc
    else
        echo "The website is down" | festival --tts
        echo "" > cc
    fi
}
test_site
catt=$(cat cc)      
        if [ "$res" != "$catt" ]; then            
            notify
        fi
          
        


"Является ли скрипт корректным?"
Отправлено Raven77 , 13-Авг-13 00:37 
Вернее, echo "The website is down" | festival --tts
        echo "0" > cc
Так как я проверял на своем роутере, просто отключая сеть, поэтому получалось Network is unreachable, и значение $res было пустым
И, конечно, забыл сравнивать лучше с переменной count


"Является ли скрипт корректным?"
Отправлено Raven77 , 13-Авг-13 01:02 
Прошу прощения за этим мои упражнения с bash в онлайн режиме. Последний мой вариант получается таким. P.S. Вдруг кому-то пригодится, хотя бы для первоначальных учебных целей.

          #!/bin/bash

count=1
test_site() {
    res=$(ping -c $count 192.168.1.2 | tail -2 | head -1 | awk '{print $4}') 2> /dev/null
    if [ "$res" = "1" ]; then
        return 1
    else
        return 0
    fi
}
notify() {
case $res in
"") echo "no network" | festival --tts
    echo "" > cc
    ;;
1) echo "The website is up" | festival --tts
    echo "1" > cc
    ;;
0)
echo "The website is down" | festival --tts
echo "0" > cc
;;
esac
}
test_site
catt=$(cat cc)      
        if [ "$res" != "$catt" ]; then            
            notify
        fi
          


"Является ли скрипт корректным?"
Отправлено skb7 , 13-Авг-13 02:19 
>[оверквотинг удален]
>  ;;
> esac
> }
> test_site
> catt=$(cat cc)
>         if [ "$res" !=
> "$catt" ]; then
>            
> notify
>         fi

Опять же, вы проверяете на 1 вместо count.
Вот мой вариант скрипта (из моего предыдущего по времени сообщения) с учетом "no network":


#!/bin/sh

# "ip" can be passed from crontab or be read from some config file
ip=8.8.8.8
count=1
prev_is_site_up=0
cur_is_site_up=0
var_file=/tmp/prev-is-site-up

write_var() {
        echo -n "$1" > $var_file
}

read_var() {
        cat $var_file
}

# Returns: 0 - site is down, 1 - site is up, 2 - no network
test_site() {
    res=$(ping -c ${count} ${ip} | tail -2 | head -1 | awk '{print $4}') \
        2> /dev/null

    case "$res" in
    "")
        return 2
        ;;
    "$count")
        return 1
        ;;
    *)
        return 0
    esac
}

notify() {
    case "$1" in
    0)
        msg="The website is down"
        ;;
    1)
        msg="The website is up"
        ;;
    2)
        msg="No network"
        ;;
    esac

    echo "$msg" | festival --tts
}

do_site_check() {
    test_site
    cur_is_site_up=$?

    if [ ! -e $var_file ]; then
        write_var $cur_is_site_up
        notify $cur_is_site_up
        exit 0
    fi

    prev_is_site_up=$(read_var)
    if [ $cur_is_site_up -ne $prev_is_site_up ]; then
        write_var $cur_is_site_up
        notify $cur_is_site_up
    fi
}

do_site_check



"Является ли скрипт корректным?"
Отправлено skb7 , 13-Авг-13 02:05 
> Пока придумал такое, может, не очень изящное решение
> Делается 2 скрипта:
> 1. Запускается один раз при старте системы, проверяет сеть и записывает значение в файл > с именем сс, например.
> 2. Проверят, допустим раз в 5 минут

Ну, скрипт будет тот же самый (а не 2).
Я бы сделал так:


#!/bin/sh

# "ip" can be passed from crontab or be read from some config file
ip=8.8.8.8
count=1
prev_is_site_up=0
cur_is_site_up=0
var_file=/tmp/prev-is-site-up

write_var() {
        echo -n "$1" > $var_file
}

read_var() {
        cat $var_file
}

# Returns 1 if site is up, 0 -- site is down
test_site() {
    res=$(ping -c ${count} ${ip} | tail -2 | head -1 | awk '{print $4}') \
        2> /dev/null
    if [ "$res" = $count ]; then
        return 1
    else
        return 0
    fi
}

notify() {
    if [ $cur_is_site_up -eq 1 ]; then
        echo "The website is up" | festival --tts
    else
        echo "The website is down" | festival --tts
    fi
}

do_site_check() {
    test_site
    cur_is_site_up=$?

    if [ ! -e $var_file ]; then
        write_var $cur_is_site_up
        notify
        exit 0
    fi

    prev_is_site_up=$(read_var)
    if [ $cur_is_site_up -ne $prev_is_site_up ]; then
        write_var $cur_is_site_up
        notify
    fi
}

do_site_check

В /etc/crontab нужно вызывать этот скрипт с использованием блокировки (с помощью flock); например:


* * * * * root /usr/bin/flock -n /tmp/check-site-lock -c '/tmp/check-site.sh'

При старте системы особого смысла вызывать этот скрипт в принципе не вижу, особенно если он каждую минуту будет запускаться. Но если и вызывать, то использовать такую же блокировку, как и в /etc/crontab.


"Является ли скрипт корректным?"
Отправлено Raven77 , 13-Авг-13 17:03 
Круто, глядя, на ваши скрипты понимаю, как много еще не знаю, спасибо, с такими интересными примерами скриптов изучение bash становится действительно интересным.

Странно, что мне в голову не пришло, проверять существования файла...


"Является ли скрипт корректным?"
Отправлено Raven77 , 18-Авг-13 16:43 
Думал еще на днях над похожим скриптом - голосовым информером магнитных бурь, собственно, тоже самое, но пришла такая мысль, что может, проще добавить логическое "и" для проверки.
Или это хуже?
И конкретный вопрос по башу. Можно ли сравнивать  сначала сроку, потом числа?
"$A" != "возмущено" -a "$cattt" -eq "1" ];
Полный скрипт ниже:

#!/bin/bash
var_file=~/mess_magg

test_mag () {
    cattt=$(cat $var_file)
    A=$(/usr/bin/curl http://www.tesis.lebedev.ru/magnetic_storms.html | /usr/bin/iconv -f cp1251 -t utf-8 | /bin/grep -o возмущено) 2>/dev/null
if [ "$A" = "возмущено" -a "$cattt" -eq "0" ]; then
    echo "1" > $var_file
     notif
fi

if [ "$A" != "возмущено" -a "$cattt" -eq "1" ]; then
    echo "0" > $var_file
     notif
fi
}

notif () {
if [ "$A" = "возмущено" ]; then
    echo "Attention! Magnetic Storm" | festival --tts
      
fi
  if [ "$A" != "возмущено" ]; then
   echo "There is no anymore" | festival --tts
  
fi
}

if [ ! -e $var_file ]; then
   touch $var_file
    echo "0" > $var_file
fi

test_mag

Прошу прощения за безграмотность, что означает flock? Как-то не обратил на это должного внимания, поисковик ничего не принес внятного.


"Является ли скрипт корректным?"
Отправлено allez , 18-Авг-13 19:31 
> И конкретный вопрос по башу. Можно ли сравнивать  сначала сроку, потом
> числа?
>  "$A" != "возмущено" -a "$cattt" -eq "1" ];

Да, можно.


"Является ли скрипт корректным?"
Отправлено skb7 , 19-Авг-13 16:35 
> Можно ли сравнивать  сначала сроку, потом числа?
> [ "$A" != "возмущено" -a "$cattt" -eq "1" ];

Да, можно.


$ help test | grep AND
      EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.

Не важно, строки вы сравниваете или числа, -- и то, и то -- выражения, так что всё ок.

Можно и так, как вы сделали (в контексте test), а можно средствами баша:

1. [ $x != "abc" -a $y -eq 2 ]
2. [ $x != "abc" ] && [ $y -eq 2 ]

Эти выражения делают одно и то же.

> может, проще добавить логическое "и" для проверки.
> Или это хуже?

Ну, если так сильно углубляться...

1. Я бы переделал этот кусок кода:


if [ "$A" = "возмущено" -a "$cattt" -eq "0" ]; then
    echo "1" > $var_file
     notif
fi

if [ "$A" != "возмущено" -a "$cattt" -eq "1" ]; then
    echo "0" > $var_file
     notif
fi

таким образом:


if [ "$A" = "возмущено" ]; then
    if [ $cattt -eq 0 ]; then
        echo 1 > $var_file
        notif
    fi
else
    if [ $cattt -eq 1 ]; then
        echo 0 > $var_file
        notif
    fi
fi

Лучше тем, что будет делаться меньше проверок. У вас будет выполняться 4 проверки, а у меня 2. Плюс я избавился от ненужных кавычек.

2. Еще вот этот кусок кода переделал бы:


if [ "$A" = "возмущено" ]; then
    echo "Attention! Magnetic Storm" | festival --tts
      
fi
  if [ "$A" != "возмущено" ]; then
   echo "There is no anymore" | festival --tts
  
fi

на такой:


if [ "$A" = "возмущено" ]; then
    echo "Attention! Magnetic Storm" | festival --tts
else
    echo "There is no anymore" | festival --tts
fi

...или даже на такой:


if [ "$A" = "возмущено" ]; then
    msg="Attention! Magnetic Storm"
else
    msg="There is no anymore"
fi

echo $msg | festival --tts

> что означает flock?

flock -- блокировка с помощью файла. Такой себе аналог мьютекса. Используется, если вы подозреваете, что скрипт может быть выполнен одновременно из нескольких параллельных потоков (процессов), чтобы избежать race conditions.

Суть в том, что каждый раз при выполнении скрипта команда flock проверяет, существует ли lock-файл;
1. если не существует -- создается такой файл и выполняется код, защищенный этим локом
2. если существует -- команда flock будет ждать, пока lock-файл исчезнет (т.е. этот скрипт, выполняющийся в другом потоке, закончит выполнять защищенный локом код и удалит lock-файл), и потом продолжит выполнять код скрипта (защищенный flock). С помощью параметров также можно задать поведение: например, ждать с таймаутом, а потом завершать скрипт, либо просто сразу завершать скрипт.

Чтобы понять, как работает flock, можете проделать следующий эксперимент:
1. В одном терминале запустите команду


flock /tmp/123 -c 'while :; do sleep 1; done'

2. В другом терминале запустите команду

flock /tmp/123 -c 'echo 1'

- flock во втором терминале будет "висеть" и ждать, пока файл /tmp/123 удалится
- файл /tmp/123 удалится тогда, когда завершиться flock в первом терминале
- а в первом терминале под локом выполняется бесконечный цикл
3. Теперь нажмите Ctrl+C в первом терминале, чтобы завершить первый flock
4. Во втором терминале сразу же выполнится команда "echo 1".

"Является ли скрипт корректным?"
Отправлено Raven77 , 19-Авг-13 20:26 
skb7, спасибо, что так возитесь со мной.
С flock теперь понял.

"Является ли скрипт корректным?"
Отправлено skb7 , 19-Авг-13 22:32 
> skb7, спасибо, что так возитесь со мной.

На такие вопросы интересно отвечать, -- сам учишься в процессе. Ведь вы дали уже готовое решение и попросили улучшить. That's the spirit :)


"Является ли скрипт корректным?"
Отправлено Raven77 , 19-Авг-13 22:38 
Спасибо Тимуру Гатину (хоть и не знаком с ним в жизни), идея скрипта принадлежит ему, я случайно наткнулся на его топик в одном сообществе по программированию в VK и заинтересовался, строго из спортивного интереса.
Подумал, смогу ли со своими скудными знаниями, почти на чистой логике сделать хоть что-то, пусть и не очень правильно, но прошло время и решил все-таки понять, как это делается правильно...
Вообще изучение bash оказалось очень увлекательным, в каком-то смысле благодаря этому увлечению нашел работу, хоть и не связанную с программированием :) и вообще IT