Основы написания сценариев
Содержание
- Последовательное выполнение команд
- Использование переменных
- Параметры командной строки
- Арифметические вычисления
- Условный оператор
- Проверка условий
- Команда case
- Цикл for
- Циклы while и until
Последовательное выполнение команд
Основная цель написания сценариев — автоматизация действий пользователя по выполнению часто повторяемых задач. Эти задачи могут иметь отношение к администрированию системы, обработке текстовых файлов, преобразованию видеоформатов и пр. В любом случае перед автором сценария стоит задача упрощения работы, выполняемой пользователем.
В простейшем случае можно считать, что сценарий — это последовательность команд системы. Такую последовательность можно выполнить и без написания сценария, достаточно написать их в командной строке, разделяя точкой с запятой:
$date ; ls Thu Nov 13 22:06:12 MSK 2008 calc data2.dat program.c data.dat exams.tex text.txt $
Более интересна возможность сохранения последовательности команд в текстовом файле (обычно имеющем расширение .sh), который потом можно было бы многократно запускать. Такой файл мог бы выглядеть следующим образом:
#!/bin/bash #Call two commands date ls
В первой строке сценария указывается интерпретатор сценария. В нашем случае это Bourne Again Shell (bash). Строки, начинающиеся с #, являются комментариями. Третья и четвертая строки — собственно последовательность исполняемых команд. Предположим, что сценарий сохранен в файле script.sh. Чтобы этот сценарий можно было бы запустить, ему нужно дать права на выполнение:
$chmod a+x script1.sh $./script1.sh Thu Nov 13 22:11:02 MSK 2008 calc data2.dat program.c text.txt data.dat exams.tex script1.sh $
Обратите внимание на команду запуска сценария. В этом примере он запускается из каталога, в котором находится, поэтому явно прописывается ссылка на текущий каталог. Если в командной строке написать только имя сценария, то командный интерпретатор будет искать его в каталогах, указанных в переменной окружения PATH.
Иногда в сценариях требуется вывести какой-нибудь текст. Для этого можно использовать команду echo:
$echo This is a test This is a test $
Если текст содержит специальные символы, то текст нужно заключать в кавычки, причем неважно, одинарные или двойные:
$echo "This is a test to see if you.re paying attention" This is a test to see if you.re paying attention $echo 'Rich says "scripting is easy".' Rich says "scripting is easy". $
Эту же команду можно добавить в сценарий для вывода дополнительной информации:
#!/bin/bash #Call two commands echo The time and date are: date echo And these are files in current directory: ls
Результат работы этого сценария будет следующим:
$./script1.sh The time and date are: Thu Nov 13 22:23:05 MSK 2008 And these are files in current directory: calc data2.dat program.c text.txt data.dat exams.tex script1.sh $
Команда echo по умолчанию после вывода строки переходит на новую строку. Ключ -n позволяет изменить это поведение. Первый ее вызов можно исправить так:
echo -n "The time and date are: "
Тогда в результате работы сценария будет выведено следующее:
$./script1.sh The time and date are: Thu Nov 13 23:06:30 MSK 2008 And these are files in current directory: calc data2.dat program.c text.txt data.dat exams.tex script1.sh $
Использование переменных
В сценариях можно использовать два вида переменных: переменные окружения и переменные, определенные пользователем. Узнать значения всех переменные окружения можно, выполнив команду env:
$ env BACKSPACE=Delete BASH=/bin/bash EUID=1000 HOME=/home/bravit HOSTNAME=edu HOSTTYPE=i586 LANG=en LANGUAGE=en US:en LINES=24 LOGNAME=bravit ...
Внутри сценария получить значения этих переменных можно, поставив перед их именем знак $:
#!/bin/bash #Using variables echo "Home directory: $HOME"
$./script2.sh Home directory: /home/bravit $
Обратите, что здесь обращение к переменной происходит внутри строки, тем не менее, ее значение подставляется правильно. Если знак $ нужно вывести на консоль, его нужно предварить обратным слешем:
$ echo "The cost of the item is \$15" The cost of the item is $15
Значения пользовательских переменных присваиваются непосредственно в тексте сценария:
#!/bin/bash # testing variables days=10 guest="Katie" echo "$guest checked in $days days ago" days=5 guest="Jessica" echo "$guest checked in $days days ago"
Результат выполнения:
$./script3.sh Katie checked in 10 days ago Jessica checked in 5 days ago $
Обратите внимание на то, что знак = должен быть написан без пробелов слева и справа. В противном случае первое слово будет восприниматься как команда.
Присваивание значения одной переменной другой будет выглядеть так:
var2=$var1
Знак $ здесь появляется только в правой части, поскольку именно там происходит чтения значения переменной var1.
Переменной можно присвоить результат вывода любой команды. Делается это с помощью оператора «обратные кавычки»:
$cat script4.sh #!/bin/bash # testing variables current_time=`date` echo "Current time is $current_time" $./script4.sh Current time is Thu Nov 13 23:43:00 MSK 2008 $
Приведем более осмысленный пример использования этого оператора. Предположим, что необходимо регулярно сохранять список файлов каталога в файл, в имя которого входит текущее время и дата. В этом случае имя файла должно формироваться на основе вывода команды date:
#!/bin/bash # Using backtick today=`date +%y%m%d` ls > log.$today
После выполнения такого сценария в текущем каталоге появится файл, имя которого будет похоже на "log.081113". Команда date с ключом +%y%m%d выводит две цифры номера года, две цифры номера месяца и две цифры дня:
$date +%y%m%d 081113
Чтение параметров командной строки
Параметры, необходимые для работы сценария, удобно задавать при его запуске в командной строке. Доступ к этим параметрам внутри сценария выполняется с помощью специальных переменных с именами $1 (первый параметр), $2 (второй параметр) и т.д. Переменная с именем $# содержит общее количество заданных параметров, а переменная $0 — имя сценария. Предположим, к примеру, что сценарий (params.sh) имеет следующий вид:
#!/bin/sh echo "Сценарий: $0" echo "Всего параметров: $#" echo "Первый параметр: $1" echo "Второй параметр: $2"
Тогда при его запуске с указанием двух параметров получим следующий результат:
$ ./params.sh first second Сценарий: ./params.sh Всего параметров: 2 Первый параметр: first Второй параметр: second
Чтобы включить в параметр символ пробела, весь его текст следует заключить в кавычки, например:
$ ./params.sh "first second" Сценарий: ./params.sh Всего параметров: 1 Первый параметр: first second Второй параметр:
Арифметические вычисления
Для вычислений с целыми значениями используется синтаксис с двойными круглыми скобками. Посмотрите пример с его использованием:
#!/bin/bash var1=100 var2=50 var3=45 var4=$(( $var1 * ($var2 - $var3) )) echo The final result is $var4
В результате выполнения получаем:
$./script6.sh The final result is 500 $
К сожалению, для вещественной арифметики этот способ не подходит. При необходимости проведения в сценариях вычислений с вещественными числами, нужно пользоваться командой bc.
Ранее для целочисленной арифметики в bash использовался синтаксис с квадратными скобками (они применялись вместо двойных круглых). Однако сейчас он признан устаревшим и планируется к исключению.
Условные операторы и циклы
Условный оператор
В простейшей форме условный оператор выглядит так:
if command then commands fi
В отличии от традиционных языков программирования, после слова if здесь пишется не условие, а команда, причем анализируется код завершения этой команды. Если код завершения равен нулю, то содержимое условного оператора выполняется, а если не равен, то не выполняется.
Сравните два примера:
$cat test1 #!/bin/bash if date then echo It works! fi $./test1 Fri Nov 14 09:56:48 MSK 2008 It works! $
Здесь команда date выполняется успешно и возвращает нулевое значение, поэтому выполняется и действие внутри условного оператора.
$cat test1a #!/bin/bash if unexisted_command then echo It works! fi echo if is over! $./test1a ./test1a: line 2: unexisted_command: command not found if is over! $
А в этом примере тело условного оператора не выполнилось.
Некоторые команды возвращают ненулевое значение при неудачном выполнении. Например, команда grep, которая ищет строки в файлах по заданному критерию, возвращает ненулевое значение, если ни одной строки не найдено. Таким образом можно в зависимости от наличия строки в файле выполнять или не выполнять некоторые действия.
$cat test2 #!/bin/bash # testing multiple commands in the then section testuser=bravit if grep $testuser /etc/passwd then echo The bash files for user $testuser are: ls -a /home/$testuser/.b* fi $./test2 bravit:*:1001:0:Vitaly Bragilevsky:/home/bravit:/bin/csh The bash files for user bravit are: /home/bravit/.bash_history /home/bravit/.bashrc $
Если переменной testuser присвоить имя несуществующего пользователя, то выведено ничего не будет:
$cat test2 #!/bin/bash # testing multiple commands in the then section testuser=unexisted if grep $testuser /etc/passwd then echo The bash files for user $testuser are: ls -a /home/$testuser/.b* fi $./test2 $
Модифицируем сценарий так, чтобы при отсутствии пользователя выводилось соответствующее сообщение. Для этого нам понадобится вторая форма условного оператора:
if command then commands else commands fi
Исправленный сценарий и результат его выполнения будут выглядеть так:
$cat test2 #!/bin/bash # testing multiple commands in the then section testuser=unexisted if grep $testuser /etc/passwd then echo The bash files for user $testuser are: ls -a /home/$testuser/.b* else echo "User $testuser is not registered" fi $./test2 User unexisted is not registered $
Условные операторы можно вкладывать один в другой, проверяя различные условия:
if command1 then command set 1 elif command2 then command set 2 elif command3 then command set 3 elif command4 then command set 4 else commands fi
Ветка else может отсутствовать.
Проверка условий
Есть еще один вариант синтаксиса условного оператора с возможностью проверки условий.
if [ condition ] then commands fi
Обратите внимание на пробелы до и после условия condition, при их отсутствии сценарий не будет правильно интерпретироваться.
Выделяют три вида условий, которые можно проверять в квадратных скобках:
- числовые (только для целых чисел);
- строковые (сравнение строк);
- файловые (существование, права доступа и пр.).
Рассмотрим пример со сравнением чисел:
$ cat test5 #!/bin/bash # using numeric test comparisons val1=10 val2=11 if [ $val1 -gt 5 ] then echo "The test value $val1 is greater than 5" fi if [ $val1 -eq $val2 ] then echo "The values are equal" else echo "The values are different" fi
Операторы сравнения обозначаются здесь -gt и -eq. Первый означает «больше», а второй «равно». В следующей таблице приведены все операторы сравнения целых чисел.
Операция | Описание |
n1 -eq n2 |
n1 равно n2 (equal) |
n1 -ge n2 |
n1 больше или равно n2 (greater or equal) |
n1 -gt n2 |
n1 больше n2 (greater than) |
n1 -le n2 |
n1 меньше или равно n2 (less or equal) |
n1 -lt n2 |
n1 меньше n2 (less than) |
n1 -ne n2 |
n1 не равно n2 (not equal) |
При сравнении строк операторы выглядят более традиционно:
$ cat test5 #!/bin/bash # using string test comparisons # testing string equality testuser=rich if [ $USER = $testuser ] then echo "Welcome $testuser" fi $ ./test7 Welcome rich $
Все операторы сравнения строк перечислены в таблице:
Операция | Описание |
str1 = str2 |
Строки равны |
str1 != str2 |
Строки не равны |
str1 < str2 |
Строка str1 меньше строки str2 |
str1 > str2 |
Строка str1 больше строки str2 |
-n str1 |
Строка str1 имеет ненулевую длину |
-z str1 |
Строка str1 имеет нулевую длину |
Оператор > необходмо экранировать обратным слешем, иначе он будет считаться символом перенаправления ввода-вывода:
$ cat test9 #!/bin/bash # using string comparisons val1=baseball val2=hockey if [ $val1 \> $val2 ] then echo "$val1 is greater than $val2" else echo "$val1 is less than $val2" fi $./test9 baseball is less than hockey $
В сравнении строк используется лексикографический порядок с учетом регистра символов.
Самой полезной при программировании сценариев является возможность проверки различных условий для файлов. В следующей таблице перечислены соответствующие операторы:
Операция | Описание |
-d file |
Файл с именем file является каталогом |
-e file |
Файл существует |
-f file |
Файл существует и является именно файлом (а не каталогом, к примеру) |
-r file |
Файл существует, и у сценария есть права на его чтение |
-s file |
Файл существует и не пуст. |
-w file |
Файл существует, и у сценария есть права на его запись |
-x file |
Файл существует и является исполняемым |
-O file |
Файл существует и принадлежит текущему пользователю |
-G file |
Файл существует и его группа совпадает с группой пользователя |
file1 -nt file2 |
file1 новее, чем file2 |
file1 -ot file2 |
file1 старее, чем file2 |
В следующем сценарии проверяется существование и возможность чтения файла паролей:
$cat test6 #!/bin/bash # testing if you can read a file pwfile=/etc/shadow # first, test if the file exists, and is a file if [ -f $pwfile ] then # now test if you can read it if [ -r $pwfile ] then tail $pwfile else echo "Sorry, I’m unable to read the $pwfile file" fi else echo "Sorry, the file $file doesn’t exist" fi $./test6 Sorry, I’m unable to read the /etc/shadow file $
В еще одном примере проверяется, можно ли запустить файл, и он запускается:
$cat test7 #!/bin/bash # testing file execution if [ -x test6 ] then echo "You can run the script:" ./test6 else echo "Sorry, you are unable to execute the script" fi
Условия можно объединять с помощью логических операций:
-
[ condition1 ] && [ condition2 ]
-
[ condition1 ] || [ condition2 ]
Например:
$cat test8 #!/bin/bash # testing compound comparisons if [ -d $HOME ] && [ -w $HOME/testing ] then echo "The file exists and you can write to it" else echo "I can’t write to the file" fi $ ./test8 I can’t write to the file $ touch $HOME/testing $./test8 The file exists and you can write to it $
Команда case
Оператор case используется для выбора одного из нескольких вариантов:
case variable in pattern1 | pattern2) commands1;; pattern3) commands2;; *) default commands;; esac
Каждый вариант заканчивается закывающей скобкой, вертикальная черта означает "или", конец набора комманд для одного варианта заканчивается двумя точками с запятой.
$ cat test9 #!/bin/bash # using the case command case $USER in rich | barbara) echo "Welcome, $USER" echo "Please enjoy your visit";; testing) echo "Special testing account";; jessica) echo "Don’t forget to log off when you’re done";; *) echo "Sorry, you’re not allowed here";; esac $ ./test9 Welcome, rich Please enjoy your visit $
Действия, аналогичные оператору case, можно выполнить и средствами условного оператора, однако считается, что синтаксис case является более наглядным.
Цикл for
В программах часто требуется перебирать наборы данных, выполняя для каждого из них некоторые действия. Такими наборами данных могут быть список всех файлов каталога, список всех пользователей системы, все строки файла. Соответствующая конструкция в языках программирования называется циклом.
Общий вид цикла for для языка сценариев bash выглядит так:
for var in list do commands done
Параметром list задается набор перебираемых данных. На каждом шаге цикла переменная var принимает значение одного из элементов списка, и для каждого такого значения выполняются команды, написанные между do и done. Рассмотрим разные способы задания набора перебираемых даных.
1) Самый простой способ задания списка — непосредственное перечисление его элементов.
$cat test1 #!/bin/bash for student in Ivanov Petrov Sidorov do echo "Student is $student" done $./test1 Student is Ivanov Student is Petrov Student is Sidorov $
Если отдельные элементы списка содержат пробелы, их нужно записывать с помощью кавычек.
$cat test1b #!/bin/bash for student in "Ivanov I.I." "Petrov P.P." "Sidorov S.S." do echo "Student is $student" done $./test1b Student is Ivanov I.I. Student is Petrov P.P. Student is Sidorov S.S. $
Обратите внимание, что после завершения цикла переменная student остается доступной, ее значение совпадает с последним элементом списка.
2) Список переменных может задаваться переменнной.
$cat test2 #!/bin/bash students="Ivanov Petrov Sidorov" students=$students" Kozlov" for student in $students do echo "Student is $student" done $./test2 Student is Ivanov Student is Petrov Student is Sidorov Student is Kozlov $
Заметьте, каким образом здесь проведено сцепление (конкатенация) строк.
3) Список переменных может являться результатом выполнения команды. Предположим, что в текущем каталоге есть файл names:
$cat names Ivanov Petrov Sidorov $
В следующем сценарии все строки этого файла перебираются в цикле:
$cat test3 #!/bin/bash file=names for student in `cat $file` do echo "Student is $student" done $./test3 Student is Ivanov Student is Petrov Student is Sidorov $
Если бы строки файла содержали пробелы, то каждая их часть перебиралась бы отдельно. Это связано с текущим символом-разделителем. По умолчанию, в качестве разделителя выступают пробелы, символы табуляции и переводы на новую строку, однако значение символа-разделителя можно изменить. Предположим, что файл с именами студентов выглядит так:
$cat names2 Ivanov I.I. Petrov P.P. Sidorov S.S. $
Следующий сценарий устанавливает в качестве разделителя только перевод строки:
$cat test4 #!/bin/bash IFS=$'\n' file=names2 for student in `cat $file` do echo "Student is $student" done $./test4 Student is Ivanov I.I. Student is Petrov P.P. Student is Sidorov S.S. $
Можно также установить сразу несколько разделителей:
IFS=$'\n':;"
4) Наконец, список значений может быть результатом использования шаблонных символов в именах файлов.
$cat test5 #!/bin/bash for file in /etc/* do echo "File is $file" done $./test5 File is /etc/X11 File is /etc/aliases File is /etc/amd.map File is /etc/apmd.conf File is /etc/auth.conf File is /etc/bluetooth File is /etc/crontab ...
В списке может указываться сразу несколько имен:
for file in /dir1/* /dir2/* /dir3/* do echo "File is $file" done
Циклы while и until
В простейшем варианте цикл while выглядит так:
while [ condition ] do other commands done
Например,
$ cat test6 #!/bin/bash # while command test var1=10 while [ $var1 -gt 0 ] do echo $var1 var1=$(( $var1 - 1 )) done $ ./test6 10 9 8 7 6 5 4 3 2 1 $
Команда until имеет следующий вид:
until [ condition ] do commands done
Например,
$ cat test12 #!/bin/bash # using the until command var1=100 until [ $var1 -eq 0 ] do echo $var1 var1=$(( $var1 - 25 )) done $ ./test12 100 75 50 25
Смысл проверяемого командой until условия следующий: цикл выполняется, пока условие не станет истинным.