Книга: Искусство программирования на языке сценариев командной оболочки
33.7. Разные советы
Для ведения учета использования сценария пользователями, добавьте следующие строки в сценарий. Они запишут в файл отчета название сценария и время запуска.
# Добавление (>>) учетной записи, об использовании сценария, в файл отчета.
date>> $SAVE_FILE # Дата и время.
echo $0>> $SAVE_FILE # Название сценария.
echo>> $SAVE_FILE # Пустая строка -- как разделитель записей.
# Не забудьте определить переменную окружения SAVE_FILE в ~/.bashrc
# (что нибудь, типа: ~/.scripts-run)
Оператор >> производит добавление строки в конец файла. А как быть, если надо добавить строку в начало существующего файла?
file=data.txt
title="***Это титульная строка в текстовом файле***"
echo $title | cat - $file >$file.new
# "cat -" объединяет stdout с содержимым $file.
# В результате получится
#+ новый файл $file.new, в начало которого добавлена строка $title.
Само собой разумеется, то же самое можно сделать с помощью sed.
Сценарий командной оболочки может использоваться как команда внутри другого сценария командной оболочки, Tcl, или wish сценария или, даже в Makefile. Он может быть вызван как внешняя команда из программы на языке C, с помощью функции system(), т.е. system("script_name");.
Собирайте свои библиотеки часто используемых функций и определений. Эти "библиотеки" могут быть "подключены" к сценариям, с помощью команды точка (.) или source.
# Сценарий-библиотека
# ------ -------
# Обратите внимание:
# Здесь нет sha-bang ("#!").
# И нет "живого кода".
# Определения переменных
ROOT_UID=0 # UID root-а, 0.
E_NOTROOT=101 # Ошибка -- "обычный пользователь".
MAXRETVAL=255 # Максимальное значение, которое могут возвращать функции.
SUCCESS=0
FAILURE=-1
# Функции
Usage () # Сообщение "Порядок использования:".
{
if [ -z "$1" ] # Нет аргументов.
then
msg=filename
else
msg=$@
fi
echo "Порядок использования: `basename $0` "$msg""
}
Check_if_root () # Проверка прав пользователя.
{ # из примера "ex39.sh".
if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Этот сценарий должен запускаться с привилегиями root."
exit $E_NOTROOT
fi
}
CreateTempfileName () # Создание "уникального" имени для временного файла.
{ # Из примера "ex51.sh".
prefix=temp
suffix=`eval date +%s`
Tempfilename=$prefix.$suffix
}
isalpha2 () # Проверка, состоит ли строка только из алфавитных символов.
{ # Из примера "isalpha.sh".
[ $# -eq 1 ] || return $FAILURE
case $1 in
*[!a-zA-Z]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac # Спасибо S.C.
}
abs () # Абсолютное значение.
{ # Внимание: Максимально возможное возвращаеиое значение
# не может превышать 255.
E_ARGERR=-999999
if [ -z "$1" ] # Проверка наличия входного аргумента.
then
return $E_ARGERR # Код ошибки, обычно возвращаемый в таких случаях.
fi
if [ "$1" -ge 0 ] # Если не отрицательное,
then #
absval=$1 # оставить как есть.
else # Иначе,
let "absval = (( 0 - $1 ))" # изменить знак.
fi
return $absval
}
tolower () # Преобразование строк символов в нижний регистр
{
if [ -z "$1" ] # Если нет входного аргумента,
then #+ выдать сообщение об ошибке
echo "(null)"
return #+ и выйти из функции.
fi
echo "$@" | tr A-Z a-z
# Преобразовать все входные аргументы ($@).
return
# Для записи результата работы функции в переменную, используйте операцию подстановки команды.
# Например:
# oldvar="A seT of miXed-caSe LEtTerS"
# newvar=`tolower "$oldvar"`
# echo "$newvar" # a set of mixed-case letters
#
# Упражнение: Добавьте в эту библиотеку функцию перевода символов в верхний регистр.
# toupper() [это довольно просто].
}
Для повышения ясности комментариев, выделяйте их особым образом.
## Внимание!
rm -rf *.zzy ## Комбинация ключей "-rf", в команде "rm", чрезвычайно опасна,
##+ особенно при удалении по шаблону.
#+ Продолжение комментария на новой строке.
# Это первая строка комментария
#+ это вторая строка комментария,
#+ это последняя строка комментария.
#* Обратите внимание.
#o Элемент списка.
#> Альтернативный вариант.
while [ "$var1" != "end" ] #> while test "$var1" != "end"
Для создания блочных комментариев, можно использовать конструкцию if-test.
#!/bin/bash
COMMENT_BLOCK=
# Если попробовать инициализировать эту переменную чем нибудь,
#+ то вы получите неожиданный результат.
if [ $COMMENT_BLOCK ]; then
Блок комментария --
=================================
Это строка комментария.
Это другая строка комментария.
Это еще одна строка комментария.
=================================
echo "Эта строка не выводится."
Этот блок комментария не вызывает сообщения об ошибке! Круто!
fi
echo "Эта строка будет выведена на stdout."
exit 0
Сравните этот вариант создания блочных комментариев со встроенным документом, использующимся для создания блочных комментариев.
С помощью служебной переменной $?, можно проверить -- является ли входной аргумент целым числом.
#!/bin/bash
SUCCESS=0
E_BADINPUT=65
test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null
# Проверка: "равно нулю или не равно нулю".
# 2>/dev/null подавление вывода сообщений об ошибках.
if [ $? -ne "$SUCCESS" ]
then
echo "Порядок использования: `basename $0` целое_число"
exit $E_BADINPUT
fi
let "sum = $1 + 25" # Будет выдавать ошибку, если $1 не является целым числом.
echo "Sum = $sum"
# Любая переменная может быть проверена таким образом, а не только входные аргументы.
exit 0
Диапазон, возвращаемых функциями значений, 0 - 255 -- серьезное ограничение. Иногда может оказаться весьма проблематичным использование глобальных переменных, для передачи результата из функции. В таких случаях можно порекомендовать передачу результатов работы функции через запись в stdout.
Пример 33-10. Необычный способ передачи возвращаемого значения
#!/bin/bash
# multiplication.sh
multiply () # Функции выполняет перемножение всех переданых аргументов.
{
local product=1
until [ -z "$1" ] # Пока не дошли до последнего аргумента...
do
let "product *= $1"
shift
done
echo $product # Значение не будет выведено на экран,
} #+ поскольку оно будет записано в переменную.
mult1=15383; mult2=25211
val1=`multiply $mult1 $mult2`
echo "$mult1 X $mult2 = $val1"
# 387820813
mult1=25; mult2=5; mult3=20
val2=`multiply $mult1 $mult2 $mult3`
echo "$mult1 X $mult2 X $mult3 = $val2"
# 2500
mult1=188; mult2=37; mult3=25; mult4=47
val3=`multiply $mult1 $mult2 $mult3 $mult4`
echo "$mult1 X $mult2 X $mult3 X mult4 = $val3"
# 8173300
exit 0
Такой прием срабатывает и для строковых значений. Таким образом, функция может "возвращать" и нечисловой результат.
capitalize_ichar () # Первый символ всех строковых аргументов
{ #+ переводится в верхний регистр.
string0="$@" # Принять все аргументы.
firstchar=${string0:0:1} # Первый символ.
string1=${string0:1} # Остаток строки.
FirstChar=`echo "$firstchar" | tr a-z A-Z`
# Преобразовать в верхний регистр.
echo "$FirstChar$string1" # Выдать на stdout.
}
newstring=`capitalize_ichar "each sentence should start with a capital letter."`
echo "$newstring" # Each sentence should start with a capital letter.
Используя этот прием, функция может "возвращать" даже несколько значений.
Пример 33-11. Необычный способ получения нескольких возвращаемых значений
#!/bin/bash
# sum-product.sh
# Функция может "возвращать" несколько значений.
sum_and_product () # Вычисляет сумму и произведение аргументов.
{
echo $(( $1 + $2 )) $(( $1 * $2 ))
# Вывод на stdout двух значений, разделенных пробелом.
}
echo
echo "Первое число: "
read first
echo
echo "Второе число: "
read second
echo
retval=`sum_and_product $first $second` # Получить результат.
sum=`echo "$retval" | awk '{print $1}'` # Первое значение (поле).
product=`echo "$retval" | awk '{print $2}'` # Второе значение (поле).
echo "$first + $second = $sum"
echo "$first * $second = $product"
echo
exit 0
Следующая хитрость -- передача массива в функцию, и "возврат" массива из функции.
Передача массива в функцию выполняется посредством записи элементов массива, разделенных пробелами, в переменную, с помощью операции подстановки команды. Получить массив обратно можно, следуя вышеописанной стратегии, через вывод на stdout, а затем, с помощью все той же операции подстановки команды и оператора ( ... ) -- записать в массив.
Пример 33-12. Передача массива в функцию и возврат массива из функции
#!/bin/bash
# array-function.sh: Передача массива в функцию и...
# "возврат" массива из функции
Pass_Array ()
{
local passed_array # Локальная переменная.
passed_array=( `echo "$1"` )
echo "${passed_array[@]}"
# Список всех элементов в новом массиве,
#+ объявленном и инициализированном в функции.
}
original_array=( element1 element2 element3 element4 element5 )
echo
echo "original_array = ${original_array[@]}"
# Список всех элементов исходного массива.
# Так можно отдать массив в функцию.
# **********************************
argument=`echo ${original_array[@]}`
# **********************************
# Поместив все элементы массива в переменную,
#+ разделяя их пробелами.
#
# Обратите внимание: метод прямой передачи массива в функцию не сработает.
# Так можно получить массив из функции.
# *****************************************
returned_array=( `Pass_Array "$argument"` )
# *****************************************
# Записать результат в переменную-массив.
echo "returned_array = ${returned_array[@]}"
echo "============================================================="
# А теперь попробуйте получить доступ к локальному массиву
#+ за пределами функции.
Pass_Array "$argument"
# Функция выведет массив, но...
#+ доступ к локальному массиву, за пределами функции, окажется невозможен.
echo "Результирующий массив (внутри функции) = ${passed_array[@]}"
# "ПУСТОЕ" ЗНАЧЕНИЕ, поскольку это локальная переменная.
echo
exit 0
Более сложный пример передачи массивов в функции, вы найдете в Пример A-11.
Использование конструкций с двойными круглыми скобками позволяет применять C-подобный синтаксис операций присвоения и инкремента переменных, а также оформления циклов for и while. См. Пример 10-12 и Пример 10-17.
Иногда очень удобно "пропускать" данные через один и тот же фильтр, но с разными параметрами, используя конвейерную обработку. Особенно это относится к tr и grep.
# Из примера "wstrings.sh".
wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z |
tr -cs '[:alpha:]' Z | tr -s '173-377' Z | tr Z ' '`
Пример 33-13. Игры с анаграммами
#!/bin/bash
# agram.sh: Игры с анаграммами.
# Поиск анаграмм...
LETTERSET=etaoinshrdlu
anagram "$LETTERSET" | # Найти все анаграммы в наборе символов...
grep '.......' | # состоящие, как минимум из 7 символов,
grep '^is' | # начинающиеся с 'is'
grep -v 's$' | # исключая множественное число
grep -v 'ed$' # и глаголы в прошедшем времени
# Здесь используется утилита "anagram"
#+ которая входит в состав пакета "yawl" , разработанного автором.
# http://ibiblio.org/pub/Linux/libs/yawl-0.2.tar.gz
exit 0 # Конец.
bash$ sh agram.sh
islander
isolate
isolead
isotheral
См. также Пример 27-2, Пример 12-18 и Пример A-10.
Для создания блочных комментариев можно использовать "анонимные встроенные документы". См. Пример 17-10.
Попытка вызова утилиты из сценария на машине, где эта утилита отсутствует, потенциально опасна. Для обхода подобных проблем можно воспользоваться утилитой whatis.
CMD=command1 # Основной вариант.
PlanB=command2 # Запасной вариант.
command_test=$(whatis "$CMD" | grep 'nothing appropriate')
# Если 'command1' не найдена в системе, то 'whatis' вернет
#+ "command1: nothing appropriate."
#==> От переводчика: Будьте внимательны! Если у вас локализованная версия whatis
#==> то вывод от нее может отличаться от используемого здесь ('nothing appropriate')
if [[ -z "$command_test" ]] # Проверка наличия утилиты в системе.
then
$CMD option1 option2 # Запуск команды с параметрами.
else # Иначе,
$PlanB #+ запустить command2 (запасной вариант).
fi
Команда run-parts удобна для запуска нескольких сценариев, особенно в комбинации с cron или at.
Было бы неплохо снабдить сценарий графическим интерфейстом X-Window. Для этого можно порекомендовать пакеты Xscript, Xmenu и widtools. Правда, первые два, кажется больше не поддерживаются разработчиками. Зато widtools можно получить здесь.
Для постороения приложений с графическим интерфейсом, можно попробовать Tk, или wish (надстройка над Tcl), PerlTk (Perl с поддержкой Tk), tksh (ksh с поддержкой Tk), XForms4Perl (Perl с поддержкой XForms), Gtk-Perl (Perl с поддержкой Gtk) или PyQt (Python с поддержкой Qt).
- РАЗДЕЛ VIII. ПОЛЕЗНЫЕ СОВЕТЫ
- Полезные советы
- Глава 5 Избегайте единого универсального решения Нравится вам или нет, ваши клиенты – разные
- Каждый раз после загрузки Windows запускаются разные бесполезные приложения, а возле часов появляются лишние значки. Что...
- Можно ли сравнить файлы Microsoft Word, правки в которые вносили разные пользователи?
- Глава 14 Советы хакера
- 4.15. Советы по конфигурированию Firewall
- 7.7.3. Секреты и советы
- Советы покупателям
- 14.12. Короткие советы
- Советы для эффективного рисования
- Советы по навигации в Сети