Есть такой скрипт, практическую задачу выполняет. Задача: проверять доступность сайта,
оповещать голосом, далее работать в цикле до изменения состояния и снова оповещать (только один раз).
Вопрос, является ли этот скрипт корректным с точки зрения программирования на 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
> Есть такой скрипт, практическую задачу выполняет. Задача: проверять доступность сайта,
> оповещать голосом, далее работать в цикле до изменения состояния и снова оповещать
> (только один раз).
> Вопрос, является ли этот скрипт корректным с точки зрения программирования на 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/ship=0
count=1
prev_is_site_up=0
cur_is_site_up=0read_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?
skb7, спасибо вам, очень большое, вроде бы в целом понятно.Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение, полученное в test_site?!
Про крон думал, конечно, только хотелось понять свои ошибки и найти правильный метод.
> Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение,
> полученное в test_site?!Да, script_init() только раз вызывается. В $? содержится код возврата последней инструкции, т.е. да, переменная prev_is_site_up принимает значение, которое вернула ф-ция test_site().
>> Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение,
>> полученное в test_site?!
> Да, script_init() только раз вызывается. В $? содержится код возврата последней инструкции,
> т.е. да, переменная prev_is_site_up принимает значение, которое вернула ф-ция test_site().Еще раз большое спасибо.
>> Я правильно понимаю script_init выполняется 1 раз и prev_is_site_up=$? принимает значение,
>> полученное в test_site?!
> Да, script_init() только раз вызывается. В $? содержится код возврата последней инструкции,
> т.е. да, переменная prev_is_site_up принимает значение, которое вернула ф-ция test_site().Кстати, как бы эта задача решалась с крон? Он ведь каждый раз запускает скрипт заново
и обнуляет значения переменных. Или нужно делать лва скрипта?один инит, который запустится один раз запишет куда-то значение пременной..
Думал-думал. Пока придумал такое, может, не очень изящное решение
Делается 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
Вернее, echo "The website is down" | festival --tts
echo "0" > cc
Так как я проверял на своем роутере, просто отключая сеть, поэтому получалось Network is unreachable, и значение $res было пустым
И, конечно, забыл сравнивать лучше с переменной count
Прошу прощения за этим мои упражнения с 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
>[оверквотинг удален]
> ;;
> 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-upwrite_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/nullcase "$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"
;;
esacecho "$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
fiprev_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
> Пока придумал такое, может, не очень изящное решение
> Делается 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-upwrite_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
fiprev_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.
Круто, глядя, на ваши скрипты понимаю, как много еще не знаю, спасибо, с такими интересными примерами скриптов изучение bash становится действительно интересным.Странно, что мне в голову не пришло, проверять существования файла...
Думал еще на днях над похожим скриптом - голосовым информером магнитных бурь, собственно, тоже самое, но пришла такая мысль, что может, проще добавить логическое "и" для проверки.
Или это хуже?
И конкретный вопрос по башу. Можно ли сравнивать сначала сроку, потом числа?
"$A" != "возмущено" -a "$cattt" -eq "1" ];
Полный скрипт ниже:
#!/bin/bash
var_file=~/mess_maggtest_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
fitest_mag
Прошу прощения за безграмотность, что означает flock? Как-то не обратил на это должного внимания, поисковик ничего не принес внятного.
> И конкретный вопрос по башу. Можно ли сравнивать сначала сроку, потом
> числа?
> "$A" != "возмущено" -a "$cattt" -eq "1" ];Да, можно.
> Можно ли сравнивать сначала сроку, потом числа?
> [ "$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
fiif [ "$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"
fiecho $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".
skb7, спасибо, что так возитесь со мной.
С flock теперь понял.
> skb7, спасибо, что так возитесь со мной.На такие вопросы интересно отвечать, -- сам учишься в процессе. Ведь вы дали уже готовое решение и попросили улучшить. That's the spirit :)
Спасибо Тимуру Гатину (хоть и не знаком с ним в жизни), идея скрипта принадлежит ему, я случайно наткнулся на его топик в одном сообществе по программированию в VK и заинтересовался, строго из спортивного интереса.
Подумал, смогу ли со своими скудными знаниями, почти на чистой логике сделать хоть что-то, пусть и не очень правильно, но прошло время и решил все-таки понять, как это делается правильно...
Вообще изучение bash оказалось очень увлекательным, в каком-то смысле благодаря этому увлечению нашел работу, хоть и не связанную с программированием :) и вообще IT