-
Для ведения учета использования сценария пользователями, добавьте следующие строки в сценарий. Они запишут в файл отчета название сценария и время запуска.
# Добавление (>>) учетной записи, об использовании сценария, в файл отчета. 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-11. Необычный способ передачи возвращаемого значения
#!/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-12. Необычный способ получения нескольких возвращаемых значений
#!/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-13. Передача массива в функцию и возврат массива из функции
#!/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-14. Игры с анаграммами
#!/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-3, Пример 12-21 и Пример A-10.
-
Для создания блочных комментариев можно использовать "анонимные встроенные документы". См. Пример 17-11.
-
Попытка вызова утилиты из сценария на машине, где эта утилита отсутствует, потенциально опасна. Для обхода подобных проблем можно воспользоваться утилитой 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 (widget tools) требует наличия библиотеки XForms. Кроме того, необходимо слегка подправить Makefile, чтобы этот пакет можно было собрать на типичной Linux-системе. Но хуже всего то, что три из шести виджетов не работают :-(( (segfault).
Утилита dialog -- еще один способ создания диалоговых форм из сценариев командной оболочки. Эта утилита предназначена для работы в текстовой консоли, но имеются ее "наследники" -- gdialog, Xdialog и kdialog, которые используют графические элементы X-Windows для построения диалоговых форм.
Пример 33-15. Сценарий с графическим интерфейсом
#!/bin/bash # dialog.sh: Использование виджетов 'gdialog'. # В вашей системе должна быть установлена утилита 'gdialog'. # Идея создания этого сценария появилась после прочтения статьи # "Scripting for X Productivity," by Marco Fioretti, # LINUX JOURNAL, Issue 113, September 2003, pp. 86-9. # Спасибо всем сотрудникам LJ. # Ошибка ввода в диалоговом окне. E_INPUT=65 # Размеры окна. HEIGHT=50 WIDTH=60 # Имя выходного файла (конструируется добавлением суффикса к имени файла-сценария). OUTFILE=$0.output # Вывести содержимое файла-сценария в отдельном окне. gdialog --title "Displaying: $0" --textbox $0 $HEIGHT $WIDTH # Попробуем записать значение, введенное в окне. echo -n "VARIABLE=\"" > $OUTFILE # Кавычка на случай, если пользователь введет #+ несколько слов, разделенных пробелами. gdialog --title "User Input" --inputbox "Введите значение переменной:" \ $HEIGHT $WIDTH >> $OUTFILE if [ "$?" -eq 0 ] # Хороший тон -- проверка кода завершения. then echo "Диалог с пользователем завершился без ошибок." else echo "Обнаружены ошибки во время диалога с пользователем." # Или была нажата кнопка "Отменить" ("Cancel") вместо "OK". rm $OUTFILE exit $E_INPUT fi echo -n "\"" >> $OUTFILE # Завершающая кавычка (см. выше). # Теперь прочитаем значение переменной из файла и выведем его. . $OUTFILE # 'Выходной' файл. echo "Было введено значение переменной: "$VARIABLE"" rm $OUTFILE # Удалить временный файл. exit 0
Кроме того, для построения приложений с графическим интерфейсом, можно попробовать Tk, или wish (надстройка над Tcl), PerlTk (Perl с поддержкой Tk), tksh (ksh с поддержкой Tk), XForms4Perl (Perl с поддержкой XForms), Gtk-Perl (Perl с поддержкой Gtk) или PyQt (Python с поддержкой Qt).