*nix scripting. Пишем user-friendly скрипты. 1.1 Установка 1.1.1

advertisement
*nix scripting. Пишем user-friendly скрипты.
Все началось довольно обычно. Как-то спонтанно возникло желание установить
Slackware на домашнюю машинку. Ее установка немного отличается от того к чему
привыкли юзеры Ubuntu, Suse или Fedora, а чем-то больше похоже на установку FreeBSD.
Все действия по предварительной настройке системы выполняются из консоли, но при этом
используются специальные диалоговые окна на псевдографике. При выборе пакетов для
свежеустановленной слаки, была обнаружена утилита dialog, в описании которой было
сказано, что все менюшки и дилоги, которые рисуются при установке системы, написаны
именно на ней и естественно возникло желание попробовать прикрутить их к лабам по
юниксу, благо интеграция dialog в шелл скрипты — проще некуда.
1.1 Установка
При использовании пакетных менеджеров установка довольно проста, поэтому здесь
будет описана наиболее «хардкорная» версия — установка из сорцов =)
Для начала нам понадобится dev-версия библиотеки ncurses с хедерами функций,
которые используются в dialog. Грубо говоря, ncurses — это набор функций,
предназначенных для консольного ввода-вывода. За подробностями — в гугл.
В RPM-дистрибутивах пакет называется ncurses-devel, в deb-дистрибутивах —
ncurses-dev, их так же легко установить пакетными менегерами, но мы не ищем легких путей
=), поэтому:
1.1.1
Ставим ncurses
1) создаем в своем хомяке временную папку
cd ~
mkdir tmp
cd ~/tmp
2) качаем в нее ncurses
wget ftp://invisible-island.net/ncurses/ncurses-5.7.tar.gz
3) распаковываем
tar -xvzf ./ncurses-5.7.tar.gz
cd ./ncurses-5.7
Дальше стандартно
./configure --prefix=[куда хотим поставить]
./make
./make install
Если чето не получилось — курим консольный вывод компилятора до просветления.
1.1.2
Ставим собственно dialog
4) качаем в ~/tmp
cd ~/tmp
wget ftp://ftp.us.debian.org/debian/pool/main/d/dialog/dialog_1.120080819.orig.tar.gz
5) распаковываем
tar -xvzf ./dialog_1.1-20080819.orig.tar.gz
6) удаляем уже не нужные архивы (хотя можно и оставить)
rm -rf ./dialog_1.1-20080819.orig.tar.gz ./ncurses-5.7.tar.gz
cd ./dialog-1.1-20080819/
и снова configure && make && make install , не забывая в configure указать путь
установки.
Итак, все установлено, начнем издевательства над dialog-ом =)
1.2 HelloWorld
Изучение любой вещи в программировании (хоть тут и не совсем программирование)
начинается с хеловорлда. Напишем диалоговое окошко, которое выводит эту заветную фразу
и пару кнопок, yes и no.
#!/bin/bash
dialog --title "hello world messagebox" --clear \
--yesno "Hello world!" 15 60
case $? in
0)
1)
255)
esac
echo "Yes pressed";;
echo "No pressed";;
echo "ESC pressed";;
Вроде всё просто. Вызываем dialog и по коду возврата определяем, что произошло
(нажаты кнопки yes/no или esc), в соответствии с этим выводим на консоль сообщение.
Опция --title определяет заголовок диалога, --yesno его тип (в данном случае это чтото типа традиционного messagebox) и --clear указывающая, что перед отрисовкой
диалогового окошка неплохо было бы почистить экран, 15 60 — размер диалогового окошка
в символах.
Единственное что не очень приятно и бросается в глаза — то что скрипт завершается
сразу после нажатия на одну из трех кнопок, что бы этого избежать, можно немного его
переделать:
#!/bin/bash
ret_val=0
${DIALOG=dialog}
main(){
$DIALOG --title "hello world messagebox" --clear \
# заменяем стандартный текст кнопок на свой
--yes-label "Ага" --no-label "Не" \
--yesno "Даров, мир"
15 60 \
ret_val=$?
}
# Крутимся в цикле пока кто-то не нажмет кнопку "no".
# если нажата любая другая кнопка кроме "no" — выводим
# окно и опять ждем кода возврата
while [ $ret_val -ne 1 ]
do
main
done;
Тут стоит упомянуть, что утилита, подобная dialog, есть и под Иксы — Xdialog,
причем полностью совместимая с dialog по своим опциям, поэтому неплохо было бы
добавить в начало нашего скрипта такую строку:
${DIALOG=dialog}
и в функции main заменить вызов dialog на $DIALOG. Тогда если мы захотим погонять этот
скрипт под иксами, то надо будет поменять всего-лишь одну букву:
${DIALOG=Xdialog}
Можно еще немного усложнить этот мега-скрипт. Добавим строку ввода aka editbox
на основной диалог и, в зависимости от него будем выдавать соответствующее сообщение с
приветствием.
#!/bin/bash
${DIALOG=dialog}
tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/test$$
trap "rm -f $tempfile" 0 1 2 5 15
$DIALOG --title "INPUT BOX" --clear \
--inputbox "Hi, pls enter your name" 10 40 2> $tempfile
retval=$?
case $retval in
0)
$DIALOG --clear \
--title "Greetings from planet Mars" \
--backtitle "We came with peace!" \
--msgbox "Hello, human `cat $tempfile`" 7 40
;;
1)
echo "Cancel pressed.";;
255)
echo "ESC pressed."
;;
esac
Тут вывод dialog перенаправляется во временный файл, созданный утилитой tempfile.
(вместо нее можно создавать этот файл вручную или использовать утилитку mktemp), а далее
строка из этого файла используется в msgbox.
После всех махинаций получится что-то типа такого:
Кроме того, можно модифицировать скрипт так, чтобы он сам определял, в иксах он
или в консоли, и в зависимости от этого запускал соответственно Xdialog или dialog.
Пример простого тестового скрипта с кривым скриншотом для иксов чуть ниже (как
его "выпрямить" пока не знаю):
#если в переменной DISPLAY пустая строка, то мы в консоли
if [ -z $DISPLAY ]
then
DIALOG=dialog
else #иначе мы в иксах
DIALOG=Xdialog
fi
$DIALOG --yesno "test" 0 0
Вроде прикольно, но не сильно ясно, где это можно применить на практике (впрочем,
это проблема всех hello-world приложений =) ) Поэтому следующий пример будет более
практичным, однако по сути таким же бесполезным.
1.3 Фронт-энд для утилиты useradd
#!/bin/bash
#
#
#
#
#
#
#
-------------------------------------------------------------------------"THE BEER-WARE LICENSE":
<gusakov.max@gmail.com> wrote this file. As long as you retain this notice
you can do whatever you want with this stuff. If we meet some day, and you
think this stuff is worth it, you can buy me a beer.
--------------------------------------------------------------------------
#
#
#
#
#
#
#
#
#
#
#
#
склерозник по useradd
-c name & surname
-G группы
-s shell
-p password
-d home directory
-m create home dir
-U создает группу с тем же именем что и у пользователя
-e expire date YYYY-MM-DD
-f число дней после устаревания пароля, до полной блокировки аккаунта
0 учётная запись блокируется сразу после устаревания пароля
-1 данная возможность не используется
declare -a A_SHELLS;
declare -a A_USERS;
declare -a A_GROUPS;
${DIALOG=dialog}
# radiolist example
generateShellsDialog()
{
SHELLS_DLG=./getShell.sh
exec 3>&1
exec > $SHELLS_DLG
echo "#!/bin/bash"
echo "$DIALOG \\"
echo "--title \"SHELLS LIST\" \\"
echo "--radiolist \"Check login shell you need \" 20 60 15 \\"
for shell in ${A_SHELLS[@]}
do
echo "\"$shell\" \"\" off \\"
done
echo "2>./~shell.tmp"
echo "retval=\$?"
echo
echo
echo
echo
echo
echo
echo
echo
}
"USER_SHELL=\`cat ./~shell.tmp\`"
"case \$retval in"
"1)"
"echo \"Cancel pressed.\""
"exit;;"
"255)"
"echo \"ESC pressed.\";;"
"esac"
exec 1>&3 3>&chmod +x $SHELLS_DLG
# checklist example, almost the same as radiolist
generateGroupsDialog()
{
GROUPS_DLG=./getGroups.sh
exec 3>&1
exec > $GROUPS_DLG
echo "#!/bin/bash"
echo "$DIALOG \\"
echo "--title \"GROUPS LIST\" \\"
echo "--checklist \"Check groups you need \" 20 60 15 \\"
for group in ${A_GROUPS[@]}
do
echo "$group \"\" off \\"
done
echo "2>./~groups.tmp"
echo "retval=\$?"
echo
echo
echo
echo
"USER_GROUPS=\`cat ./~groups.tmp\`"
"case \$retval in"
"1)"
"echo \"Cancel pressed.\""
echo
echo
echo
echo
"exit;;"
"255)"
"echo \"ESC pressed.\";;"
"esac"
exec 1>&3 3>&chmod +x $GROUPS_DLG
}
# I do not know where to get list of installed shells
# so i took it from /etc/shells. If you read this, and you know where to
# find such list (on ALL Linux systems) pls mail me - I'll buy you a beer =)
getShellsList()
{
if [ -z $1 ]; then
echo "getShellsList() requers one parameter - filename\n";
return -1;
else
if [ -f $1 ]; then
trap "rm -rf ./tmp" 0 1 2 5 15
sed -e '/^#.*/d' $1 > "./tmp"; #Remove all strings, starting with '#'
A_SHELLS=( `cat "./tmp"`);
generateShellsDialog;
else
echo "file $1 does'n exist, or you don't have rights to open it\n";
return -1;
fi
fi
}
# usually the shells list in POSIX OS is in /etc/passwd & in /etc/shadow
# but to make application more flexible we'll pass the filename to function
getUsersList()
{
if [ -z $1 ]; then
echo "getUsersList() requers one parameter - filename\n";
return -1;
else
if [ -f $1 ]; then
A_USERS=( `cat $1 | sort | cut -d":" -f1`); #get only users names
else
echo "file $1 does'n exist, or you don't have rights to open it\n";
return -1;
fi
fi
}
# usually... oh I think You know it already =); see /etc/group
getGroupsList()
{
if [ -z $1 ]; then
echo "getGroupsList() requers one parameter - filename\n";
return -1;
else
if [ -f $1 ]; then
A_GROUPS=( `cat $1 | sort | cut -d":" -f1`); #get only groups names
generateGroupsDialog
else
echo "file $1 does'n exist, or you don't have rights to open it\n";
return -1;
fi
fi
}
cleanup()
{
rm -rf
rm -rf
rm -rf
rm -rf
clear
}
./~groups.tmp
./getGroups.sh
./getShell.sh
./tmp
addUser()
{
EXPIRE_DAYS=0
sed -e 's/\"//g' ./~groups.tmp > ./groups.tmp
USER_GROUPS=`sed -e 's/\s/,/g' ./groups.tmp`
rm -rf ./groups.tmp
useradd -c "$USER_NAME" -p "$USER_PASSWORD" \
-d /home/$USER_LOGIN -m -s $USER_SHELL \
-e $EXPIRE_DATE -f $EXPIRE_DAYS \
-G $USER_GROUPS $USER_LOGIN
chown $USER_LOGIN /home/$USER_LOGIN
cleanup
}
# calendar example
getExpireDate()
{
cur_date=`date +%D | sed -e 'y/\// /'`
exec 3>&1
EXPIRE_DATE=`$DIALOG --title "CALENDAR" --calendar\
"Please choose a password expire date..." 0 0 $cur_date 2>&1 1>&3`
code=$?
exec 3>&#convert dd/mm/yyyy (output from dialog) to
#YYYY-MM-DD (input to useradd)
OLD_IFS="$IFS"
IFS="/"
EXPIRE_DATE=($EXPIRE_DATE)
IFS="$OLD_IFS"
local year=${EXPIRE_DATE[2]}
local month=${EXPIRE_DATE[1]}
local day=${EXPIRE_DATE[0]}
EXPIRE_DATE="$year-$month-$day"
case $code in
0)
addUser
;;
1)
echo "Cancel pressed."
exit
;;
esac
}
# checklist dialog example
getGroups()
{
./getGroups.sh
USER_GROUPS=`cat ./~groups.tmp`
getExpireDate
}
# radiolist dialog example
getLoginShell()
{
./getShell.sh
USER_SHELL=`cat ./~shell.tmp`
rm -rf ./~shell.tmp
getGroups
}
# inputform dialog example
getUserName()
{
exec 3>&1
value=`$DIALOG --ok-label "Submit" \
--form "You can skip entering this data" \
15 50 0 \
"Title:" 1 1 "$Title"
1 10 20 0 \
"Name:"
2 1 "$Name"
2 10 20 0 \
"Surname:"
3 1 "$Surname" 3 10 20 0 \
"Phone:"
4 1 "$Phone"
4 10 20 0 \
2>&1 1>&3`
returncode=$?
exec 3>&USER_NAME=`echo "$value" |sed -e 's/^/ /'`
}
case $returncode in
0)
getLoginShell
;;
1)
exit
;;
esac
# Passwordform dialog example
# this func' prints 2 edits and asks user to input password 2 times
# if passwords don't match - it will print error message
getPassword()
{
# link descriptor #3 with stdout
exec 3>&1
value=`$DIALOG --cancel-label "Exit" \
--ok-label "Submit" \
--insecure \
--passwordform "Enter password and confirm it." \
10 50 0 \
"Password:"
1 1 "$pass"
1 20 20 0 \
"Confirm password:"
2 1 "$c_pass" 2 20 20 0 \
2>&1 1>&3`
retval=$?
# close descriptor
exec 3>&-
case $retval in
0)
passwords=(`echo "$value" |sed -e 's/^/ /'`);
}
if [ ${passwords[0]} != ${passwords[1]} ];then
$DIALOG --title "Error" \
--yesno \
" Passwords do not match \
Would you like to try again?" 0 0
case $? in
0)
getPassword
;;
1)
exit
;;
esac
else
USER_PASSWORD=`./md5.crypt ${passwords[0]}`;
getUserName;
fi
;;
1)
exit
;;
esac
# inputbox dialog example
# this function prints input box and prompts user to enter login
# if login exists - warning message 'll be shown
getLogin()
{
tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/test$$
trap "rm -rf $tempfile" 0 1 2 5 15
$DIALOG --cr-wrap \
--title "INPUT BOX" --clear \
--inputbox \
" It is usually recommended to only use usernames that begin with a lower
case letter or an underscore, and are only followed by lower case
letters, digits, underscores, dashes, and optionally terminated by a
dollar sign. In regular expression terms: [a-z_][a-z0-9_-]*[$]?
On Debian, the only constraints are that usernames must neither start
with a dash ('-') nor contain a colon (':') or a whitespace (space,
end of line, tabulation, etc.).
Usernames may only be up to 32 characters long." 0 0 2> $tempfile
retval=$?;
case $retval in
0)
USER_LOGIN=`cat $tempfile`;
flag=0;
# if user entered login exists in /etc/passwd file
# print error message and try to enter it again
for u_log in ${A_USERS[@]}
do
if [ "$USER_LOGIN" == "$u_log" ]; then
flag=1;
fi
done
if [ "$flag" == "1" ]; then
$DIALOG --title "Error" \
--yesno \
" Login you've entered already exists. \
Would you like to try again?" 0 0
case $? in
0)
getLogin
;;
1)
exit
;;
esac
else
getPassword
fi
;;
1)
exit;
;;
esac
}
# ------------------------------------MAIN-----------------------------------main()
{
if [ $USER != "root" ]; then
echo "You must be root, to execute this file!";
exit;
else
getShellsList "/etc/shells";
getUsersList "/etc/passwd";
getGroupsList "/etc/group";
$DIALOG --title
"useradd frontend" \
--yesno
"This is frontend to standart *NIX utility \
useradd. Press Yes, if you want to proceed and add \
user or no if you want to quit." 0 0
case $? in
0)
getLogin
;;
1)
$DIALOG --title "Exit" --clear \
--msgbox "Bye" 6 10
;;
esac
fi
}
# ------------------------------------MAIN-----------------------------------main
Вкратце, как это работает. Первым делом, при запуске скрипта, из файлов /etc/shells
/etc/passwd и /etc/group забираются данные в глобальные массивы A_SHELLS, A_USERS и
A_GROUPS соответственно. При вызовах функций для набора данных в массивы A_SHELLS
и A_GROUPS генерируются временные скрипты ./getShell.sh и ./getGroups.sh, которые
содержат в себе вызов диалога для выбора шелла и групп. Такой изврат с генерацией
скриптов необходим, поскольку изначально неизвестно — сколько и какие группы есть в
системе (аналогично с шеллами). Эти скрипты будут вызваны позднее. Дальше выводится
предложение ввести логин нового пользователя, и если такой логин еще не существует в
системе, то выводится предложение ввести пароль. 2 раза. Если оба введенных пароля
совпадают, то выводится предложение ввести информацию о пользователе (ее можно и не
вводить). После всего этого вызываются сгенерированные скрипты ./getShell.sh, за ним
./getGroups.sh для выбора шелла и групп, после чего диалог с выбором даты истечения
"срока годности" пароля и наконец вызов функции addUser() которая вызывает
непосредственно useradd с выбранными параметрами и сменяет владельца домашней
директории на нужного. Остальное в коментах.
Чуть не забыл, пароль в useradd надо переделать в зашифрованном виде. В мане
написано что для этого используется функция crypt() (http://www.opennet.ru/man.shtml?
topic=crypt&category=3&russian=0) Для возможности вызова ее из скрипта была написана
небольшая обертка md5.crypt.c которая компилируется командой
gcc -lcrypt md5.crypt.c -o md5.crypt
Ее задача — выводить в stdout результат работы функции crypt()
#include <stdio.h>
#include <unistd.h>
int main (int argc, char* argv[])
{
char *result;
printf ("%s\n", crypt (argv[1], "$1$"));
return (0);
}
Внизу унылые скриншоты того, что получилось (не всё)
1.4
Вместо послесловия
Просьба не спрашивать, что употреблял аффтар при написании данного быдлокода,
т.к. аффтар не помнит =). Так же просьба не тыкать мордой в баги, бо они там есть (при
вводе паролей, например, можно оставить одно поле пустым). Целью было не написать 100%
работающий скрипт, а показать возможности утилиты dialog ну и так вышло, что некоторые
фишки интерпретатора bash. Более простые примеры писать было неинтересно, т.к. их
можно нагуглить в огромнейшем количестве (правда они все какие-то одинаковые), ну и в
самом пакете dialog есть замечательная папка samples, так что если лень разбирать
получившийся быдлокод, то можно посмотреть простые примеры там. По поводу коментов в
коде на английском (если это можно назвать английским) просто было лень переключать
раскладку, все остальное, что вы видите на русском — копипаст откуда-то. И последняя
просьба для адептов командной строки — не стоит говорить, что "просто написать useradd
[options] LOGIN быстрее в 1000 раз. Зачем заморачиваться с подобной дурнёй". Да быстрее
=) Но цель данной "статьи" — показать как использовать утилиту dialog, а не как писать в
консоли безумные однострочные скрипты, наподобие perl -e '$??s:;s:s;;$?::s;;=]=>%{<-|}<&|`{;;y; -/:-@[-`{-};`-{/" -;;s;;$_;see'
При написании скрипта никто не пострадал был использован ОЧЕНЬ полезный
плагин для Eclipse - shelled. Качать тут http://sourceforge.net/projects/shelled. Он занимается
подсветкой синтаксиса в шелл файлах, и поддерживает тучу различных форматов скриптов,
включая bash, csh, zsh, ksh и так далее.
Download