Фундаментальные основы Linux. Часть VI. Сценарии
Оригинал:
Linux Fundamentals
Автор: Paul Cobbaut
Дата публикации: 16 октября 2014 г.
Перевод: А.Панин
Дата перевода: 21 декабря 2014 г.
Глава 21. Введение в разработку сценариев
Командные оболочки, такие, как bash
и Korn shell
поддерживают программные конструкции, которые могут быть сохранены в форме сценариев. Эти сценарии
, в свою очередь, впоследствии могут использоваться в качестве реализаций дополнительных команд командной оболочки
. Многие команды Linux реализованы в форме сценариев
. Например, сценарии для обслуживания профиля пользователя
исполняются при входе пользователя в систему, а сценарии инициализации
— при остановке и запуске демона
.
Исходя из вышесказанного, системные администраторы также должны иметь базовое представление о сценариях командной оболочки
для понимания того, как функционируют их серверы, запускаются, обновляются, исправляются, поддерживаются, настраиваются и удаляются их приложения, а также для понимания устройства программного окружения.
Целью данной главы является предоставление достаточной информации для того, чтобы вы могли читать и понимать сценарии. Для этого вам совершенно не нужно становиться разработчиком сложных сценариев.
Предварительное чтение
Перед тем, как приступить к чтению данной главы, вам необходимо прочитать и понять главы из Части III. "Раскрытие команд командной оболочкой"
и Части IV. "Программные каналы и команды"
.
Hello world
По аналогии с практически любым курсом по программированию, мы начнем работу с разработки сценария hello_world
. Следующий сценарий будет выводить строку Hello World
.
echo Hello World
После создания этого простого сценария в редакторе vi
или с помощью команды echo
вам придется выполнить команду chmod +x hello_world
для того, чтобы сделать файл сценария исполняемым. В том случае, если вы не будете добавлять путь к директории с вашими сценариями в список директорий из переменной окружения PATH, вам придется вводить полный путь к сценарию для того, чтобы командная оболочка смогла найти его.
[paul@RHEL4a ~]$ echo echo Hello World > hello_world [paul@RHEL4a ~]$ chmod +x hello_world [paul@RHEL4a ~]$ ./hello_world Hello World [paul@RHEL4a ~]$
She-bang
Давайте немного доработаем наш пример, разместив строку #!/bin/bash
в начале сценария. Последовательность символов #!
называется she-bang
(или иногда sha-bang
), причем слово she-bang
составлено из названий двух первых символов сценария.
#!/bin/bash echo Hello World
Вы ни при каких обстоятельствах не можете быть уверены в том, какая командная оболочка используется в системе пользователя. Сценарий, превосходно работающий в командной оболочке bash
, может не работать в командных оболочках ksh
, csh
или dash
. Для того, чтобы проинструктировать командную оболочку о необходимости запуска вашего сценария в определенной командной оболочке, вы должны начинать ваш сценарий с последовательности символов she-bang
, после которой должен располагаться путь к бинарному файлу командной оболочки, в которой сценарий должен исполняться. Приведенный ниже сценарий будет исполняться в командной оболочке bash.
#!/bin/bash echo -n hello echo Дочерняя командная оболочка bash `echo -n hello`
А этот сценарий будет исполняться в командной оболочке Korn shell (за исключением тех случаев, когда по пути /bin/ksh
расположена жесткая ссылка на бинарный файл /bin/bash
). Файл /etc/shells
содержит список путей к командным оболочкам, установленным в вашей системе.
#!/bin/ksh echo -n hello echo Дочерняя командная оболочка Korn shell `echo -n hello`
Комментарий
Давайте еще немного усовершенствуем наш пример, добавив строки комментариев.
#!/bin/bash # # Сценарий Hello World # echo Hello World
Переменные
Ниже приведен простой пример объявления переменной в сценарии.
#!/bin/bash # # простая переменная в сценарии # var1=4 echo var1 = $var1
Сценарии могут содержать переменные, но ввиду того, что сценарии исполняются в своих собственных экземплярах командных оболочек, переменные не смогут пережить момент завершения исполнения сценария.
[paul@RHEL4a ~]$ echo $var1 [paul@RHEL4a ~]$ ./vars var1 = 4 [paul@RHEL4a ~]$ echo $var1 [paul@RHEL4a ~]$
Использование рабочей командной оболочки
К счастью, у вас имеется возможность исполнения сценария в той же рабочей командной оболочке; данная техника называется использованием рабочей командной оболочки
(sourcing a script
).
[paul@RHEL4a ~]$ source ./vars var1 = 4 [paul@RHEL4a ~]$ echo $var1 4 [paul@RHEL4a ~]$
Представленная выше команда аналогична следующей команде.
[paul@RHEL4a ~]$ . ./vars var1 = 4 [paul@RHEL4a ~]$ echo $var1 4 [paul@RHEL4a ~]$
Отладка сценария
Другой способ исполнения сценария в отдельной командной оболочке заключается во вводе команды bash
перед именем сценария, которое в этом случае будет передаваться бинарному файлу командной оболочки в качестве параметра.
paul@debian6~/test$ bash runme 42
Дополнение данной команды до формы bash -x
позволит вам ознакомиться со всеми командами, исполняемыми командной оболочкой (после раскрытия команд).
paul@debian6~/test$ bash -x runme + var4=42 + echo 42 42 paul@debian6~/test$ cat runme # сценарий runme var4=42 echo $var4 paul@debian6~/test$
Обратите внимание на отсутствие строки комментария (первым символом которой является символ #), а также замену значения переменной перед исполнением команды для вывода данных echo
.
Предотвращение подмены имен файлов сценариев с целью повышения привилегий в системе
Какой-либо пользователь может попытаться выполнить сценарий с установленным битом setuid
с целью повышения привилегий в системе, подменив имя файла этого сценария путем создания ссылки на него (root spoofing
). Это довольно редкая, но возможная атака. Для повышения безопасности функционирования вашего сценария, а также для предотвращения подмены имен файлов сценариев, вам необходимо добавить после строки #!/bin/bash
параметр --
, который позволит деактивировать механизм обработки параметров, благодаря чему командная оболочка не примет дополнительных параметров.
#!/bin/bash - или #!/bin/bash --
Любые аргументы, расположенные после последовательности символов --
будут рассматриваться как имена файлов и аргументы. Аргумент -
эквивалентен аргументу --
.
Практическое задание: введение в разработку сценариев
-
0. Используйте различные имена для файлов ваших сценариев и сохраните их на будущее!
-
1. Создайте сценарий, который выводит имя города.
-
2. Сделайте так, чтобы сценарий исполнялся в командной оболочке bash.
-
3. Сделайте так, чтобы сценарий исполнялся в командной оболочке Korn shell.
-
4. Создайте сценарий, в котором осуществляется объявление двух переменных с последующим выводом их значений.
-
5. Предыдущий сценарий не должен оказывать воздействия на вашу рабочую командную оболочку (объявленные в нем переменные не будут существовать вне сценария). А теперь запустите сценарий таким образом, чтобы он оказал влияние на вашу рабочую командную оболочку.
-
6. Существует ли более короткая форма команды для
использования рабочей командной оболочки с целью исполнении сценария
? -
7. Добавьте комментарии в ваши сценарии для того, чтобы знать о выполняемых ими операциях.
Корректная процедура выполнения практического задания: введение в разработку сценариев
-
0. Используйте различные имена для файлов ваших сценариев и сохраните их на будущее!
-
1. Создайте сценарий, который выводит имя города.
-
$ echo 'echo Antwerp' > first.bash $ chmod +x first.bash $ ./first.bash Antwerp
-
2. Сделайте так, чтобы сценарий исполнялся в командной оболочке bash.
-
$ cat first.bash #!/bin/bash echo Antwerp
-
3. Сделайте так, чтобы сценарий исполнялся в командной оболочке Korn shell.
-
$ cat first.bash #!/bin/ksh echo Antwerp
Обратите внимание на то, что хотя данный сценарий и будет технически исполняться как сценарий командной оболочки Korn shell, расширение файла .bash может смутить пользователя.
-
4. Создайте сценарий, в котором осуществляется объявление двух переменных с последующим выводом их значений.
-
$ cat second.bash #!/bin/bash var33=300 var42=400 echo $var33 $var42
-
5. Предыдущий сценарий не должен оказывать воздействия на вашу рабочую командную оболочку (объявленные в нем переменные не будут существовать вне сценария). А теперь запустите сценарий таким образом, чтобы он оказал влияние на вашу рабочую командную оболочку.
-
6. Существует ли более короткая форма команды для
использования рабочей командной оболочки с целью исполнении сценария
? -
7. Добавьте комментарии в ваши сценарии для того, чтобы знать о выполняемых ими операциях.
-
$ cat second.bash #!/bin/bash # сценарий для проверки работы механизма использования рабочей командной оболочки # объявление двух переменных var33=300 var42=400 # вывод значений этих переменных echo $var33 $var42
Если вам понравилась статья, поделитесь ею с друзьями:
Bash-скрипты: начало
Bash-скрипты, часть 2: циклы
Bash-скрипты, часть 3: параметры и ключи командной строки
Bash-скрипты, часть 4: ввод и вывод
Bash-скрипты, часть 5: сигналы, фоновые задачи, управление сценариями
Bash-скрипты, часть 6: функции и разработка библиотек
Bash-скрипты, часть 7: sed и обработка текстов
Bash-скрипты, часть 8: язык обработки данных awk
Bash-скрипты, часть 9: регулярные выражения
Bash-скрипты, часть 10: практические примеры
Bash-скрипты, часть 11: expect и автоматизация интерактивных утилит
Bash-скрипты: начало
Как устроены bash-скрипты
Создайте пустой файл с использованием команды touch
. В его первой строке нужно указать, какую именно оболочку мы собираемся использовать. Нас интересует bash
, поэтому первая строка файла будет такой:
В других строках этого файла символ решётки используется для обозначения комментариев, которые оболочка не обрабатывает. Однако, первая строка — это особый случай, здесь решётка, за которой следует восклицательный знак (эту последовательность называют шебанг) и путь кbash
, указывают системе на то, что сценарий создан именно для bash
.
Команды оболочки отделяются знаком перевода строки, комментарии выделяют знаком решётки. Вот как это выглядит:
#!/bin/bash # This is a comment pwd whoami
Тут, так же, как и в командной строке, можно записывать команды в одной строке, разделяя точкой с запятой. Однако, если писать команды на разных строках, файл легче читать. В любом случае оболочка их обработает.
Установка разрешений для файла сценария
Сохраните файл, дав ему имяmyscript
, и работа по созданию bash-скрипта почти закончена. Сейчас осталось лишь сделать этот файл исполняемым, иначе, попытавшись его запустить, вы столкнётесь с ошибкойPermission denied
.
Попытка запуска файла сценария с неправильно настроенными разрешениями
Сделаем файл исполняемым:
Теперь попытаемся его выполнить:
После настройки разрешений всё работает как надо.
Успешный запуск bash-скрипта
Вывод сообщений
Для вывода текста в консоль Linux применяется командаecho
. Воспользуемся знанием этого факта и отредактируем наш скрипт, добавив пояснения к данным, которые выводят уже имеющиеся в нём команды:
#!/bin/bash # our comment is here echo "The current directory is:" pwd echo "The user logged in is:" whoami
Вот что получится после запуска обновлённого скрипта.
Вывод сообщений из скрипта
Теперь мы можем выводить поясняющие надписи, используя командуecho
. Если вы не знаете, как отредактировать файл, пользуясь средствами Linux, или раньше не встречались с командойecho
, взгляните наэтотматериал.
Использование переменных
Переменные позволяют хранить в файле сценария информацию, например — результаты работы команд для использования их другими командами.
Нет ничего плохого в исполнении отдельных команд без хранения результатов их работы, но возможности такого подхода весьма ограничены.
Существуют два типа переменных, которые можно использовать в bash-скриптах:
- Переменные среды
- Пользовательские переменные
Переменные среды
Иногда в командах оболочки нужно работать с некими системными данными. Вот, например, как вывести домашнюю директорию текущего пользователя:
#!/bin/bash # display user home echo "Home for the current user is: $HOME"
Обратите внимание на то, что мы можем использовать системную переменную$HOME
в двойных кавычках, это не помешает системе её распознать. Вот что получится, если выполнить вышеприведённый сценарий.
Использование переменной среды в сценарии
А что если надо вывести на экран значок доллара? Попробуем так:
echo "I have $1 in my pocket"
Система обнаружит знак доллара в строке, ограниченной кавычками, и решит, что мы сослались на переменную. Скрипт попытается вывести на экран значение неопределённой переменной$1
. Это не то, что нам нужно. Что делать?
В подобной ситуации поможет использование управляющего символа, обратной косой черты, перед знаком доллара:
echo "I have $1 in my pocket"
Теперь сценарий выведет именно то, что ожидается.
Использование управляющей последовательности для вывода знака доллара
Пользовательские переменные
В дополнение к переменным среды, bash-скрипты позволяют задавать и использовать в сценарии собственные переменные. Подобные переменные хранят значение до тех пор, пока не завершится выполнение сценария.
Как и в случае с системными переменными, к пользовательским переменным можно обращаться, используя знак доллара:
TNW-CUS-FMP — промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней
#!/bin/bash # testing variables grade=5 person="Adam" echo "$person is a good boy, he is in grade $grade"
Вот что получится после запуска такого сценария.
Пользовательские переменные в сценарии
Подстановка команд
Одна из самых полезных возможностей bash-скриптов — это возможность извлекать информацию из вывода команд и назначать её переменным, что позволяет использовать эту информацию где угодно в файле сценария.
Сделать это можно двумя способами.
- С помощью значка обратного апострофа «`»
- С помощью конструкции
$()
Используя первый подход, проследите за тем, чтобы вместо обратного апострофа не ввести одиночную кавычку. Команду нужно заключить в два таких значка:
При втором подходе то же самое записывают так:
А скрипт, в итоге, может выглядеть так:
#!/bin/bash mydir=$(pwd) echo $mydir
В ходе его работы вывод командыpwd
будет сохранён в переменнойmydir
, содержимое которой, с помощью командыecho
, попадёт в консоль.
Скрипт, сохраняющий результаты работы команды в переменной
Математические операции
Для выполнения математических операций в файле скрипта можно использовать конструкцию вида$((a+b))
:
#!/bin/bash var1=$(( 5 + 5 )) echo $var1 var2=$(( $var1 * 2 )) echo $var2
Математические операции в сценарии
Управляющая конструкция if-then
В некоторых сценариях требуется управлять потоком исполнения команд. Например, если некое значение больше пяти, нужно выполнить одно действие, в противном случае — другое. Подобное применимо в очень многих ситуациях, и здесь нам поможет управляющая конструкцияif-then
. В наиболее простом виде она выглядит так:
if команда then команды fi
А вот рабочий пример:
#!/bin/bash if pwd then echo "It works" fi
В данном случае, если выполнение командыpwd
завершится успешно, в консоль будет выведен текст «it works».
Воспользуемся имеющимися у нас знаниями и напишем более сложный сценарий. Скажем, надо найти некоего пользователя в/etc/passwd
, и если найти его удалось, сообщить о том, что он существует.
#!/bin/bash user=likegeeks if grep $user /etc/passwd then echo "The user $user Exists" fi
Вот что получается после запуска этого скрипта.
Поиск пользователя
Здесь мы воспользовались командойgrep
для поиска пользователя в файле/etc/passwd
. Если командаgrep
вам незнакома, её описание можно найтиздесь.
В этом примере, если пользователь найден, скрипт выведет соответствующее сообщение. А если найти пользователя не удалось? В данном случае скрипт просто завершит выполнение, ничего нам не сообщив. Хотелось бы, чтобы он сказал нам и об этом, поэтому усовершенствуем код.
Управляющая конструкция if-then-else
Для того, чтобы программа смогла сообщить и о результатах успешного поиска, и о неудаче, воспользуемся конструкциейif-then-else
. Вот как она устроена:
if команда then команды else команды fi
Если первая команда возвратит ноль, что означает её успешное выполнение, условие окажется истинным и выполнение не пойдёт по веткеelse
. В противном случае, если будет возвращено что-то, отличающееся от нуля, что будет означать неудачу, или ложный результат, будут выполнены команды, расположенные послеelse
.
Напишем такой скрипт:
#!/bin/bash user=anotherUser if grep $user /etc/passwd then echo "The user $user Exists" else echo "The user $user doesn’t exist" fi
Его исполнение пошло по веткеelse
.
Запуск скрипта с конструкцией if-then-else
Ну что же, продолжаем двигаться дальше и зададимся вопросом о более сложных условиях. Что если надо проверить не одно условие, а несколько? Например, если нужный пользователь найден, надо вывести одно сообщение, если выполняется ещё какое-то условие — ещё одно сообщение, и так далее. В подобной ситуации нам помогут вложенные условия. Выглядит это так:
if команда1 then команды elif команда2 then команды fi
Если первая команда вернёт ноль, что говорит о её успешном выполнении, выполнятся команды в первом блокеthen
, иначе, если первое условие окажется ложным, и если вторая команда вернёт ноль, выполнится второй блок кода.
#!/bin/bash user=anotherUser if grep $user /etc/passwd then echo "The user $user Exists" elif ls /home then echo "The user doesn’t exist but anyway there is a directory under /home" fi
В подобном скрипте можно, например, создавать нового пользователя с помощью командыuseradd
, если поиск не дал результатов, или делать ещё что-нибудь полезное.
Сравнение чисел
В скриптах можно сравнивать числовые значения. Ниже приведён список соответствующих команд.
n1 -eq n2
Возвращает истинное значение, еслиn1
равноn2
.
n1 -ge n2
Возвращает истинное значение, еслиn1
больше или равноn2
.
n1 -gt n2
Возвращает истинное значение, еслиn1
большеn2
.
n1 -le n2
Возвращает истинное значение, еслиn1
меньше или равноn2
.
n1 -lt n2
Возвращает истинное значение, если n1 меньшеn2
.
n1 -ne n2
Возвращает истинное значение, еслиn1
не равноn2
.
В качестве примера опробуем один из операторов сравнения. Обратите внимание на то, что выражение заключено в квадратные скобки.
#!/bin/bash val1=6 if [ $val1 -gt 5 ] then echo "The test value $val1 is greater than 5" else echo "The test value $val1 is not greater than 5" fi
Вот что выведет эта команда.
Сравнение чисел в скриптах
Значение переменнойval1
больше чем 5, в итоге выполняется ветвьthen
оператора сравнения и в консоль выводится соответствующее сообщение.
Сравнение строк
В сценариях можно сравнивать и строковые значения. Операторы сравнения выглядят довольно просто, однако у операций сравнения строк есть определённые особенности, которых мы коснёмся ниже. Вот список операторов.
str1 = str2
Проверяет строки на равенство, возвращает истину, если строки идентичны.
str1 != str2
Возвращает истину, если строки не идентичны.
str1 < str2
Возвращает истину, еслиstr1
меньше, чемstr2
.
str1 > str2
Возвращает истину, еслиstr1
больше, чемstr2
.
-n str1
Возвращает истину, если длинаstr1
больше нуля.
-z str1
Возвращает истину, если длинаstr1
равна нулю.
Вот пример сравнения строк в сценарии:
#!/bin/bash user ="likegeeks" if [$user = $USER] then echo "The user $user is the current logged in user" fi
В результате выполнения скрипта получим следующее.
Сравнение строк в скриптах
Вот одна особенность сравнения строк, о которой стоит упомянуть. А именно, операторы «>» и «<» необходимо экранировать с помощью обратной косой черты, иначе скрипт будет работать неправильно, хотя сообщений об ошибках и не появится. Скрипт интерпретирует знак «>» как команду перенаправления вывода.
Вот как работа с этими операторами выглядит в коде:
#!/bin/bash val1=text val2="another text" if [ $val1 > $val2 ] then echo "$val1 is greater than $val2" else echo "$val1 is less than $val2" fi
Вот результаты работы скрипта.
Сравнение строк, выведенное предупреждение
Обратите внимание на то, что скрипт, хотя и выполняется, выдаёт предупреждение:
./myscript: line 5: [: too many arguments
Для того, чтобы избавиться от этого предупреждения, заключим$val2
в двойные кавычки:
#!/bin/bash val1=text val2="another text" if [ $val1 > "$val2" ] then echo "$val1 is greater than $val2" else echo "$val1 is less than $val2" fi
Теперь всё работает как надо.
Сравнение строк
Ещё одна особенность операторов «>» и «<» заключается в том, как они работают с символами в верхнем и нижнем регистрах. Для того, чтобы понять эту особенность, подготовим текстовый файл с таким содержимым:
Сохраним его, дав имяmyfile
, после чего выполним в терминале такую команду:
Она отсортирует строки из файла так:
Командаsort
, по умолчанию, сортирует строки по возрастанию, то есть строчная буква в нашем примере меньше прописной. Теперь подготовим скрипт, который будет сравнивать те же строки:
#!/bin/bash val1=Likegeeks val2=likegeeks if [ $val1 > $val2 ] then echo "$val1 is greater than $val2" else echo "$val1 is less than $val2" fi
Если его запустить, окажется, что всё наоборот — строчная буква теперь больше прописной.
Команда sort и сравнение строк в файле сценария
В командах сравнения прописные буквы меньше строчных. Сравнение строк здесь выполняется путём сравнения ASCII-кодов символов, порядок сортировки, таким образом, зависит от кодов символов.
Командаsort
, в свою очередь, использует порядок сортировки, заданный в настройках системного языка.
Проверки файлов
Пожалуй, нижеприведённые команды используются в bash-скриптах чаще всего. Они позволяют проверять различные условия, касающиеся файлов. Вот список этих команд.
-d file
Проверяет, существует ли файл, и является ли он директорией.
-e file
Проверяет, существует ли файл.
-f file
Проверяет, существует ли файл, и является ли он файлом.
-r file
Проверяет, существует ли файл, и доступен ли он для чтения.
-s file П
роверяет, существует ли файл, и не является ли он пустым.
-w file
Проверяет, существует ли файл, и доступен ли он для записи.
-x file
Проверяет, существует ли файл, и является ли он исполняемым.
file1 -nt file2
Проверяет, новее лиfile1
, чемfile2
.
file1 -ot file2
Проверяет, старше лиfile1
, чемfile2
.
-O file
Проверяет, существует ли файл, и является ли его владельцем текущий пользователь.
-G file
Проверяет, существует ли файл, и соответствует ли его идентификатор группы идентификатору группы текущего пользователя.
Эти команды, как впрочем, и многие другие рассмотренные сегодня, несложно запомнить. Их имена, являясь сокращениями от различных слов, прямо указывают на выполняемые ими проверки.
Опробуем одну из команд на практике:
#!/bin/bash mydir=/home/likegeeks if [ -d $mydir ] then echo "The $mydir directory exists" cd $ mydir ls else echo "The $mydir directory does not exist" fi
Этот скрипт, для существующей директории, выведет её содержимое.
Вывод содержимого директории
Полагаем, с остальными командами вы сможете поэкспериментировать самостоятельно, все они применяются по тому же принципу.
Bash-скрипты, часть 2: циклы
Циклы for
Оболочка bash поддерживает циклыfor
, которые позволяют организовывать перебор последовательностей значений. Вот какова базовая структура таких циклов:
for var in list do команды done
В каждой итерации цикла в переменнуюvar
будет записываться следующее значение из спискаlist
. В первом проходе цикла, таким образом, будет задействовано первое значение из списка. Во втором — второе, и так далее — до тех пор, пока цикл не дойдёт до последнего элемента.
Перебор простых значений
Пожалуй, самый простой пример циклаfor
в bash-скриптах — это перебор списка простых значений:
#!/bin/bash for var in first second third fourth fifth do echo The $var item done
Ниже показаны результаты работы этого скрипта. Хорошо видно, что в переменную$var
последовательно попадают элементы из списка. Происходит так до тех пор, пока цикл не дойдёт до последнего из них.
Простой цикл for
Обратите внимание на то, что переменная$var
сохраняет значение при выходе из цикла, её содержимое можно менять, в целом, работать с ней можно как с любой другой переменной.
Перебор сложных значений
В списке, использованном при инициализации циклаfor
, могут содержаться не только простые строки, состоящие из одного слова, но и целые фразы, в которые входят несколько слов и знаков препинания. Например, всё это может выглядеть так:
#!/bin/bash for var in first "the second" "the third" "I’ll do it" do echo "This is: $var" done
Вот что получится после того, как этот цикл пройдётся по списку. Как видите, результат вполне ожидаем.
Перебор сложных значений
TNW-CUS-FMP — промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней»
Инициализация цикла списком, полученным из результатов работы команды
Ещё один способ инициализации циклаfor
заключается в передаче ему списка, который является результатом работы некоей команды. Тут используется подстановка команд для их исполнения и получения результатов их работы.
#!/bin/bash file="myfile" for var in $(cat $file) do echo " $var" done
В этом примере задействована командаcat
, которая читает содержимое файла. Полученный список значений передаётся в цикл и выводится на экран. Обратите внимание на то, что в файле, к которому мы обращаемся, содержится список слов, разделённых знаками перевода строки, пробелы при этом не используются.
Цикл, который перебирает содержимое файла
Тут надо учесть, что подобный подход, если ожидается построчная обработка данных, не сработает для файла более сложной структуры, в строках которого может содержаться по несколько слов, разделённых пробелами. Цикл будет обрабатывать отдельные слова, а не строки.
Что, если это совсем не то, что нужно?
Разделители полей
Причина вышеописанной особенности заключается в специальной переменной окружения, которая называетсяIFS
(Internal Field Separator) и позволяет указывать разделители полей. По умолчанию оболочка bash считает разделителями полей следующие символы:
- Пробел
- Знак табуляции
- Знак перевода строки
Если bash встречает в данных любой из этих символов, он считает, что перед ним — следующее самостоятельное значение списка.
Для того, чтобы решить проблему, можно временно изменить переменную средыIFS
. Вот как это сделать в bash-скрипте, если исходить из предположения, что в качестве разделителя полей нужен только перевод строки:
После добавления этой команды в bash-скрипт, он будет работать как надо, игнорируя пробелы и знаки табуляции, считая разделителями полей лишь символы перевода строки.
#!/bin/bash file="/etc/passwd" IFS=$'n' for var in $(cat $file) do echo " $var" done
Если этот скрипт запустить, он выведет он именно то, что от него требуется, давая, в каждой итерации цикла, доступ к очередной строке, записанной в файл.
Построчный обход содержимого файла в цикле for
Разделителями могут быть и другие символы. Например, выше мы выводили на экран содержимое файла/etc/passwd
. Данные о пользователях в строках разделены с помощью двоеточий. Если в цикле нужно обрабатывать подобные строки,IFS
можно настроить так:
Обход файлов, содержащихся в директории
Один из самых распространённых вариантов использования цикловfor
в bash-скриптах заключается в обходе файлов, находящихся в некоей директории, и в обработке этих файлов.
Например, вот как можно вывести список файлов и папок:
#!/bin/bash for file in /home/likegeeks/* do if [ -d "$file" ] then echo "$file is a directory" elif [ -f "$file" ] then echo "$file is a file" fi done
Если вы разобрались спредыдущим материаломиз этой серии статей, вам должно быть понятно устройство конструкцииif-then
, а так же то, как отличить файл от папки. Если вам сложно понять вышеприведённый код, перечитайте этот материал.
Вот что выведет скрипт.
Вывод содержимого папки
Обратите внимание на то, как мы инициализируем цикл, а именно — на подстановочный знак «*» в конце адреса папки. Этот символ можно воспринимать как шаблон, означающий: «все файлы с любыми именами». он позволяет организовать автоматическую подстановку имён файлов, которые соответствуют шаблону.
При проверке условия в оператореif
, мы заключаем имя переменной в кавычки. Сделано это потому что имя файла или папки может содержать пробелы.
Циклы for в стиле C
Если вы знакомы с языком программирования C, синтаксис описания bash-цикловfor
может показаться вам странным, так как привыкли вы, очевидно, к такому описанию циклов:
for (i = 0; i < 10; i++) { printf("number is %dn", i); }
В bash-скриптах можно использовать циклыfor
, описание которых выглядит очень похожим на циклы в стиле C, правда, без некоторых отличий тут не обошлось. Схема цикла при подобном подходе выглядит так:
for (( начальное значение переменной ; условие окончания цикла; изменение переменной ))
На bash это можно написать так:
for (( a = 1; a < 10; a++ ))
А вот рабочий пример:
#!/bin/bash for (( i=1; i <= 10; i++ )) do echo "number is $i" done
Этот код выведет список чисел от 1 до 10.
Работа цикла в стиле C
Цикл while
Конструкцияfor —
не единственный способ организации циклов в bash-скриптах. Здесь можно пользоваться и цикламиwhile
. В таком цикле можно задать команду проверки некоего условия и выполнять тело цикла до тех пор, пока проверяемое условие возвращает ноль, или сигнал успешного завершения некоей операции. Когда условие цикла вернёт ненулевое значение, что означает ошибку, цикл остановится.
Вот схема организации цикловwhile while команда проверки условия do другие команды done
Взглянем на пример скрипта с таким циклом:
#!/bin/bash var1=5 while [ $var1 -gt 0 ] do echo $var1 var1=$[ $var1 - 1 ] done
На входе в цикл проверяется, больше ли нуля переменная$var1
. Если это так, выполняется тело цикла, в котором из значения переменной вычитается единица. Так происходит в каждой итерации, при этом мы выводим в консоль значение переменной до его модификации. Как только$var1
примет значение 0, цикл прекращается.
Результат работы цикла while
Если не модифицировать переменную$var1
, это приведёт к попаданию скрипта в бесконечный цикл.
Вложенные циклы
В теле цикла можно использовать любые команды, в том числе — запускать другие циклы. Такие конструкции называют вложенными циклами:
#!/bin/bash for (( a = 1; a <= 3; a++ )) do echo "Start $a:" for (( b = 1; b <= 3; b++ )) do echo " Inner loop: $b" done done
Ниже показано то, что выведет этот скрипт. Как видно, сначала выполняется первая итерация внешнего цикла, потом — три итерации внутреннего, после его завершения снова в дело вступает внешний цикл, потом опять — внутренний.
Вложенные циклы
Обработка содержимого файла
Чаще всего вложенные циклы используют для обработки файлов. Так, внешний цикл занимается перебором строк файла, а внутренний уже работает с каждой строкой. Вот, например, как выглядит обработка файла/etc/passwd
:
#!/bin/bash IFS=$'n' for entry in $(cat /etc/passwd) do echo "Values in $entry –" IFS=: for value in $entry do echo " $value" done done
В этом скрипте два цикла. Первый проходится по строкам, используя в качестве разделителя знак перевода строки. Внутренний занят разбором строк, поля которых разделены двоеточиями.
Обработка данных файла
Такой подход можно использовать при обработке файлов формата CSV, или любых подобных файлов, записывая, по мере надобности, в переменную окруженияIFS
символ-разделитель.
Управление циклами
Возможно, после входа в цикл, нужно будет остановить его при достижении переменной цикла определённого значения, которое не соответствует изначально заданному условию окончания цикла. Надо ли будет в такой ситуации дожидаться нормального завершения цикла? Нет конечно, и в подобных случаях пригодятся следующие две команды:
break
continue
Команда break
Эта команда позволяет прервать выполнение цикла. Её можно использовать и для цикловfor
, и для цикловwhile
:
#!/bin/bash for var1 in 1 2 3 4 5 6 7 8 9 10 do if [ $var1 -eq 5 ] then break fi echo "Number: $var1" done
Такой цикл, в обычных условиях, пройдётся по всему списку значений из списка. Однако, в нашем случае, его выполнение будет прервано, когда переменная$var1
будет равна 5.
Досрочный выход из цикла for
Вот — то же самое, но уже для циклаwhile
:
#!/bin/bash var1=1 while [ $var1 -lt 10 ] do if [ $var1 -eq 5 ] then break fi echo "Iteration: $var1" var1=$(( $var1 + 1 )) done
Командаbreak
, исполненная, когда значение$var1
станет равно 5, прерывает цикл. В консоль выведется то же самое, что и в предыдущем примере.
Команда continue
Когда в теле цикла встречается эта команда, текущая итерация завершается досрочно и начинается следующая, при этом выхода из цикла не происходит. Посмотрим на командуcontinue
в циклеfor
:
#!/bin/bash for (( var1 = 1; var1 < 15; var1++ )) do if [ $var1 -gt 5 ] && [ $var1 -lt 10 ] then continue fi echo "Iteration number: $var1" done
Когда условие внутри цикла выполняется, то есть, когда$var1
больше 5 и меньше 10, оболочка исполняет командуcontinue
. Это приводит к пропуску оставшихся в теле цикла команд и переходу к следующей итерации.
Команда continue в цикле for
Обработка вывода, выполняемого в цикле
Данные, выводимые в цикле, можно обработать, либо перенаправив вывод, либо передав их в конвейер. Делается это с помощью добавления команд обработки вывода после инструкцииdone
.
Например, вместо того, чтобы показывать на экране то, что выводится в цикле, можно записать всё это в файл или передать ещё куда-нибудь:
#!/bin/bash for (( a = 1; a < 10; a++ )) do echo "Number is $a" done > myfile.txt echo "finished."
Оболочка создаст файлmyfile.txt
и перенаправит в этот файл вывод конструкцииfor
. Откроем файл и удостоверимся в том, что он содержит именно то, что ожидается.
Перенаправление вывода цикла в файл
Пример: поиск исполняемых файлов
Давайте воспользуемся тем, что мы уже разобрали, и напишем что-нибудь полезное. Например, если надо выяснить, какие именно исполняемые файлы доступны в системе, можно просканировать все папки, записанные в переменную окруженияPATH
. Весь арсенал средств, который для этого нужен, у нас уже есть, надо лишь собрать всё это воедино:
#!/bin/bash IFS=: for folder in $PATH do echo "$folder:" for file in $folder/* do if [ -x $file ] then echo " $file" fi done done
Такой вот скрипт, небольшой и несложный, позволил получить список исполняемых файлов, хранящихся в папках изPATH
.
Поиск исполняемых файлов в папках из переменной PATH
Bash-скрипты, часть 3: параметры и ключи командной строки
Чтение параметров командной строки
Оболочка bash назначает специальным переменным, называемым позиционными параметрами, введённые при вызове скрипта параметры командной строки:
-
$0
— имя скрипта. -
$1
— первый параметр. -
$2
— второй параметр — и так далее, вплоть до переменной$9
, в которую попадает девятый параметр.
Вот как можно использовать параметры командной строки в скрипте с помощью этих переменных:
#!/bin/bash echo $0 echo $1 echo $2 echo $3
Запустим сценарий с параметрами:
Вот что он выведет в консоль.
Вывод параметров, с которыми запущен скрипт
Обратите внимание на то, что параметры командной строки разделяются пробелами.
Взглянем на ещё один пример использования параметров. Тут мы найдём сумму чисел, переданных сценарию:
#!/bin/bash total=$[ $1 + $2 ] echo The first parameter is $1. echo The second parameter is $2. echo The sum is $total.
Запустим скрипт и проверим результат вычислений.
Сценарий, который находит сумму переданных ему чисел
Параметры командной строки не обязательно должны быть числами. Сценариям можно передавать и строки. Например, вот скрипт, работающий со строкой:
#!/bin/bash echo Hello $1, how do you do
Запустим его:
Он выведет то, что мы от него ожидаем.
Сценарий, работающий со строковым параметром
Что если параметр содержит пробелы, а нам надо обрабатывать его как самостоятельный фрагмент данных? Полагаем, если вы освоили предыдущие части этого руководства, ответ вы уже знаете. Заключается он в использовании кавычек.
Если скрипту надо больше девяти параметров, при обращении к ним номер в имени переменной надо заключать в фигурные скобки, например так:
Проверка параметров
Если скрипт вызван без параметров, но для нормальной работы кода предполагается их наличие, возникнет ошибка. Поэтому рекомендуется всегда проверять наличие параметров, переданных сценарию при вызове. Например, это можно организовать так:
#!/bin/bash if [ -n "$1" ] then echo Hello $1. else echo "No parameters found. " fi
Вызовем скрипт сначала с параметром, а потом без параметров.
Вызов скрипта, проверяющего наличие параметров командной строки
Подсчёт параметров
В скрипте можно подсчитать количество переданных ему параметров. Оболочка bash предоставляет для этого специальную переменную. А именно, переменная$#
содержит количество параметров, переданных сценарию при вызове.
Опробуем её:
#!/bin/bash echo There were $# parameters passed.
Вызовем сценарий.
В результате скрипт сообщит о том, что ему передано 5 параметров.
Подсчёт количества параметров в скрипте
Эта переменная даёт необычный способ получения последнего из переданных скрипту параметров, не требующий знания их количества. Вот как это выглядит:
#!/bin/bash echo The last parameter was ${!#}
Вызовем скрипт и посмотрим, что он выведет.
Обращение к последнему параметру
Захват всех параметров командной строки
В некоторых случаях нужно захватить все параметры, переданные скрипту. Для этого можно воспользоваться переменными$*
и$@
. Обе они содержат все параметры командной строки, что делает возможным доступ к тому, что передано сценарию, без использования позиционных параметров.
Переменная$*
содержит все параметры, введённые в командной строке, в виде единого «слова».
В переменной$@
параметры разбиты на отдельные «слова». Эти параметры можно перебирать в циклах.
Рассмотрим разницу между этими переменными на примерах. Сначала взглянем на их содержимое:
#!/bin/bash echo "Using the $* method: $*" echo "-----------" echo "Using the $@ method: $@"
Вот вывод скрипта.
Переменные $* и $@
Как видно, при выводе обеих переменных получается одно и то же. Теперь попробуем пройтись по содержимому этих переменных в циклах для того, чтобы увидеть разницу между ними:
#!/bin/bash count=1 for param in "$*" do echo "$* Parameter #$count = $param" count=$(( $count + 1 )) done count=1 for param in "$@" do echo "$@ Parameter #$count = $param" count=$(( $count + 1 )) done
Взгляните на то, что скрипт вывел в консоль. Разница между переменными вполне очевидна.
Разбор переменных $* и $@ в цикле
Переменная$*
содержит все переданные скрипту параметры как единый фрагмент данных, в то время как в переменной$@
они представлены самостоятельными значениями. Какой именно переменной воспользоваться — зависит от того, что именно нужно в конкретном сценарии.
Команда shift
Использовать командуshift
в bash-скриптах следует с осторожностью, так как она, в прямом смысле слова, сдвигает значения позиционных параметров.
Когда вы используете эту команду, она, по умолчанию, сдвигает значения позиционных параметров влево. Например, значение переменной$3
становится значением переменной$2
, значение$2
переходит в$1
, а то, что было до этого в$1,
теряется. Обратите внимание на то, что при этом значение переменной$0
, содержащей имя скрипта, не меняется.
Воспользовавшись командойshift
, рассмотрим ещё один способ перебора переданных скрипту параметров:
#!/bin/bash count=1 while [ -n "$1" ] do echo "Parameter #$count = $1" count=$(( $count + 1 )) shift done
Скрипт задействует циклwhile
, проверяя длину значения первого параметра. Когда длина станет равна нулю, происходит выход из цикла. После проверки первого параметра и вывода его на экран, вызывается командаshift
, которая сдвигает значения параметров на одну позицию.
Использование команды shift для перебора параметров
Используя командуshift
, помните о том, что при каждом её вызове значение переменной$1
безвозвратно теряется.
Ключи командной строки
Ключи командной строки обычно выглядят как буквы, перед которыми ставится тире. Они служат для управления сценариями. Рассмотрим такой пример:
#!/bin/bash echo while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) echo "Found the -b option" ;; -c) echo "Found the -c option" ;; *) echo "$1 is not an option" ;; esac shift done
Запустим скрипт:
И проанализируем то, что он выведет в терминал.
Обработка ключей в скрипте
В этом коде использована конструкцияcase
, которая сверяет переданный ей ключ со списком обрабатываемых скриптом ключей. Если переданное значение нашлось в этом списке, выполняется соответствующая ветвь кода. Если при вызове скрипта будет использован любой ключ, обработка которого не предусмотрена, будет исполнена ветвь «*».
Как различать ключи и параметры
Часто при написании bash-скриптов возникает ситуация, когда надо использовать и параметры командной строки, и ключи. Стандартный способ это сделать заключается в применении специальной последовательности символов, которая сообщает скрипту о том, когда заканчиваются ключи и начинаются обычные параметры.
Эта последовательность — двойное тире (—). Оболочка использует её для указания позиции, на которой заканчивается список ключей. После того, как скрипт обнаружит признак окончания ключей, то, что осталось, можно, не опасаясь ошибок, обрабатывать как параметры, а не как ключи. Рассмотрим пример:
#!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) echo "Found the -b option";; -c) echo "Found the -c option" ;; --) shift break ;; *) echo "$1 is not an option";; esac shift done count=1 for param in $@ do echo "Parameter #$count: $param" count=$(( $count + 1 )) done
Этот сценарий использует командуbreak
для прерывания циклаwhile
при обнаружении в строке двойного тире.
Вот что получится после его вызова.
Обработка ключей и параметров командной строки
Как видно, когда скрипт, разбирая переданные ему данные, находит двойное тире, он завершает обработку ключей и считает всё, что ещё не обработано, параметрами.
Обработка ключей со значениями
По мере усложнения ваших скриптов, вы столкнётесь с ситуациями, когда обычных ключей уже недостаточно, а значит, нужно будет использовать ключи с некими значениями. Например, вызов сценария в котором используется подобная возможность, выглядит так:
./myscript -a test1 -b -c test2
Скрипт должен уметь определять, когда вместе с ключами командной строки используются дополнительные параметры:
#!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option";; -b) param="$2" echo "Found the -b option, with parameter value $param" shift ;; -c) echo "Found the -c option";; --) shift break ;; *) echo "$1 is not an option";; esac shift done count=1 for param in "$@" do echo "Parameter #$count: $param" count=$(( $count + 1 )) done
Вызовем этот скрипт в таком виде:
./myscript -a -b test1 -d
Посмотрим на результаты его работы.
Обработка параметров ключей
В данном примере в конструкцииcase
обрабатываются три ключа. Ключ-b
требует наличия дополнительного параметра. Так как обрабатываемый ключ находится в переменной$1
, соответствующий ему параметр будет находиться в$2
(тут используется командаshift
, поэтому, по мере обработки, всё, что передано сценарию, сдвигается влево). Когда с этим мы разобрались, осталось лишь извлечь значение переменной$2
и у нас будет параметр нужного ключа. Конечно, тут понадобится ещё одна командаshift
для того, чтобы следующий ключ попал в$1
.
Использование стандартных ключей
При написании bash-скриптов вы можете выбирать любые буквы для ключей командной строки и произвольно задавать реакцию скрипта на эти ключи. Однако, в мире Linux значения некоторых ключей стали чем-то вроде стандарта, которого полезно придерживаться. Вот список этих ключей:
-a
Вывести все объекты.
-c
Произвести подсчёт.
-d
Указать директорию.
-e
Развернуть объект.
-f
Указать файл, из которого нужно прочитать данные.
-h
Вывести справку по команде.
-i
Игнорировать регистр символов.
-l
Выполнить полноформатный вывод данных.
-n
Использовать неинтерактивный (пакетный) режим.
-o
Позволяет указать файл, в который нужно перенаправить вывод.
-q
Выполнить скрипт в quiet-режиме.
-r
Обрабатывать папки и файлы рекурсивно.
-s
Выполнить скрипт в silent-режиме.
-v
Выполнить многословный вывод.
-x
Исключить объект.
-y
Ответить «yes» на все вопросы.
Если вы работаете в Linux, вам, скорее всего, знакомы многие из этих ключей. Использовав их в общепринятом значении в своих скриптах, вы поможете пользователям взаимодействовать с ними, не беспокоясь о чтении документации.
Получение данных от пользователя
Ключи и параметры командной строки — это отличный способ получить данные от того, кто пользуется скриптом, однако в некоторых случаях нужно больше интерактивности.
Иногда сценарии нуждаются в данных, которые пользователь должен ввести во время выполнения программы. Именно для этой цели в оболочке bash имеется командаread
.
Эта команда позволяет принимать введённые данные либо со стандартного ввода (с клавиатуры), либо используя другие дескрипторы файлов. После получения данных, эта команда помещает их в переменную:
#!/bin/bash echo -n "Enter your name: " read name echo "Hello $name, welcome to my program."
Обратите внимание на то, что команда echo
, которая выводит приглашение, вызывается с ключом -n
. Это приводит к тому, что в конце приглашения не выводится знак перевода строки, что позволяет пользователю скрипта вводить данные там же, где расположено приглашение, а не на следующей строке.
Обработка пользовательского ввода
При вызовеread
можно указывать и несколько переменных:
#!/bin/bash read -p "Enter your name: " first last echo "Your data for $last, $first…"
Вот что выведет скрипт после запуска.
Несколько переменных в команде read
Если, вызвав read
, не указывать переменную, данные, введённые пользователем, будут помещены в специальную переменную среды REPLY
:
#!/bin/bash read -p "Enter your name: " echo Hello $REPLY, welcome to my program.
Использование переменной среды REPLY
Если скрипт должен продолжать выполнение независимо от того, введёт пользователь какие-то данные или нет, вызывая команду read
можно воспользоваться ключом -t
. А именно, параметр ключа задаёт время ожидания ввода в секундах:
#!/bin/bash if read -t 5 -p "Enter your name: " name then echo "Hello $name, welcome to my script" else echo "Sorry, too slow! " fi
Если данные не будут введены в течение 5 секунд, скрипт выполнит ветвь условного оператора else
, выведя извинения.
Ограничение времени на ввод данных
Ввод паролей
Иногда то, что вводит пользователь в ответ на вопрос скрипта, лучше на экране не показывать. Например, так обычно делают, запрашивая пароли. Ключ -s
команды read
предотвращает отображение на экране данных, вводимых с клавиатуры. На самом деле, данные выводятся, но командаread
делает цвет текста таким же, как цвет фона.
#!/bin/bash read -s -p "Enter your password: " pass echo "Is your password really $pass? "
Вот как отработает этот скрипт.
Ввод конфиденциальных данных
Чтение данных из файла
Командаread
может, при каждом вызове, читать одну строку текста из файла. Когда в файле больше не останется непрочитанных строк, она просто остановится. Если нужно получить в скрипте всё содержимое файла, можно, с помощью конвейера, передать результаты вызова командыcat
для файла, конструкцииwhile
, которая содержит командуread
(конечно, использование командыcat
выглядит примитивно, но наша цель — показать всё максимально просто, ориентируясь на новичков; опытные пользователи, уверены, это поймут).
Напишем скрипт, в котором используется только что описанный подход к чтению файлов.
#!/bin/bash count=1 cat myfile | while read line do echo "Line $count: $line" count=$(( $count + 1 )) done echo "Finished"
Посмотрим на него в деле.
Чтение данных из файла
Тут мы передали в цикл while
содержимое файла и перебрали все строки этого файла, выводя номер и содержимое каждой из них.
Bash-скрипты, часть 4: ввод и вывод
Стандартные дескрипторы файлов
Всё в Linux — это файлы, в том числе — ввод и вывод. Операционная система идентифицирует файлы с использованием дескрипторов.
Каждому процессу позволено иметь до девяти открытых дескрипторов файлов. Оболочка bash резервирует первые три дескриптора с идентификаторами 0, 1 и 2. Вот что они означают.
0
,STDIN
— стандартный поток ввода.1
,STDOUT
— стандартный поток вывода.2
,STDERR
— стандартный поток ошибок.
Эти три специальных дескриптора обрабатывают ввод и вывод данных в сценарии.
Вам нужно как следует разобраться в стандартных потоках. Их можно сравнить с фундаментом, на котором строится взаимодействие скриптов с внешним миром. Рассмотрим подробности о них.
STDIN
STDIN
— это стандартный поток ввода оболочки. Для терминала стандартный ввод — это клавиатура. Когда в сценариях используют символ перенаправления ввода — <
, Linux заменяет дескриптор файла стандартного ввода на тот, который указан в команде. Система читает файл и обрабатывает данные так, будто они введены с клавиатуры.
Многие команды bash принимают ввод из STDIN
, если в командной строке не указан файл, из которого надо брать данные. Например, это справедливо для команды cat
.
Когда вы вводите команду cat
в командной строке, не задавая параметров, она принимает ввод из STDIN
. После того, как вы вводите очередную строку, cat
просто выводит её на экран.
STDOUT
STDOUT
— стандартный поток вывода оболочки. По умолчанию это — экран. Большинство bash-команд выводят данные в STDOUT
, что приводит к их появлению в консоли. Данные можно перенаправить в файл, присоединяя их к его содержимому, для этого служит команда >>
.
Итак, у нас есть некий файл с данными, к которому мы можем добавить другие данные с помощью этой команды:
То, что выведетpwd
, будет добавлено к файлуmyfile
, при этом уже имеющиеся в нём данные никуда не денутся.
Перенаправление вывода команды в файл
Пока всё хорошо, но что если попытаться выполнить что-то вроде показанного ниже, обратившись к несуществующему файлуxfile
, задумывая всё это для того, чтобы в файлmyfile
попало сообщение об ошибке.
После выполнения этой команды мы увидим сообщения об ошибках на экране.
Попытка обращения к несуществующему файлу
При попытке обращения к несуществующему файлу генерируется ошибка, но оболочка не перенаправила сообщения об ошибках в файл, выведя их на экран. Но мы-то хотели, чтобы сообщения об ошибках попали в файл. Что делать? Ответ прост — воспользоваться третьим стандартным дескриптором.
STDERR
STDERR
представляет собой стандартный поток ошибок оболочки. По умолчанию этот дескриптор указывает на то же самое, на что указываетSTDOUT
, именно поэтому при возникновении ошибки мы видим сообщение на экране.
Итак, предположим, что надо перенаправить сообщения об ошибках, скажем, в лог-файл, или куда-нибудь ещё, вместо того, чтобы выводить их на экран.
Перенаправление потока ошибок
Как вы уже знаете, дескриптор файлаSTDERR —
2. Мы можем перенаправить ошибки, разместив этот дескриптор перед командой перенаправления:
ls -l xfile 2>myfile
cat ./myfile
Сообщение об ошибке теперь попадёт в файлmyfile
.
Перенаправление сообщения об ошибке в файл
Перенаправление потоков ошибок и вывода
При написании сценариев командной строки может возникнуть ситуация, когда нужно организовать и перенаправление сообщений об ошибках, и перенаправление стандартного вывода. Для того, чтобы этого добиться, нужно использовать команды перенаправления для соответствующих дескрипторов с указанием файлов, куда должны попадать ошибки и стандартный вывод:
ls –l myfile xfile anotherfile 2> errorcontent 1> correctcontent
Перенаправление ошибок и стандартного вывода
Оболочка перенаправит то, что командаls
обычно отправляет вSTDOUT
, в файлcorrectcontent
благодаря конструкции1>
. Сообщения об ошибках, которые попали бы вSTDERR
, оказываются в файлеerrorcontent
из-за команды перенаправления2>
.
Если надо, иSTDERR
, иSTDOUT
можно перенаправить в один и тот же файл, воспользовавшись командой&>
:
Перенаправление STDERR и STDOUT в один и тот же файл
После выполнения команды то, что предназначено дляSTDERR
иSTDOUT
, оказывается в файлеcontent
.
Перенаправление вывода в скриптах
Существует два метода перенаправления вывода в сценариях командной строки:
- Временное перенаправление, или перенаправление вывода одной строки.
- Постоянное перенаправление, или перенаправление всего вывода в скрипте либо в какой-то его части.
Временное перенаправление вывода
В скрипте можно перенаправить вывод отдельной строки вSTDERR
. Для того, чтобы это сделать, достаточно использовать команду перенаправления, указав дескрипторSTDERR
, при этом перед номером дескриптора надо поставить символ амперсанда (&
):
#!/bin/bash echo "This is an error" >&2 echo "This is normal output"
Если запустить скрипт, обе строки попадут на экран, так как, как вы уже знаете, по умолчанию ошибки выводятся туда же, куда и обычные данные.
Временное перенаправление
Запустим скрипт так, чтобы выводSTDERR
попадал в файл.
Как видно, теперь обычный вывод делается в консоль, а сообщения об ошибках попадают в файл.
Сообщения об ошибках записываются в файл
Постоянное перенаправление вывода
Если в скрипте нужно перенаправлять много выводимых на экран данных, добавлять соответствующую команду к каждому вызовуecho
неудобно. Вместо этого можно задать перенаправление вывода в определённый дескриптор на время выполнения скрипта, воспользовавшись командойexec
:
#!/bin/bash exec 1>outfile echo "This is a test of redirecting all output" echo "from a shell script to another file." echo "without having to redirect every line"
Запустим скрипт.
Перенаправление всего вывода в файл
Если просмотреть файл, указанный в команде перенаправления вывода, окажется, что всё, что выводилось командамиecho
, попало в этот файл.
Командуexec
можно использовать не только в начале скрипта, но и в других местах:
#!/bin/bash exec 2>myerror echo "This is the start of the script" echo "now redirecting all output to another location" exec 1>myfile echo "This should go to the myfile file" echo "and this should go to the myerror file" >&2
Вот что получится после запуска скрипта и просмотра файлов, в которые мы перенаправляли вывод.
Перенаправление вывода в разные файлы
Сначала командаexec
задаёт перенаправление вывода изSTDERR
в файлmyerror
. Затем вывод нескольких командecho
отправляется вSTDOUT
и выводится на экран. После этого командаexec
задаёт отправку того, что попадает вSTDOUT
, в файлmyfile
, и, наконец, мы пользуемся командой перенаправления вSTDERR
в командеecho
, что приводит к записи соответствующей строки в файлmyerror.
Освоив это, вы сможете перенаправлять вывод туда, куда нужно. Теперь поговорим о перенаправлении ввода.
Перенаправление ввода в скриптах
Для перенаправления ввода можно воспользоваться той же методикой, которую мы применяли для перенаправления вывода. Например, командаexec
позволяет сделать источником данных дляSTDIN
какой-нибудь файл:
Эта команда указывает оболочке на то, что источником вводимых данных должен стать файлmyfile
, а не обычныйSTDIN
. Посмотрим на перенаправление ввода в действии:
#!/bin/bash exec 0< testfile count=1 while read line do echo "Line #$count: $line" count=$(( $count + 1 )) done
Вот что появится на экране после запуска скрипта.
Перенаправление ввода
В одном из предыдущих материалов вы узнали о том, как использовать командуread
для чтения данных, вводимых пользователем с клавиатуры. Если перенаправить ввод, сделав источником данных файл, то командаread
, при попытке прочитать данные изSTDIN
, будет читать их из файла, а не с клавиатуры.
Некоторые администраторы Linux используют этот подход для чтения и последующей обработки лог-файлов.
Создание собственного перенаправления вывода
Перенаправляя ввод и вывод в сценариях, вы не ограничены тремя стандартными дескрипторами файлов. Как уже говорилось, можно иметь до девяти открытых дескрипторов. Остальные шесть, с номерами от 3 до 8, можно использовать для перенаправления ввода или вывода. Любой из них можно назначить файлу и использовать в коде скрипта.
Назначить дескриптор для вывода данных можно, используя командуexec
:
#!/bin/bash exec 3>myfile echo "This should display on the screen" echo "and this should be stored in the file" >&3 echo "And this should be back on the screen"
После запуска скрипта часть вывода попадёт на экран, часть — в файл с дескриптором3
.
Перенаправление вывода, используя собственный дескриптор
Создание дескрипторов файлов для ввода данных
Перенаправить ввод в скрипте можно точно так же, как и вывод. СохранитеSTDIN
в другом дескрипторе, прежде чем перенаправлять ввод данных.
После окончания чтения файла можно восстановитьSTDIN
и пользоваться им как обычно:
#!/bin/bash exec 6<&0 exec 0< myfile count=1 while read line do echo "Line #$count: $line" count=$(( $count + 1 )) done exec 0<&6 read -p "Are you done now? " answer case $answer in y) echo "Goodbye";; n) echo "Sorry, this is the end.";; esac
Испытаем сценарий.
Перенаправление ввода
В этом примере дескриптор файла 6 использовался для хранения ссылки наSTDIN
. Затем было сделано перенаправление ввода, источником данных дляSTDIN
стал файл. После этого входные данные для командыread
поступали из перенаправленногоSTDIN
, то есть из файла.
После чтения файла мы возвращаемSTDIN
в исходное состояние, перенаправляя его в дескриптор6
. Теперь, для того, чтобы проверить, что всё работает правильно, скрипт задаёт пользователю вопрос, ожидает ввода с клавиатуры и обрабатывает то, что введено.
Закрытие дескрипторов файлов
Оболочка автоматически закрывает дескрипторы файлов после завершения работы скрипта. Однако, в некоторых случаях нужно закрывать дескрипторы вручную, до того, как скрипт закончит работу. Для того, чтобы закрыть дескриптор, его нужно перенаправить в&-
. Выглядит это так:
#!/bin/bash exec 3> myfile echo "This is a test line of data" >&3 exec 3>&- echo "This won't work" >&3
После исполнения скрипта мы получим сообщение об ошибке.
Попытка обращения к закрытому дескриптору файла
Всё дело в том, что мы попытались обратиться к несуществующему дескриптору.
Будьте внимательны, закрывая дескрипторы файлов в сценариях. Если вы отправляли данные в файл, потом закрыли дескриптор, потом — открыли снова, оболочка заменит существующий файл новым. То есть всё то, что было записано в этот файл ранее, будет утеряно.
Получение сведений об открытых дескрипторах
Для того, чтобы получить список всех открытых в Linux дескрипторов, можно воспользоваться командойlsof
. Во многих дистрибутивах, вроде Fedora, утилитаlsof
находится в/usr/sbin
. Эта команда весьма полезна, так как она выводит сведения о каждом дескрипторе, открытом в системе. Сюда входит и то, что открыли процессы, выполняемые в фоне, и то, что открыто пользователями, вошедшими в систему.
У этой команды есть множество ключей, рассмотрим самые важные.
-p
Позволяет указатьID
процесса.-d
Позволяет указать номер дескриптора, о котором надо получить сведения.
Для того, чтобы узнатьPID
текущего процесса, можно использовать специальную переменную окружения$$
, в которую оболочка записывает текущийPID
.
Ключ-a
используется для выполнения операции логическогоИ
над результатами, возвращёнными благодаря использованию двух других ключей:
Вывод сведений об открытых дескрипторах
Тип файлов, связанных сSTDIN
,STDOUT
иSTDERR —
CHR (character mode, символьный режим). Так как все они указывают на терминал, имя файла соответствует имени устройства, назначенного терминалу. Все три стандартных файла доступны и для чтения, и для записи.
Посмотрим на вызов командыlsof
из скрипта, в котором открыты, в дополнение к стандартным, другие дескрипторы:
#!/bin/bash exec 3> myfile1 exec 6> myfile2 exec 7< myfile3 lsof -a -p $$ -d 0,1,2,3,6,7
Вот что получится, если этот скрипт запустить.
Просмотр дескрипторов файлов, открытых скриптом
Скрипт открыл два дескриптора для вывода (3
и6
) и один — для ввода (7
). Тут же показаны и пути к файлам, использованных для настройки дескрипторов.
Подавление вывода
Иногда надо сделать так, чтобы команды в скрипте, который, например, может исполняться как фоновый процесс, ничего не выводили на экран. Для этого можно перенаправить вывод в /dev/null
. Это — что-то вроде «чёрной дыры».
Вот, например, как подавить вывод сообщений об ошибках:
ls -al badfile anotherfile 2> /dev/null
Тот же подход используется, если, например, надо очистить файл, не удаляя его:
Bash-скрипты, часть 5: сигналы, фоновые задачи, управление сценариями
Сигналы Linux
В Linux существует более трёх десятков сигналов, которые генерирует система или приложения. Вот список наиболее часто используемых, которые наверняка пригодятся при разработке сценариев командной строки.
Код сигнала | Название | Описание |
1 | SIGHUP | Закрытие терминала |
2 | SIGINT | Сигнал остановки процесса пользователем с терминала (CTRL + C) |
3 | SIGQUIT | Сигнал остановки процесса пользователем с терминала (CTRL + ) с дампом памяти |
9 | SIGKILL | Безусловное завершение процесса |
15 | SIGTERM | Сигнал запроса завершения процесса |
17 | SIGSTOP | Принудительная приостановка выполнения процесса, но не завершение его работы |
18 | SIGTSTP | Приостановка процесса с терминала (CTRL + Z), но не завершение работы |
19 | SIGCONT | Продолжение выполнения ранее остановленного процесса |
Если оболочка bash получает сигналSIGHUP
когда вы закрываете терминал, она завершает работу. Перед выходом она отправляет сигналSIGHUP
всем запущенным в ней процессам, включая выполняющиеся скрипты.
СигналSIGINT
приводит к временной остановке работы. Ядро Linux перестаёт выделять оболочке процессорное время. Когда это происходит, оболочка уведомляет процессы, отправляя им сигналSIGINT
.
Bash-скрипты не контролируют эти сигналы, но они могут распознавать их и выполнять некие команды для подготовки скрипта к последствиям, вызываемым сигналами.
Отправка сигналов скриптам
Оболочка bash позволяет вам отправлять скриптам сигналы, пользуясь комбинациями клавиш на клавиатуре. Это оказывается очень кстати если нужно временно остановить выполняющийся скрипт или завершить его работу.
Завершение работы процесса
Комбинация клавишCTRL + C
генерирует сигналSIGINT
и отправляет его всем процессам, выполняющимся в оболочке, что приводит к завершению их работы.
Выполним в оболочке такую команду:
После этого завершим её работу комбинацией клавишCTRL + C
.
Завершение работы процесса с клавиатуры
Временная остановка процесса
Комбинация клавишCTRL + Z
позволяет сгенерировать сигналSIGTSTP
, который приостанавливает работу процесса, но не завершает его выполнение. Такой процесс остаётся в памяти, его работу можно возобновить. Выполним в оболочке команду:
И временно остановим её комбинацией клавишCTRL + Z
.
Приостановка процесса
Число в квадратных скобках — это номер задания, который оболочка назначает процессу. Оболочка рассматривает процессы, выполняющиеся в ней, как задания с уникальными номерами. Первому процессу назначается номер 1, второму — 2, и так далее.
Если вы приостановите задание, привязанное к оболочке, и попытаетесь выйти из неё, bash выдаст предупреждение.
Просмотреть приостановленные задания можно такой командой:
Список заданий
В колонкеS
, выводящей состояние процесса, для приостановленных процессов выводитсяT
. Это указывает на то, что команда либо приостановлена, либо находится в состоянии трассировки.
Если нужно завершить работу приостановленного процесса, можно воспользоваться командойkill
. Подробности о ней можно почитатьздесь.
Выглядит её вызов так:
Перехват сигналов
Для того, чтобы включить в скрипте отслеживание сигналов Linux, используется командаtrap
. Если скрипт получает сигнал, указанный при вызове этой команды, он обрабатывает его самостоятельно, при этом оболочка такой сигнал обрабатывать не будет.
Командаtrap
позволяет скрипту реагировать на сигналы, в противном случае их обработка выполняется оболочкой без его участия.
Рассмотрим пример, в котором показано, как при вызове командыtrap
задаётся код, который надо выполнить, и список сигналов, разделённых пробелами, которые мы хотим перехватить. В данном случае это всего один сигнал:
#!/bin/bash trap "echo ' Trapped Ctrl-C'" SIGINT echo This is a test script count=1 while [ $count -le 10 ] do echo "Loop #$count" sleep 1 count=$(( $count + 1 )) done
Командаtrap
, использованная в этом примере, выводит текстовое сообщение всякий раз, когда она обнаруживает сигналSIGINT
, который можно сгенерировать, нажавCtrl + C
на клавиатуре.
Перехват сигналов
Каждый раз, когда вы нажимаете клавишиCTRL + C
, скрипт выполняет командуecho
, указанную при вызовеtrace
вместо того, чтобы позволить оболочке завершит его работу.
Перехват сигнала выхода из скрипта
Перехватить сигнал выхода из скрипта можно, использовав при вызове командыtrap
имя сигналаEXIT
:
#!/bin/bash trap "echo Goodbye..." EXIT count=1 while [ $count -le 5 ] do echo "Loop #$count" sleep 1 count=$(( $count + 1 )) done
Перехват сигнала выхода из скрипта
При выходе из скрипта, будь то нормальное завершение его работы или завершение, вызванное сигналомSIGINT
, сработает перехват и оболочка исполнит командуecho
.
Модификация перехваченных сигналов и отмена перехвата
Для модификации перехваченных скриптом сигналов можно выполнить командуtrap
с новыми параметрами:
#!/bin/bash trap "echo 'Ctrl-C is trapped.'" SIGINT count=1 while [ $count -le 5 ] do echo "Loop #$count" sleep 1 count=$(( $count + 1 )) done trap "echo ' I modified the trap!'" SIGINT count=1 while [ $count -le 5 ] do echo "Second Loop #$count" sleep 1 count=$(( $count + 1 )) done
Модификация перехвата сигналов
После модификации сигналы будут обрабатываться по-новому.
Перехват сигналов можно и отменить, для этого достаточно выполнить командуtrap
, передав ей двойное тире и имя сигнала:
#!/bin/bash trap "echo 'Ctrl-C is trapped.'" SIGINT count=1 while [ $count -le 5 ] do echo "Loop #$count" sleep 1 count=$(( $count + 1 )) done trap -- SIGINT echo "I just removed the trap" count=1 while [ $count -le 5 ] do echo "Second Loop #$count" sleep 1 count=$(( $count + 1 )) done
Если скрипт получит сигнал до отмены перехвата, он обработает его так, как задано в действующей командеtrap
. Запустим скрипт:
И нажмёмCTRL + C
на клавиатуре.
Сигнал, перехваченный до отмены перехвата
Первое нажатиеCTRL + C
пришлось на момент исполнения скрипта, когда перехват сигнала был в силе, поэтому скрипт исполнил назначенную сигналу командуecho
. После того, как исполнение дошло до команды отмены перехвата, командаCTRL + C
сработала обычным образом, завершив работу скрипта.
Выполнение сценариев командной строки в фоновом режиме
Иногда bash-скриптам требуется немало времени для выполнения некоей задачи. При этом вам может понадобиться возможность нормально работать в командной строке, не дожидаясь завершения скрипта. Реализовать это не так уж и сложно.
Если вы видели список процессов, выводимый командойps
, вы могли заметить процессы, которые выполняются в фоне и не привязаны к терминалу.
Напишем такой скрипт:
#!/bin/bash count=1 while [ $count -le 10 ] do sleep 1 count=$(( $count + 1 )) done
Запустим его, указав после имени символ амперсанда (&
):
Это приведёт к тому, что он будет запущен как фоновый процесс.
Запуск скрипта в фоновом режиме
Скрипт будет запущен в фоновом процессе, в терминал выведется его идентификатор, а когда его выполнение завершится, вы увидите сообщение об этом.
Обратите внимание на то, что хотя скрипт выполняется в фоне, он продолжает использовать терминал для вывода сообщений вSTDOUT
иSTDERR
, то есть, выводимый им текст или сообщения об ошибках можно будет увидеть в терминале.
Список процессов
При таком подходе, если выйти из терминала, скрипт, выполняющийся в фоне, так же завершит работу.
Что если нужно, чтобы скрипт продолжал работать и после закрытия терминала?
Выполнение скриптов, не завершающих работу при закрытии терминала
Скрипты можно выполнять в фоновых процессах даже после выхода из терминальной сессии. Для этого можно воспользоваться командойnohup
. Эта команда позволяет запустить программу, блокируя сигналыSIGHUP
, отправляемые процессу. В результате процесс будет исполняться даже при выходе из терминала, в котором он был запущен.
Применим эту методику при запуске нашего скрипта:
Вот что будет выведено в терминал.
Команда nohup
Командаnohup
отвязывает процесс от терминала. Это означает, что процесс потеряет ссылки наSTDOUT
иSTDERR
. Для того, чтобы не потерять данные, выводимые скриптом,nohup
автоматически перенаправляет сообщения, поступающие вSTDOUT
и вSTDERR
, в файлnohup.out
.
Обратите внимание на то, что при запуске нескольких скриптов из одной и той же директории то, что они выводят, попадёт в один файлnohup.out
.
Просмотр заданий
Командаjobs
позволяет просматривать текущие задания, которые выполняются в оболочке. Напишем такой скрипт:
#!/bin/bash count=1 while [ $count -le 10 ] do echo "Loop #$count" sleep 10 count=$(( $count + 1 )) done
Запустим его:
И временно остановим комбинацией клавишCTRL + Z
.
Запуск и приостановка скрипта
Запустим тот же скрипт в фоновом режиме, при этом перенаправим вывод скрипта в файл так, чтобы он ничего не выводил на экране:
Выполнив теперь командуjobs
, мы увидим сведения как о приостановленном скрипте, так и о том, который работает в фоне.
Получение сведений о скриптах
Ключ-l
при вызове командыjobs
указывает на то, что нам нужны сведения обID
процессов.
Перезапуск приостановленных заданий
Для того, чтобы перезапустить скрипт в фоновом режиме, можно воспользоваться командойbg
.
Запустим скрипт:
НажмёмCTRL + Z
, что временно остановит его выполнение. Выполним следующую команду:
Команда bg
Теперь скрипт выполняется в фоновом режиме.
Если у вас имеется несколько приостановленных заданий, для перезапуска конкретного задания командеbg
можно передать его номер.
Для перезапуска задания в обычном режиме воспользуйтесь командойfg
:
Планирование запуска скриптов
Linux предоставляет пару способов запуска bash-скриптов в заданное время. Это командаat
и планировщик заданийcron
.
Вызов команды at выглядит так:
Эта команда распознаёт множество форматов указания времени.
- Стандартный, с указанием часов и минут, например — 10:15.
- С использованием индикаторов AM/PM, до или после полудня, например — 10:15PM.
- С использованием специальных имён, таких, как
now
,noon
,midnight
.
В дополнение к возможности указания времени запуска задания, командеat
можно передать и дату, используя один из поддерживаемых ей форматов.
- Стандартный формат указания даты, при котором дата записывается по шаблонам
MMDDYY
,MM/DD/YY
, илиDD.MM.YY
. - Текстовое представление даты, например,
Jul 4
илиDec 25
, при этом год можно указать, а можно обойтись и без него. - Запись вида
now + 25 minutes
. - Запись вида
10:15PM tomorrow
. - Запись вида
10:15 + 7 days
.
Не будем углубляться в эту тему, рассмотрим простой вариант использования команды:
Планирование заданий с использованием команды at
Ключ-M
при вызовеat
используется для отправки того, что выведет скрипт, по электронной почте, если система соответствующим образом настроена. Если отправка электронного письма невозможна, этот ключ просто подавит вывод.
Для того чтобы посмотреть список заданий, ожидающих выполнения, можно воспользоваться командойatq
:
Список заданий, ожидающих выполнения
Удаление заданий, ожидающих выполнения
Удалить задание, ожидающее выполнения, позволяет командаatrm
. При её вызове указывают номер задания:
Удаление задания
Запуск скриптов по расписанию
Планирование однократного запуска скриптов с использованием командыat
способно облегчить жизнь во многих ситуациях. Но как быть, если нужно, чтобы скрипт выполнялся в одно и то же время ежедневно, или раз в неделю, или раз в месяц?
В Linux имеется утилитаcrontab
, позволяющая планировать запуск скриптов, которые нужно выполнять регулярно.
Crontab
выполняется в фоне и, основываясь на данных в так называемых cron-таблицах, запускает задания по расписанию.
Для того, чтобы просмотреть существующую таблицу заданийcron
, воспользуйтесь такой командой:
При планировании запуска скрипта по расписаниюcrontab
принимает данные о том, когда нужно выполнить задание, в таком формате:
минута, час, день месяца, месяц, день недели.
Например, если надо, чтобы некий скрипт с именемcommand
выполнялся ежедневно в 10:30, этому будет соответствовать такая запись в таблице заданий:
Здесь универсальный символ «*
», использованный для полей, задающих день месяца, месяц и день недели, указывает на то, чтоcron
должен выполнять команду каждый день каждого месяца в 10:30.
Если, например, надо, чтобы скрипт запускался в4:30PM
каждый понедельник, понадобится создать в таблице заданий такую запись:
Нумерация дней недели начинается с 0, 0 означает воскресенье, 6 — субботу. Вот ещё один пример. Здесь команда будет выполняться в 12 часов дня в первый день каждого месяца.
Нумерация месяцев начинается с 1.
Для того чтобы добавить запись в таблицу, нужно вызватьcrontab
с ключом-e
:
Затем можно вводить команды формирования расписания:
30 10 * * * /home/likegeeks/Desktop/myscript
Благодаря этой команде скрипт будет вызываться ежедневно в 10:30. Если вы столкнётесь с ошибкой «Resource temporarily unavailable», выполните нижеприведённую команду с правами root-пользователя:
$ rm -f /var/run/crond.pid
Организовать периодический запуск скриптов с использованиемcron
можно ещё проще, воспользовавшись несколькими специальными директориями:
/etc/cron.hourly
/etc/cron.daily
/etc/cron.weekly
/etc/cron.monthly
Если поместить файл скрипта в одну из них, это приведёт, соответственно, к его ежечасному, ежедневному, еженедельному или ежемесячному запуску.
Запуск скриптов при входе в систему и при запуске оболочки
Автоматизировать запуск скриптов можно, опираясь на различные события, такие, как вход пользователя в систему или запуск оболочки.Тутможно почитать о файлах, которые обрабатываются в подобных ситуациях. Например, это следующие файлы:
$HOME/.bash_profile $HOME/.bash_login $HOME/.profile
Для того, чтобы запускать скрипт при входе в систему, поместите его вызов в файл.bash_profile
.
А как насчёт запуска скриптов при открытии терминала? Организовать это поможет файл.bashrc
.
Bash-скрипты, часть 6: функции и разработка библиотек
Объявление функций
Функцию можно объявить так:
Или так:
Функцию можно вызвать без аргументов и с аргументами.
Использование функций
Напишем скрипт, содержащий объявление функции и использующий её:
#!/bin/bash function myfunc { echo "This is an example of using a function" } count=1 while [ $count -le 3 ] do myfunc count=$(( $count + 1 )) done echo "This is the end of the loop" myfunc echo "End of the script"
Здесь создана функция с именемmyfunc
. Для вызова функции достаточно указать её имя.
Результаты вызова функции
Функцию можно вызывать столько раз, сколько нужно. Обратите внимание на то, что попытавшись использовать функцию до её объявления, вы столкнётесь с ошибкой. Напишем демонстрирующий это скрипт:
#!/bin/bash count=1 while [ $count -le 3 ] do myfunc count=$(( $count + 1 )) done echo "This is the end of the loop" function myfunc { echo "This is an example of using a function" } echo "End of the script"
Как и ожидается, ничего хорошего после его запуска не произошло.
Попытка воспользоваться функцией до её объявления
Придумывая имена для функций, учитывайте то, что они должны быть уникальными, иначе проблем не избежать. Если вы переопределите ранее объявленную функцию, новая функция будет вызываться вместо старой без каких-либо уведомлений или сообщений об ошибках. Продемонстрируем это на примере:
#!/bin/bash function myfunc { echo "The first function definition" } myfunc function myfunc { echo "The second function definition" } myfunc echo "End of the script"
Как видно, новая функция преспокойно затёрла старую.
Переопределение функции
Использование команды return
Командаreturn
позволяет задавать возвращаемый функцией целочисленный код завершения. Есть два способа работы с тем, что является результатом вызова функции. Вот первый:
#!/bin/bash function myfunc { read -p "Enter a value: " value echo "adding value" return $(( $value + 10 )) } myfunc echo "The new value is $?"
Командаecho
вывела сумму введённого числа и числа 10.
Вывод значения, возвращаемого функцией
Функцияmyfunc
добавляет 10 к числу, которое содержится в переменной$value
, значение которой задаёт пользователь во время работы сценария. Затем она возвращает результат, используя командуreturn
. То, что возвратила функция, выводится командойecho
с использованием переменной$?
.
Если вы выполните любую другую команду до извлечения из переменной$?
значения, возвращённого функцией, это значение будет утеряно. Дело в том, что данная переменная хранит код возврата последней выполненной команды.
Учтите, что максимальное число, которое может вернуть командаreturn
— 255. Если функция должна возвращать большее число или строку, понадобится другой подход.
Запись вывода функции в переменную
Ещё один способ возврата результатов работы функции заключается в записи данных, выводимых функцией, в переменную. Такой подход позволяет обойти ограничения командыreturn
и возвращать из функции любые данные. Рассмотрим пример:
#!/bin/bash function myfunc { read -p "Enter a value: " value echo $(( $value + 10 )) } result=$( myfunc) echo "The value is $result"
Вот что получится после вызова данного скрипта.
Запись результатов работы функции в переменную
Аргументы функций
Функции bash можно воспринимать как небольшие фрагменты кода, которые позволяют экономить время и место, избавляя нас от необходимости постоянно вводить с клавиатуры или копировать одни и те же наборы команд. Однако, возможности функций гораздо шире. В частности, речь идёт о передаче им аргументов.
Функции могут использовать стандартные позиционные параметры, в которые записывается то, что передаётся им при вызове. Например, имя функции хранится в параметре$0
, первый переданный ей аргумент — в$1
, второй — в$2
, и так далее. Количество переданных функции аргументов можно узнать, обратившись к переменной$#
. Если вы знакомы стретьей частьюэтого цикла материалов, вы не можете не заметить, что всё это очень похоже на то, как скрипты обрабатывают переданные им параметры командной строки.
Аргументы передают функции, записывая их после её имени:
Вот пример, в котором функция вызывается с аргументами и занимается их обработкой:
#!/bin/bash function addnum { if [ $# -eq 0 ] || [ $# -gt 2 ] then echo -1 elif [ $# -eq 1 ] then echo $(( $1 + $1 )) else echo $(( $1 + $2 )) fi } echo -n "Adding 10 and 15: " value=$(addnum 10 15) echo $value echo -n "Adding one number: " value=$(addnum 10) echo $value echo -n "Adding no numbers: " value=$(addnum) echo $value echo -n "Adding three numbers: " value=$(addnum 10 15 20) echo $value
Запустим скрипт.
Вызов функции с аргументами
Функцияaddnum
проверяет число переданных ей при вызове из скрипта аргументов. Если их нет, или их больше двух, функция возвращает значение -1. Если параметр всего один, она прибавляет его к нему самому и возвращает результат. Если параметров два, функция складывает их.
Обратите внимание на то, что функция не может напрямую работать с параметрами, которые переданы скрипту при его запуске из командной строки. Например, напишем такой сценарий:
#!/bin/bash function myfunc { echo $(( $1 + $2 )) } if [ $# -eq 2 ] then value=$( myfunc) echo "The result is $value" else echo "Usage: myfunc a b" fi
При его запуске, а точнее, при вызове объявленной в нём функции, будет выведено сообщение об ошибке.
Функция не может напрямую использовать параметры, переданные сценарию
Вместо этого, если в функции планируется использовать параметры, переданные скрипту при вызове из командной строки, надо передать их ей при вызове:
#!/bin/bash function myfunc { echo $(( $1 + $2 )) } if [ $# -eq 2 ] then value=$(myfunc $1 $2) echo "The result is $value" else echo "Usage: myfunc a b" fi
Теперь всё работает правильно.
Передача функции параметров, с которыми запущен скрипт
Работа с переменными в функциях
Переменные, которыми мы пользуемся в сценариях, характеризуются областью видимости. Это — те места кода, из которых можно работать с этими переменными. Переменные, объявленные внутри функций, ведут себя не так, как те переменные, с которыми мы уже сталкивались. Они могут быть скрыты от других частей скриптов.
Существуют два вида переменных:
- Глобальные переменные.
- Локальные переменные.
Глобальные переменные
Глобальные переменные — это переменные, которые видны из любого места bash-скрипта. Если вы объявили глобальную переменную в основном коде скрипта, к такой переменной можно обратиться из функции.
Почти то же самое справедливо и для глобальных переменных, объявленных в функциях. Обращаться к ним можно и в основном коде скрипта после вызова функций.
По умолчанию все объявленные в скриптах переменные глобальны. Так, к переменным, объявленным за пределами функций, можно без проблем обращаться из функций:
#!/bin/bash function myfunc { value=$(( $value + 10 )) } read -p "Enter a value: " value myfunc echo "The new value is: $value"
Вот что выведет этот сценарий.
Обращение к глобальной переменной из функции
Когда переменной присваивается новое значение в функции, это новое значение не теряется когда скрипт обращается к ней после завершения работы функции. Именно это можно видеть в предыдущем примере.
Что если такое поведение нас не устраивает? Ответ прост — надо использовать локальные переменные.
Локальные переменные
Переменные, которые объявляют и используют внутри функции, могут быть объявлены локальными. Для того, чтобы это сделать, используется ключевое словоlocal
перед именем переменной:
local temp=$(( $value + 5 ))
Если за пределами функции есть переменная с таким же именем, это на неё не повлияет. Ключевое словоlocal
позволяет отделить переменные, используемые внутри функции, от остальных переменных. Рассмотрим пример:
#!/bin/bash function myfunc { local temp=$[ $value + 5 ] echo "The Temp from inside function is $temp" } temp=4 myfunc echo "The temp from outside is $temp"
Запустим скрипт.
Локальная переменная в функции
Здесь, когда мы работаем с переменной$temp
внутри функции, это не влияет на значение, назначенное переменной с таким же именем за её пределами.
Передача функциям массивов в качестве аргументов
Попробуем передать функции в качестве аргумента массив. Сразу хочется сказать, что работать такая конструкция будет неправильно:
#!/bin/bash function myfunc { echo "The parameters are: $@" arr=$1 echo "The received array is ${arr[*]}" } myarray=(1 2 3 4 5) echo "The original array is: ${myarray[*]}" myfunc $myarray
Неправильный подход к передаче функциям массивов
Как видно из примера, при передаче функции массива, она получит доступ лишь к его первому элементу.
Для того, чтобы эту проблему решить, из массива надо извлечь имеющиеся в нём данные и передать их функции как самостоятельные аргументы. Если надо, внутри функции полученные ей аргументы можно снова собрать в массив:
#!/bin/bash function myfunc { local newarray newarray=("$@") echo "The new array value is: ${newarray[*]}" } myarray=(1 2 3 4 5) echo "The original array is ${myarray[*]}" myfunc ${myarray[*]}
Запустим сценарий.
Сборка массива внутри функции
Как видно из примера, функция собрала массив из переданных ей аргументов.
Рекурсивные функции
Рекурсия — это когда функция сама себя вызывает. Классический пример рекурсии — функция для вычисления факториала. Факториал числа — это произведение всех натуральных чисел от 1 до этого числа. Например, факториал 5 можно найти так:
Если формулу вычисления факториала написать в рекурсивном виде, получится следующее:
Этой формулой можно воспользоваться для того, чтобы написать рекурсивную функцию:
#!/bin/bash function factorial { if [ $1 -eq 1 ] then echo 1 else local temp=$(( $1 - 1 )) local result=$(factorial $temp) echo $(( $result * $1 )) fi } read -p "Enter value: " value result=$(factorial $value) echo "The factorial of $value is: $result"
Проверим, верно ли работает этот скрипт.
Вычисление факториала
Как видите, всё работает как надо.
Создание и использование библиотек
Итак, теперь вы знаете, как писать функции и как вызывать их в том же скрипте, где они объявлены. Что если надо использовать функцию, тот блок кода, который она собой представляет, в другом скрипте, не используя копирование и вставку?
Оболочка bash позволяет создавать так называемые библиотеки — файлы, содержащие функции, а затем использовать эти библиотеки в любых скриптах, где они нужны.
Ключ к использованию библиотек — в командеsource
. Эта команда используется для подключения библиотек к скриптам. В результате функции, объявленные в библиотеке, становятся доступными в скрипте, в противном же случае функции из библиотек не будут доступны в области видимости других скриптов.
У командыsource
есть псевдоним — оператор «точка». Для того, чтобы подключить файл в скрипте, в скрипт надо добавить конструкцию такого вида:
Предположим, что у нас имеется файлmyfuncs
, который содержит следующее:
function addnum { echo $(( $1 + $2 )) }
Это — библиотека. Воспользуемся ей в сценарии:
#!/bin/bash . ./myfuncs result=$(addnum 10 20) echo "The result is: $result"
Вызовем его.
Использование библиотек
Только что мы использовали библиотечную функцию внутри скрипта. Всё это замечательно, но что если мы хотим вызвать функцию, объявленную в библиотеке, из командной строки?
Вызов bash-функций из командной строки
Если вы освоилипредыдущую частьиз этой серии, вы, вероятно, уже догадываетесь, что функцию из библиотеки можно подключить в файле .bashrc
, используя командуsource
. Как результат, вызывать функцию можно будет прямо из командной строки.
Отредактируйте.bashrc
, добавив в него такую строку (путь к файлу библиотеки в вашей системе, естественно, будет другим):
. /home/likegeeks/Desktop/myfuncs
Теперь функцию можно вызывать прямо из командной строки:
Вызов функции из командной строки
Ещё приятнее то, что такая вот библиотека оказывается доступной всем дочерним процессам оболочки, то есть — ей можно пользоваться в bash-скриптах, не заботясь о подключении к ним этой библиотеки.
Тут стоит отметить, что для того, чтобы вышеприведённый пример заработал, может понадобиться выйти из системы, а потом войти снова. Кроме того, обратите внимание на то, что если имя функции из библиотеки совпадёт с именем какой-нибудь стандартной команды, вместо этой команды будет вызываться функция. Поэтому внимательно относитесь к именам функций.
Bash-скрипты, часть 7: sed и обработка текстов
Основы работы с sed
Утилиту sed называют потоковым текстовым редактором. В интерактивных текстовых редакторах, наподобие nano, с текстами работают, используя клавиатуру, редактируя файлы, добавляя, удаляя или изменяя тексты. Sed позволяет редактировать потоки данных, основываясь на заданных разработчиком наборах правил. Вот как выглядит схема вызова этой команды:
По умолчанию sed применяет указанные при вызове правила, выраженные в виде набора команд, кSTDIN
. Это позволяет передавать данные непосредственно sed.
Например, так:
$ echo "This is a test" | sed 's/test/another test/'
Вот что получится при выполнении этой команды.
Простой пример вызова sed
В данном случае sed заменяет слово «test» в строке, переданной для обработки, словами «another test». Для оформления правила обработки текста, заключённого в кавычки, используются прямые слэши. В нашем случае применена команда видаs/pattern1/pattern2/
. Буква «s» — это сокращение слова «substitute», то есть — перед нами команда замены. Sed, выполняя эту команду, просмотрит переданный текст и заменит найденные в нём фрагменты (о том — какие именно, поговорим ниже), соответствующиеpattern1
, наpattern2
.
Выше приведён примитивный пример использования sed, нужный для того, чтобы ввести вас в курс дела. На самом деле, sed можно применять в гораздо более сложных сценариях обработки текстов, например — для работы с файлами.
Ниже показан файл, в котором содержится фрагмент текста, и результаты его обработки такой командой:
$ sed 's/test/another test' ./myfile
Текстовый файл и результаты его обработки
Здесь применён тот же подход, который мы использовали выше, но теперь sed обрабатывает текст, хранящийся в файле. При этом, если файл достаточно велик, можно заметить, что sed обрабатывает данные порциями и выводит то, что обработано, на экран, не дожидаясь обработки всего файла.
Sed не меняет данные в обрабатываемом файле. Редактор читает файл, обрабатывает прочитанное, и отправляет то, что получилось, вSTDOUT
. Для того, чтобы убедиться в том, что исходный файл не изменился, достаточно, после того, как он был передан sed, открыть его. При необходимости вывод sed можно перенаправить в файл, возможно — перезаписать старый файл. Если вы знакомы с одним из предыдущихматериаловэтой серии, где речь идёт о перенаправлении потоков ввода и вывода, вы вполне сможете это сделать.
Выполнение наборов команд при вызове sed
Для выполнения нескольких действий с данными, используйте ключ-e
при вызове sed. Например, вот как организовать замену двух фрагментов текста:
$ sed -e 's/This/That/; s/test/another test/' ./myfile
Использование ключа -e при вызове sed
К каждой строке текста из файла применяются обе команды. Их нужно разделить точкой с запятой, при этом между окончанием команды и точкой с запятой не должно быть пробела.
Для ввода нескольких шаблонов обработки текста при вызове sed, можно, после ввода первой одиночной кавычки, нажать Enter, после чего вводить каждое правило с новой строки, не забыв о закрывающей кавычке:
$ sed -e '
> s/This/That/
> s/test/another test/' ./myfile
Вот что получится после того, как команда, представленная в таком виде, будет выполнена.
Другой способ работы с sed
Чтение команд из файла
Если имеется множество команд sed, с помощью которых надо обработать текст, обычно удобнее всего предварительно записать их в файл. Для того, чтобы указать sed файл, содержащий команды, используют ключ-f
:
Вот содержимое файлаmycommands
:
s/This/That/
s/test/another test/
Вызовем sed, передав редактору файл с командами и файл для обработки:
$ sed -f mycommands myfile
Результат при вызове такой команды аналогичен тому, который получался в предыдущих примерах.
Использование файла с командами при вызове sed
Флаги команды замены
Внимательно посмотрите на следующий пример.
$ sed 's/test/another test/' myfile
Вот что содержится в файле, и что будет получено после его обработки sed.
Исходный файл и результаты его обработки
Команда замены нормально обрабатывает файл, состоящий из нескольких строк, но заменяются только первые вхождения искомого фрагмента текста в каждой строке. Для того, чтобы заменить все вхождения шаблона, нужно использовать соответствующий флаг.
Схема записи команды замены при использовании флагов выглядит так:
s/pattern/replacement/flags
Выполнение этой команды можно модифицировать несколькими способами.
-
При передаче номера учитывается порядковый номер вхождения шаблона в строку, заменено будет именно это вхождение.
-
Флаг
g
указывает на то, что нужно обработать все вхождения шаблона, имеющиеся в строке. -
Флаг
p
указывает на то, что нужно вывести содержимое исходной строки. -
Флаг вида
w file
указывает команде на то, что нужно записать результаты обработки текста в файл.
Рассмотрим использование первого варианта команды замены, с указанием позиции заменяемого вхождения искомого фрагмента:
$ sed 's/test/another test/2' myfile
Вызов команды замены с указанием позиции заменяемого фрагмента
Тут мы указали, в качестве флага замены, число 2. Это привело к тому, что было заменено лишь второе вхождение искомого шаблона в каждой строке. Теперь опробуем флаг глобальной замены —g
:
$ sed 's/test/another test/g' myfile
Как видно из результатов вывода, такая команда заменила все вхождения шаблона в тексте.
Глобальная замена
Флаг команды заменыp
позволяет выводить строки, в которых найдены совпадения, при этом ключ-n
, указанный при вызове sed, подавляет обычный вывод:
$ sed -n 's/test/another test/p' myfile
Как результат, при запуске sed в такой конфигурации на экран выводятся лишь строки (в нашем случае — одна строка), в которых найден заданный фрагмент текста.
Использование флага команды замены p
Воспользуемся флагомw
, который позволяет сохранить результаты обработки текста в файл:
$ sed 's/test/another test/w output' myfile
Сохранение результатов обработки текста в файл
Хорошо видно, что в ходе работы команды данные выводятся вSTDOUT, при этом обработанные строки записываются в файл, имя которого указано послеw
.
Символы-разделители
Представьте, что нужно заменить/bin/bash
на/bin/csh
в файле/etc/passwd
. Задача не такая уж и сложная:
$ sed 's//bin/bash//bin/csh/' /etc/passwd
Однако, выглядит всё это не очень-то хорошо. Всё дело в том, что так как прямые слэши используются в роли символов-разделителей, такие же символы в передаваемых sed строках приходится экранировать. В результате страдает читаемость команды.
К счастью, sed позволяет нам самостоятельно задавать символы-разделители для использования их в команде замены. Разделителем считается первый символ, который будет встречен послеs
:
$ sed 's!/bin/bash!/bin/csh!' /etc/passwd
В данном случае в качестве разделителя использован восклицательный знак, в результате код легче читать и он выглядит куда опрятнее, чем прежде.
Выбор фрагментов текста для обработки
До сих пор мы вызывали sed для обработки всего переданного редактору потока данных. В некоторых случаях с помощью sed надо обработать лишь какую-то часть текста — некую конкретную строку или группу строк. Для достижения такой цели можно воспользоваться двумя подходами:
-
Задать ограничение на номера обрабатываемых строк.
-
Указать фильтр, соответствующие которому строки нужно обработать.
Рассмотрим первый подход. Тут допустимо два варианта. Первый, рассмотренный ниже, предусматривает указание номера одной строки, которую нужно обработать:
$ sed '2s/test/another test/' myfile
Обработка только одной строки, номер который задан при вызове sed
Второй вариант — диапазон строк:
$ sed '2,3s/test/another test/' myfile
Обработка диапазона строк
Кроме того, можно вызвать команду замены так, чтобы файл был обработан начиная с некоей строки и до конца:
$ sed '2,$s/test/another test/' myfile
Обработка файла начиная со второй строки и до конца
Для того, чтобы обрабатывать с помощью команды замены только строки, соответствующие заданному фильтру, команду надо вызвать так:
$ sed '/likegeeks/s/bash/csh/' /etc/passwd
По аналогии с тем, что было рассмотрено выше, шаблон передаётся перед именем командыs
.
Обработка строк, соответствующих фильтру
Тут мы использовали очень простой фильтр. Для того, чтобы в полной мере раскрыть возможности данного подхода, можно воспользоваться регулярными выражениями. О них мы поговорим в одном из следующих материалов этой серии.
Удаление строк
Утилита sed годится не только для замены одних последовательностей символов в строках на другие. С её помощью, а именно, используя командуd
, можно удалять строки из текстового потока.
Вызов команды выглядит так:
Мы хотим, чтобы из текста была удалена третья строка. Обратите внимание на то, что речь не идёт о файле. Файл останется неизменным, удаление отразится лишь на выводе, который сформирует sed.
Удаление третьей строки
Если при вызове командыd
не указать номер удаляемой строки, удалены будут все строки потока.
Вот как применить командуd
к диапазону строк:
Удаление диапазона строк
А вот как удалить строки, начиная с заданной — и до конца файла:
Удаление строк до конца файла
Строки можно удалять и по шаблону:
Удаление строк по шаблону
При вызовеd
можно указывать пару шаблонов — будут удалены строки, в которых встретится шаблон, и те строки, которые находятся между ними:
$ sed '/second/,/fourth/d' myfile
Удаление диапазона строк с использованием шаблонов
Вставка текста в поток
С помощью sed можно вставлять данные в текстовый поток, используя команды i
и a
:
-
Команда
i
добавляет новую строку перед заданной. -
Команда
a
добавляет новую строку после заданной.
Рассмотрим пример использования командыi
:
$ echo "Another test" | sed 'iFirst test '
Команда i
Теперь взглянем на командуa
:
$ echo "Another test" | sed 'aFirst test '
Команда a
Как видно, эти команды добавляют текст до или после данных из потока. Что если надо добавить строку где-нибудь посередине?
Тут нам поможет указание номера опорной строки в потоке, или шаблона. Учтите, что адресация строк в виде диапазона тут не подойдёт. Вызовем командуi
, указав номер строки, перед которой надо вставить новую строку:
$ sed '2iThis is the inserted line.' myfile
Команда i с указанием номера опорной строки
Проделаем то же самое с командойa
:
$ sed '2aThis is the appended line.' myfile
Команда a с указанием номера опорной строки
Обратите внимание на разницу в работе командi
иa
. Первая вставляет новую строку до указанной, вторая — после.
Замена строк
Командаc
позволяет изменить содержимое целой строки текста в потоке данных. При её вызове нужно указать номер строки, вместо которой в поток надо добавить новые данные:
$ sed '3cThis is a modified line.' myfile
Замена строки целиком
Если воспользоваться при вызове команды шаблоном в виде обычного текста или регулярного выражения, заменены будут все соответствующие шаблону строки:
$ sed '/This is/c This is a changed line of text.' myfile
Замена строк по шаблону
Замена символов
Командаy
работает с отдельными символами, заменяя их в соответствии с переданными ей при вызове данными:
$ sed 'y/123/567/' myfile
Замена символов
Используя эту команду, нужно учесть, что она применяется ко всему текстовому потоку, ограничить её конкретными вхождениями символов нельзя.
Вывод номеров строк
Если вызвать sed, использовав команду=
, утилита выведет номера строк в потоке данных:
Вывод номеров строк
Потоковый редактор вывел номера строк перед их содержимым.
Если передать этой команде шаблон и воспользоваться ключом sed-n
, выведены будут только номера строк, соответствующих шаблону:
$ sed -n '/test/=' myfile
Вывод номеров строк, соответствующих шаблону
Чтение данных для вставки из файла
Выше мы рассматривали приёмы вставки данных в поток, указывая то, что надо вставить, прямо при вызове sed. В качестве источника данных можно воспользоваться и файлом. Для этого служит командаr
, которая позволяет вставлять в поток данные из указанного файла. При её вызове можно указать номер строки, после которой надо вставить содержимое файла, или шаблон.
Рассмотрим пример:
$ sed '3r newfile' myfile
Вставка в поток содержимого файла
Тут содержимое файлаnewfile
было вставлено после третьей строки файлаmyfile
.
Вот что произойдёт, если применить при вызове командыr
шаблон:
$ sed '/test/r newfile' myfile
Использование шаблона при вызове команды r
Содержимое файла будет вставлено после каждой строки, соответствующей шаблону.
Пример
Представим себе такую задачу. Есть файл, в котором имеется некая последовательность символов, сама по себе бессмысленная, которую надо заменить на данные, взятые из другого файла. А именно, пусть это будет файлnewfile
, в котором роль указателя места заполнения играет последовательность символовDATA
. Данные, которые нужно подставить вместоDATA
, хранятся в файлеdata
.
Решить эту задачу можно, воспользовавшись командамиr
иd
потокового редактора sed:
$ Sed '/DATA>/ {
r newfile
d}' myfile
Замена указателя места заполнения на реальные данные
Как видите, вместо заполнителяDATA
sed добавил в выходной поток две строки из файлаdata
.
Bash-скрипты, часть 8: язык обработки данных awk
Особенности вызова awk
Схема вызова awk выглядит так:
$ awk options program file
Awk воспринимает поступающие к нему данные в виде набора записей. Записи представляют собой наборы полей. Упрощенно, если не учитывать возможности настройки awk и говорить о некоем вполне обычном тексте, строки которого разделены символами перевода строки, запись — это строка. Поле — это слово в строке.
Рассмотрим наиболее часто используемые ключи командной строки awk:
-F fs
— позволяет указать символ-разделитель для полей в записи.
-f file
— указывает имя файла, из которого нужно прочесть awk-скрипт.
-v var=value
— позволяет объявить переменную и задать её значение по умолчанию, которое будет использовать awk.
-mf N
— задаёт максимальное число полей для обработки в файле данных.
-mr N
— задаёт максимальный размер записи в файле данных.
-W keyword
— позволяет задать режим совместимости или уровень выдачи предупреждений awk.
Настоящая мощь awk скрывается в той части команды его вызова, которая помечена выше какprogram
. Она указывает на файл awk-скрипта, написанный программистом и предназначенный для чтения данных, их обработки и вывода результатов.
Чтение awk-скриптов из командной строки
Скрипты awk, которые можно писать прямо в командной строке, оформляются в виде текстов команд, заключённых в фигурные скобки. Кроме того, так как awk предполагает, что скрипт представляет собой текстовую строку, его нужно заключить в одинарные кавычки:
$ awk '{print "Welcome to awk command tutorial"}'
Запустим эту команду… И ничего не произойдёт Дело тут в том, что мы, при вызове awk, не указали файл с данными. В подобной ситуации awk ожидает поступления данных изSTDIN. Поэтому выполнение такой команды не приводит к немедленно наблюдаемым эффектам, но это не значит, что awk не работает — он ждёт входных данных изSTDIN
.
Если теперь ввести что-нибудь в консоль и нажатьEnter
, awk обработает введённые данные с помощью скрипта, заданного при его запуске. Awk обрабатывает текст из потока ввода построчно, этим он похож на sed. В нашем случае awk ничего не делает с данными, он лишь, в ответ на каждую новую полученную им строку, выводит на экран текст, заданный в командеprint
.
Первый запуск awk, вывод на экран заданного текста
Что бы мы ни ввели, результат в данном случае будет одним и тем же — вывод текста.
Для того, чтобы завершить работу awk, нужно передать ему символ конца файла (EOF, End-of-File). Сделать это можно, воспользовавшись сочетанием клавишCTRL + D
.
Неудивительно, если этот первый пример показался вам не особо впечатляющим. Однако, самое интересное — впереди.
Позиционные переменные, хранящие данные полей
Одна из основных функций awk заключается в возможности манипулировать данными в текстовых файлах. Делается это путём автоматического назначения переменной каждому элементу в строке. По умолчанию awk назначает следующие переменные каждому полю данных, обнаруженному им в записи:
$0
— представляет всю строку текста (запись).$1
— первое поле.$2
— второе поле.$n
— n-ное поле.
Поля выделяются из текста с использованием символа-разделителя. По умолчанию — это пробельные символы вроде пробела или символа табуляции.
Рассмотрим использование этих переменных на простом примере. А именно, обработаем файл, в котором содержится несколько строк (этот файл показан на рисунке ниже) с помощью такой команды:
$ awk '{print $1}' myfile
Вывод в консоль первого поля каждой строки
Здесь использована переменная$1
, которая позволяет получить доступ к первому полю каждой строки и вывести его на экран.
Иногда в некоторых файлах в качестве разделителей полей используется что-то, отличающееся от пробелов или символов табуляции. Выше мы упоминали ключ awk-F
, который позволяет задать необходимый для обработки конкретного файла разделитель:
$ awk -F: '{print $1}' /etc/passwd
Указание символа-разделителя при вызове awk
Эта команда выводит первые элементы строк, содержащихся в файле/etc/passwd
. Так как в этом файле в качестве разделителей используются двоеточия, именно этот символ был передан awk после ключа-F
.
Использование нескольких команд
Вызов awk с одной командой обработки текста — подход очень ограниченный. Awk позволяет обрабатывать данные с использованием многострочных скриптов. Для того, чтобы передать awk многострочную команду при вызове его из консоли, нужно разделить её части точкой с запятой:
$ echo "My name is Tom" | awk '{$4="Adam"; print $0}'
Вызов awk из командной строки с передачей ему многострочного скрипта
В данном примере первая команда записывает новое значение в переменную$4
, а вторая выводит на экран всю строку.
Чтение скрипта awk из файла
Awk позволяет хранить скрипты в файлах и ссылаться на них, используя ключ-f
. Подготовим файлtestfile
, в который запишем следующее:
{print $1 " has a home directory at " $6}
Вызовем awk, указав этот файл в качестве источника команд:
$ awk -F: -f testfile /etc/passwd
Вызов awk с указанием файла скрипта
Тут мы выводим из файла/etc/passwd
имена пользователей, которые попадают в переменную$1
, и их домашние директории, которые попадают в$6
. Обратите внимание на то, что файл скрипта задают с помощью ключа-f
, а разделитель полей, двоеточие в нашем случае, с помощью ключа-F
.
В файле скрипта может содержаться множество команд, при этом каждую из них достаточно записывать с новой строки, ставить после каждой точку с запятой не требуется.
Вот как это может выглядеть:
{
text = " has a home directory at "
print $1 text $6
}
Тут мы храним текст, используемый при выводе данных, полученных из каждой строки обрабатываемого файла, в переменной, и используем эту переменную в командеprint
. Если воспроизвести предыдущий пример, записав этот код в файлtestfile
, выведено будет то же самое.
Выполнение команд до начала обработки данных
Иногда нужно выполнить какие-то действия до того, как скрипт начнёт обработку записей из входного потока. Например — создать шапку отчёта или что-то подобное.
Для этого можно воспользоваться ключевым словомBEGIN
. Команды, которые следуют заBEGIN
, будут исполнены до начала обработки данных. В простейшем виде это выглядит так:
$ awk 'BEGIN {print "Hello World!"}'
А вот — немного более сложный пример:
$ awk 'BEGIN {print "The File Contents:"}
{print $0}' myfile
Выполнение команд до начала обработки данных
Сначала awk исполняет блокBEGIN
, после чего выполняется обработка данных. Будьте внимательны с одинарными кавычками, используя подобные конструкции в командной строке. Обратите внимание на то, что и блокBEGIN
, и команды обработки потока, являются в представлении awk одной строкой. Первая одинарная кавычка, ограничивающая эту строку, стоит передBEGIN
. Вторая — после закрывающей фигурной скобки команды обработки данных.
Выполнение команд после окончания обработки данных
Ключевое словоEND
позволяет задавать команды, которые надо выполнить после окончания обработки данных:
$ awk 'BEGIN {print "The File Contents:"}
{print $0}
END {print "End of File"}' myfile
Результаты работы скрипта, в котором имеются блоки BEGIN и END
После завершения вывода содержимого файла, awk выполняет команды блокаEND
. Это полезная возможность, с её помощью, например, можно сформировать подвал отчёта. Теперь напишем скрипт следующего содержания и сохраним его в файлеmyscript
:
BEGIN {
print "The latest list of users and shells"
print " UserName t HomePath"
print "-------- t -------"
FS=":"
}
{
print $1 " t " $6
}
END {
print "The end"
}
Тут, в блокеBEGIN
, создаётся заголовок табличного отчёта. В этом же разделе мы указываем символ-разделитель. После окончания обработки файла, благодаря блокуEND
, система сообщит нам о том, что работа окончена.
Запустим скрипт:
$ awk -f myscript /etc/passwd
Обработка файла /etc/passwd с помощью awk-скрипта
Всё, о чём мы говорили выше — лишь малая часть возможностей awk. Продолжим освоение этого полезного инструмента.
Встроенные переменные: настройка процесса обработки данных
Утилита awk использует встроенные переменные, которые позволяют настраивать процесс обработки данных и дают доступ как к обрабатываемым данным, так и к некоторым сведениям о них.
Мы уже рассматривали позиционные переменные —$1
,$2
,$3
, которые позволяют извлекать значения полей, работали мы и с некоторыми другими переменными. На самом деле, их довольно много. Вот некоторые из наиболее часто используемых:
FIELDWIDTHS —
разделённый пробелами список чисел, определяющий точную ширину каждого поля данных с учётом разделителей полей.
FS
— уже знакомая вам переменная, позволяющая задавать символ-разделитель полей.
RS —
переменная, которая позволяет задавать символ-разделитель записей.
OFS —
разделитель полей на выводе awk-скрипта.
ORS —
разделитель записей на выводе awk-скрипта.
По умолчанию переменнаяOFS
настроена на использование пробела. Её можно установить так, как нужно для целей вывода данных:
$ awk 'BEGIN{FS=":"; OFS="-"} {print $1,$6,$7}' /etc/passwd
Установка разделителя полей выходного потока
ПеременнаяFIELDWIDTHS
позволяет читать записи без использования символа-разделителя полей.
В некоторых случаях, вместо использования разделителя полей, данные в пределах записей расположены в колонках постоянной ширины. В подобных случаях необходимо задать переменнуюFIELDWIDTHS
таким образом, чтобы её содержимое соответствовало особенностям представления данных.
При установленной переменнойFIELDWIDTHS
awk будет игнорировать переменнуюFS
и находить поля данных в соответствии со сведениями об их ширине, заданными вFIELDWIDTHS
.
Предположим, имеется файлtestfile
, содержащий такие данные:
1235.9652147.91
927-8.365217.27
36257.8157492.5
Известно, что внутренняя организация этих данных соответствует шаблону 3-5-2-5, то есть, первое поле имеет ширину 3 символа, второе — 5, и так далее. Вот скрипт, который позволит разобрать такие записи:
$ awk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' testfile
Использование переменной FIELDWIDTHS
Посмотрим на то, что выведет скрипт. Данные разобраны с учётом значения переменнойFIELDWIDTHS
, в результате числа и другие символы в строках разбиты в соответствии с заданной шириной полей.
ПеременныеRS
иORS
задают порядок обработки записей. По умолчаниюRS
иORS
установлены на символ перевода строки. Это означает, что awk воспринимает каждую новую строку текста как новую запись и выводит каждую запись с новой строки.
Иногда случается так, что поля в потоке данных распределены по нескольким строкам. Например, пусть имеется такой файл с именемaddresses
:
Person Name
123 High Street
(222) 466-1234
Another person
487 High Street
(523) 643-8754
Если попытаться прочесть эти данные при условии, чтоFS
иRS
установлены в значения по умолчанию, awk сочтёт каждую новую строку отдельной записью и выделит поля, опираясь на пробелы. Это не то, что нам в данном случае нужно.
Для того, чтобы решить эту проблему, вFS
надо записать символ перевода строки. Это укажет awk на то, что каждая строка в потоке данных является отдельным полем.
Кроме того, в данном примере понадобится записать в переменнуюRS
пустую строку. Обратите внимание на то, что в файле блоки данных о разных людях разделены пустой строкой. В результате awk будет считать пустые строки разделителями записей. Вот как всё это сделать:
$ awk 'BEGIN{FS="n"; RS=""} {print $1,$3}' addresses
Результаты настройки переменных RS и FS
Как видите, awk, благодаря таким настройкам переменных, воспринимает строки из файла как поля, а разделителями записей становятся пустые строки.
Встроенные переменные: сведения о данных и об окружении
Помимо встроенных переменных, о которых мы уже говорили, существуют и другие, которые предоставляют сведения о данных и об окружении, в котором работает awk:
ARGC
— количество аргументов командной строки.
ARGV
— массив с аргументами командной строки.
ARGIND
— индекс текущего обрабатываемого файла в массивеARGV
.
ENVIRON
— ассоциативный массив с переменными окружения и их значениями.
ERRNO
— код системной ошибки, которая может возникнуть при чтении или закрытии входных файлов.
FILENAME
— имя входного файла с данными.
FNR
— номер текущей записи в файле данных.
IGNORECASE
— если эта переменная установлена в ненулевое значение, при обработке игнорируется регистр символов.
NF
— общее число полей данных в текущей записи.
NR
— общее число обработанных записей.
ПеременныеARGC
иARGV
позволяют работать с аргументами командной строки. При этом скрипт, переданный awk, не попадает в массив аргументовARGV
. Напишем такой скрипт:
$ awk 'BEGIN{print ARGC,ARGV[1]}' myfile
После его запуска можно узнать, что общее число аргументов командной строки — 2, а под индексом 1 в массивеARGV
записано имя обрабатываемого файла. В элементе массива с индексом 0 в данном случае будет «awk».
Работа с параметрами командной строки
ПеременнаяENVIRON
представляет собой ассоциативный массив с переменными среды. Опробуем её:
$ awk '
BEGIN{
print ENVIRON["HOME"]
print ENVIRON["PATH"]
}'
Работа с переменными среды
Переменные среды можно использовать и без обращения кENVIRON
. Сделать это, например, можно так:
$ echo | awk -v home=$HOME '{print "My home is " home}'
Работа с переменными среды без использования ENVIRON
ПеременнаяNF
позволяет обращаться к последнему полю данных в записи, не зная его точной позиции:
$ awk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd
Пример использования переменной NF
Эта переменная содержит числовой индекс последнего поля данных в записи. Обратиться к данному полю можно, поместив передNF
знак$
.
ПеременныеFNR
иNR
, хотя и могут показаться похожими, на самом деле различаются. Так, переменнаяFNR
хранит число записей, обработанных в текущем файле. ПеременнаяNR
хранит общее число обработанных записей. Рассмотрим пару примеров, передав awk один и тот же файл дважды:
$ awk 'BEGIN{FS=","}{print $1,"FNR="FNR}' myfile myfile
Исследование переменной FNR
Передача одного и того же файла дважды равносильна передаче двух разных файлов. Обратите внимание на то, чтоFNR
сбрасывается в начале обработки каждого файла.
Взглянем теперь на то, как ведёт себя в подобной ситуации переменнаяNR
:
$ awk '
BEGIN {FS=","}
{print $1,"FNR="FNR,"NR="NR}
END{print "There were",NR,"records processed"}' myfile myfile
Различие переменных NR и FNR
Как видно,FNR
, как и в предыдущем примере, сбрасывается в начале обработки каждого файла, а вотNR
, при переходе к следующему файлу, сохраняет значение.
Пользовательские переменные
Как и любые другие языки программирования, awk позволяет программисту объявлять переменные. Имена переменных могут включать в себя буквы, цифры, символы подчёркивания. Однако, они не могут начинаться с цифры. Объявить переменную, присвоить ей значение и воспользоваться ей в коде можно так:
$ awk '
BEGIN{
test="This is a test"
print test
}'
Работа с пользовательской переменной
Условный оператор
Awk поддерживает стандартный во многих языках программирования формат условного оператораif-then-else
. Однострочный вариант оператора представляет собой ключевое словоif
, за которым, в скобках, записывают проверяемое выражение, а затем — команду, которую нужно выполнить, если выражение истинно.
Например, есть такой файл с именемtestfile
:
Напишем скрипт, который выводит числа из этого файла, большие 20:
$ awk '{if ($1 > 20) print $1}' testfile
Однострочный оператор if
Если нужно выполнить в блокеif
несколько операторов, их нужно заключить в фигурные скобки:
$ awk '{
if ($1 > 20)
{
x = $1 * 2
print x
}
}' testfile
Выполнение нескольких команд в блоке if
Как уже было сказано, условный оператор awk может содержать блокelse
:
$ awk '{
if ($1 > 20)
{
x = $1 * 2
print x
} else
{
x = $1 / 2
print x
}}' testfile
Условный оператор с блоком else
Ветвьelse
может быть частью однострочной записи условного оператора, включая в себя лишь одну строку с командой. В подобном случае после ветвиif
, сразу передelse
, надо поставить точку с запятой:
$ awk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' testfile
Условный оператор, содержащий ветви if и else, записанный в одну строку
Цикл while
Циклwhile
позволяет перебирать наборы данных, проверяя условие, которое остановит цикл.
Вот файлmyfile
, обработку которого мы хотим организовать с помощью цикла:
124 127 130
112 142 135
175 158 245
Напишем такой скрипт:
$ awk '{
total = 0
i = 1
while (i < 4)
{
total += $i
i++
}
avg = total / 3
print "Average:",avg
}' testfile
Обработка данных в цикле while
Циклwhile
перебирает поля каждой записи, накапливая их сумму в переменнойtotal
и увеличивая в каждой итерации на 1 переменную-счётчикi
. Когдаi
достигнет 4, условие на входе в цикл окажется ложным и цикл завершится, после чего будут выполнены остальные команды — подсчёт среднего значения для числовых полей текущей записи и вывод найденного значения.
В циклахwhile
можно использовать командыbreak
иcontinue
. Первая позволяет досрочно завершить цикл и приступить к выполнению команд, расположенных после него. Вторая позволяет, не завершая до конца текущую итерацию, перейти к следующей.
Вот как работает командаbreak
:
$ awk '{
total = 0
i = 1
while (i < 4)
{
total += $i
if (i == 2)
break
i++
}
avg = total / 2
print "The average of the first two elements is:",avg
}' testfile
Команда break в цикле while
Цикл for
Циклыfor
используются во множестве языков программировании. Поддерживает их и awk. Решим задачу расчёта среднего значения числовых полей с использованием такого цикла:
$ awk '{
total = 0
for (i = 1; i < 4; i++)
{
total += $i
}
avg = total / 3
print "Average:",avg
}' testfile
Цикл for
Начальное значение переменной-счётчика и правило её изменения в каждой итерации, а также условие прекращения цикла, задаются в начале цикла, в круглых скобках. В итоге нам не нужно, в отличие от случая с цикломwhile
, самостоятельно инкрементировать счётчик.
Форматированный вывод данных
Командаprintf
в awk позволяет выводить форматированные данные. Она даёт возможность настраивать внешний вид выводимых данных благодаря использованию шаблонов, в которых могут содержаться текстовые данные и спецификаторы форматирования.
Спецификатор форматирования — это специальный символ, который задаёт тип выводимых данных и то, как именно их нужно выводить. Awk использует спецификаторы форматирования как указатели мест вставки данных из переменных, передаваемыхprintf
.
Первый спецификатор соответствует первой переменной, второй спецификатор — второй, и так далее.
Спецификаторы форматирования записывают в таком виде:
%[modifier]control-letter
Вот некоторые из них:
c
— воспринимает переданное ему число как код ASCII-символа и выводит этот символ.
d
— выводит десятичное целое число.
i
— то же самое, что иd
.
e
— выводит число в экспоненциальной форме.
f
— выводит число с плавающей запятой.
g
— выводит число либо в экспоненциальной записи, либо в формате с плавающей запятой, в зависимости от того, как получается короче.
o
— выводит восьмеричное представление числа.
s
— выводит текстовую строку.
Вот как форматировать выводимые данные с помощьюprintf
:
$ awk 'BEGIN{
x = 100 * 100
printf "The result is: %en", x
}'
Форматирование выходных данных с помощью printf
Тут, в качестве примера, мы выводим число в экспоненциальной записи. Полагаем, этого достаточно для того, чтобы вы поняли основную идею, на которой построена работа сprintf
.
Встроенные математические функции
При работе с awk программисту доступнывстроенные функции. В частности, это математические и строковые функции, функции для работы со временем. Вот, например, список математических функций, которыми можно пользоваться при разработке awk-скриптов:
cos(x)
— косинусx
(x
выражено в радианах).
sin(x)
— синусx
.
exp(x)
— экспоненциальная функция.
int(x)
— возвращает целую часть аргумента.
log(x)
— натуральный логарифм.
rand()
— возвращает случайное число с плавающей запятой в диапазоне 0 — 1.
sqrt(x)
— квадратный корень изx
.
Вот как пользоваться этими функциями:
$ awk 'BEGIN{x=exp(5); print x}'
Работа с математическими функциями
Строковые функции
Awk поддерживает множествостроковых функций. Все они устроены более или менее одинаково. Вот, например, функцияtoupper
:
$ awk 'BEGIN{x = "likegeeks"; print toupper(x)}'
Использование строковой функции toupper
Эта функция преобразует символы, хранящиеся в переданной ей строковой переменной, к верхнему регистру.
Пользовательские функции
При необходимости вы можете создавать собственные функции awk. Такие функции можно использовать так же, как встроенные:
$ awk '
function myprint()
{
printf "The user %s has home path at %sn", $1,$6
}
BEGIN{FS=":"}
{
myprint()
}' /etc/passwd
Использование собственной функции
В примере используется заданная нами функцияmyprint
, которая выводит данные.
Bash-скрипты, часть 9: регулярные выражения
Что такое регулярные выражения
У многих, когда они впервые видят регулярные выражения, сразу же возникает мысль, что перед ними бессмысленное нагромождение символов. Но это, конечно, далеко не так. Взгляните, например, на это регулярное выражение
^([a-zA-Z0-9_-.+]+)@([a-zA-Z0-9_-.]+).([a-zA-Z]{2,5})$
На наш взгляд даже абсолютный новичок сходу поймёт, как оно устроено и зачем нужно Если же вам не вполне понятно — просто читайте дальше и всё встанет на свои места.
Регулярное выражение — это шаблон, пользуясь которым программы вроде sed или awk фильтруют тексты. В шаблонах используются обычные ASCII-символы, представляющие сами себя, и так называемые метасимволы, которые играют особую роль, например, позволяя ссылаться на некие группы символов.
Типы регулярных выражений
Реализации регулярных выражений в различных средах, например, в языках программирования вроде Java, Perl и Python, в инструментах Linux вроде sed, awk и grep, имеют определённые особенности. Эти особенности зависят от так называемых движков обработки регулярных выражений, которые занимаются интерпретацией шаблонов.
В Linux имеется два движка регулярных выражений:
- Движок, поддерживающий стандарт POSIX Basic Regular Expression (BRE).
- Движок, поддерживающий стандарт POSIX Extended Regular Expression (ERE).
Большинство утилит Linux соответствуют, как минимум, стандарту POSIX BRE, но некоторые утилиты (в их числе — sed) понимают лишь некое подмножество стандарта BRE. Одна из причин такого ограничения — стремление сделать такие утилиты как можно более быстрыми в деле обработки текстов.
Стандарт POSIX ERE часто реализуют в языках программирования. Он позволяет пользоваться большим количеством средств при разработке регулярных выражений. Например, это могут быть специальные последовательности символов для часто используемых шаблонов, вроде поиска в тексте отдельных слов или наборов цифр. Awk поддерживает стандарт ERE.
Существует много способов разработки регулярных выражений, зависящих и от мнения программиста, и от особенностей движка, под который их создают. Непросто писать универсальные регулярные выражения, которые сможет понять любой движок. Поэтому мы сосредоточимся на наиболее часто используемых регулярных выражениях и рассмотрим особенности их реализации для sed и awk.
Регулярные выражения POSIX BRE
Пожалуй, самый простой шаблон BRE представляет собой регулярное выражение для поиска точного вхождения последовательности символов в тексте. Вот как выглядит поиск строки в sed и awk:
$ echo "This is a test" | sed -n '/test/p'
$ echo "This is a test" | awk '/test/{print $0}'
Поиск текста по шаблону в sed
Поиск текста по шаблону в awk
Можно заметить, что поиск заданного шаблона выполняется без учёта точного места нахождения текста в строке. Кроме того, не имеет значение и количество вхождений. После того, как регулярное выражение найдёт заданный текст в любом месте строки, строка считается подходящей и передаётся для дальнейшей обработки.
Работая с регулярными выражениями нужно учитывать то, что они чувствительны к регистру символов:
$ echo "This is a test" | awk '/Test/{print $0}'
$ echo "This is a test" | awk '/test/{print $0}'
Регулярные выражения чувствительны к регистру
Первое регулярное выражение совпадений не нашло, так как слово «test», начинающееся с заглавной буквы, в тексте не встречается. Второе же, настроенное на поиск слова, написанного прописными буквами, обнаружило в потоке подходящую строку.
В регулярных выражениях можно использовать не только буквы, но и пробелы, и цифры:
$ echo "This is a test 2 again" | awk '/test 2/{print $0}'
Поиск фрагмента текста, содержащего пробелы и цифры
Пробелы воспринимаются движком регулярных выражений как обычные символы.
Специальные символы
При использовании различных символов в регулярных выражениях надо учитывать некоторые особенности. Так, существуют некоторые специальные символы, или метасимволы, использование которых в шаблоне требует особого подхода. Вот они:
Если один из них нужен в шаблоне, его нужно будет экранировать с помощью обратной косой черты (обратного слэша) —.
Например, если в тексте нужно найти знак доллара, его надо включить в шаблон, предварив символом экранирования. Скажем, имеется файлmyfile
с таким текстом:
There is 10$ on my pocket
Знак доллара можно обнаружить с помощью такого шаблона:
$ awk '/$/{print $0}' myfile
Использование в шаблоне специального символа
Кроме того, обратная косая черта — это тоже специальный символ, поэтому, если нужно использовать его в шаблоне, его тоже надо будет экранировать. Выглядит это как два слэша, идущих друг за другом:
$ echo " is a special character" | awk '/\/{print $0}'
Экранирование обратного слэша
Хотя прямой слэш и не входит в приведённый выше список специальных символов, попытка воспользоваться им в регулярном выражении, написанном для sed или awk, приведёт к ошибке:
$ echo "3 / 2" | awk '///{print $0}'
Неправильное использование прямого слэша в шаблоне
Если он нужен, его тоже надо экранировать:
$ echo "3 / 2" | awk '///{print $0}'
Экранирование прямого слэша
Якорные символы
Существуют два специальных символа для привязки шаблона к началу или к концу текстовой строки. Символ «крышка» —^
позволяет описывать последовательности символов, которые находятся в начале текстовых строк. Если искомый шаблон окажется в другом месте строки, регулярное выражение на него не отреагирует. Выглядит использование этого символа так:
$ echo "welcome to likegeeks website" | awk '/^likegeeks/{print $0}'
$ echo "likegeeks website" | awk '/^likegeeks/{print $0}'
Поиск шаблона в начале строки
Символ^
предназначен для поиска шаблона в начале строки, при этом регистр символов так же учитывается. Посмотрим, как это отразится на обработке текстового файла:
$ awk '/^this/{print $0}' myfile
Поиск шаблона в начале строки в тексте из файла
При использовании sed, если поместить крышку где-нибудь внутри шаблона, она будет восприниматься как любой другой обычный символ:
$ echo "This ^ is a test" | sed -n '/s ^/p'
Крышка, находящаяся не в начале шаблона в sed
В awk, при использовании такого же шаблона, данный символ надо экранировать:
$ echo "This ^ is a test" | awk '/s ^/{print $0}'
Крышка, находящаяся не в начале шаблона в awk
С поиском фрагментов текста, находящихся в начале строки мы разобрались. Что, если надо найти нечто, расположенное в конце строки?
В этом нам поможет знак доллара —$
, являющийся якорным символом конца строки:
$ echo "This is a test" | awk '/test$/{print $0}'
Поиск текста, находящегося в конце строки
В одном и том же шаблоне можно использовать оба якорных символа. Выполним обработку файлаmyfile
, содержимое которого показано на рисунке ниже, с помощью такого регулярного выражения:
$ awk '/^this is a test$/{print $0}' myfile
Шаблон, в котором использованы специальные символы начала и конца строки
Как видно, шаблон среагировал лишь на строку, полностью соответствующую заданной последовательности символов и их расположению.
Вот как, пользуясь якорными символами, отфильтровать пустые строки:
$ awk '!/^$/{print $0}' myfile
В данном шаблоне использовал символ отрицания, восклицательный знак —!
. Благодаря использованию такого шаблона выполняется поиск строк, не содержащих ничего между началом и концом строки, а благодаря восклицательному знаку на печать выводятся лишь строки, которые не соответствуют этому шаблону.
Символ «точка»
Точка используется для поиска любого одиночного символа, за исключением символа перевода строки. Передадим такому регулярному выражению файлmyfile
, содержимое которого приведено ниже:
$ awk '/.st/{print $0}' myfile
Использование точки в регулярных выражениях
Как видно по выведенным данным, шаблону соответствуют лишь первые две строки из файла, так как они содержат последовательность символов «st», предварённую ещё одним символом, в то время как третья строка подходящей последовательности не содержит, а в четвёртой она есть, но находится в самом начале строки.
Классы символов
Точка соответствует любому одиночному символу, но что если нужно более гибко ограничить набор искомых символов? В подобной ситуации можно воспользоваться классами символов.
Благодаря такому подходу можно организовать поиск любого символа из заданного набора. Для описания класса символов используются квадратные скобки —[]
:
$ awk '/[oi]th/{print $0}' myfile
Описание класса символов в регулярном выражении
Тут мы ищем последовательность символов «th», перед которой есть символ «o» или символ «i».
Классы оказываются очень кстати, если выполняется поиск слов, которые могут начинаться как с прописной, так и со строчной буквы:
$ echo "this is a test" | awk '/[Tt]his is a test/{print $0}'
$ echo "This is a test" | awk '/[Tt]his is a test/{print $0}'
Поиск слов, которые могут начинаться со строчной или прописной буквы
Классы символов не ограничены буквами. Тут можно использовать и другие символы. Нельзя заранее сказать, в какой ситуации понадобятся классы — всё зависит от решаемой задачи.
Отрицание классов символов
Классы символов можно использовать и для решения задачи, обратной описанной выше. А именно, вместо поиска символов, входящих в класс, можно организовать поиск всего, что в класс не входит. Для того, чтобы добиться такого поведения регулярного выражения, перед списком символов класса нужно поместить знак^
. Выглядит это так:
$ awk '/[^oi]th/{print $0}' myfile
Поиск символов, не входящих в класс
В данном случае будут найдены последовательности символов «th», перед которыми нет ни «o», ни «i».
Диапазоны символов
В символьных классах можно описывать диапазоны символов, используя тире:
$ awk '/[e-p]st/{print $0}' myfile
Описание диапазона символов в символьном классе
В данном примере регулярное выражение реагирует на последовательность символов «st», перед которой находится любой символ, расположенный, в алфавитном порядке, между символами «e» и «p».
Диапазоны можно создавать и из чисел:
$ echo "123" | awk '/[0-9][0-9][0-9]/'
$ echo "12a" | awk '/[0-9][0-9][0-9]/'
Регулярное выражение для поиска трёх любых чисел
В класс символов могут входить несколько диапазонов:
$ awk '/[a-fm-z]st/{print $0}' myfile
Класс символов, состоящий из нескольких диапазонов
Данное регулярное выражение найдёт все последовательности «st», перед которыми есть символы из диапазоновa-f
иm-z
.
Специальные классы символов
В BRE имеются специальные классы символов, которые можно использовать при написании регулярных выражений:
[[:alpha:]]
— соответствует любому алфавитному символу, записанному в верхнем или нижнем регистре.[[:alnum:]]
— соответствует любому алфавитно-цифровому символу, а именно — символам в диапазонах0-9
,A-Z
,a-z
.[[:blank:]]
— соответствует пробелу и знаку табуляции.[[:digit:]]
— любой цифровой символ от0
до9
.[[:upper:]]
— алфавитные символы в верхнем регистре —A-Z
.[[:lower:]]
— алфавитные символы в нижнем регистре —a-z
.[[:print:]]
— соответствует любому печатаемому символу.[[:punct:]]
— соответствует знакам препинания.[[:space:]]
— пробельные символы, в частности — пробел, знак табуляции, символыNL
,FF
,VT
,CR
.
Использовать специальные классы в шаблонах можно так:
$ echo "abc" | awk '/[[:alpha:]]/{print $0}'
$ echo "abc" | awk '/[[:digit:]]/{print $0}'
$ echo "abc123" | awk '/[[:digit:]]/{print $0}'
Специальные классы символов в регулярных выражениях
Символ «звёздочка»
Если в шаблоне после символа поместить звёздочку, это будет означать, что регулярное выражение сработает, если символ появляется в строке любое количество раз — включая и ситуацию, когда символ в строке отсутствует.
$ echo "test" | awk '/tes*t/{print $0}'
$ echo "tessst" | awk '/tes*t/{print $0}'
Использование символа * в регулярных выражениях
Этот шаблонный символ обычно используют для работы со словами, в которых постоянно встречаются опечатки, или для слов, допускающих разные варианты корректного написания:
$ echo "I like green color" | awk '/colou*r/{print $0}'
$ echo "I like green colour " | awk '/colou*r/{print $0}'
Поиск слова, имеющего разные варианты написания
В этом примере одно и то же регулярное выражение реагирует и на слово «color», и на слово «colour». Это так благодаря тому, что символ «u», после которого стоит звёздочка, может либо отсутствовать, либо встречаться несколько раз подряд.
Ещё одна полезная возможность, вытекающая из особенностей символа звёздочки, заключается в комбинировании его с точкой. Такая комбинация позволяет регулярному выражению реагировать на любое количество любых символов:
$ awk '/this.*test/{print $0}' myfile
Шаблон, реагирующий на любое количество любых символов
В данном случае неважно сколько и каких символов находится между словами «this» и «test».
Звёздочку можно использовать и с классами символов:
$ echo "st" | awk '/s[ae]*t/{print $0}'
$ echo "sat" | awk '/s[ae]*t/{print $0}'
$ echo "set" | awk '/s[ae]*t/{print $0}'
Использование звёздочки с классами символов
Во всех трёх примерах регулярное выражение срабатывает, так как звёздочка после класса символов означает, что если будет найдено любое количество символов «a» или «e», а также если их найти не удастся, строка будет соответствовать заданному шаблону.
Регулярные выражения POSIX ERE
Шаблоны стандарта POSIX ERE, которые поддерживают некоторые утилиты Linux, могут содержать дополнительные символы. Как уже было сказано, awk поддерживает этот стандарт, а вот sed — нет.
Тут мы рассмотрим наиболее часто используемые в ERE-шаблонах символы, которые пригодятся вам при создании собственных регулярных выражений.
Вопросительный знак
Вопросительный знак указывает на то, что предшествующий символ может встретиться в тексте один раз или не встретиться вовсе. Этот символ — один из метасимволов повторений. Вот несколько примеров:
$ echo "tet" | awk '/tes?t/{print $0}'
$ echo "test" | awk '/tes?t/{print $0}'
$ echo "tesst" | awk '/tes?t/{print $0}'
Вопросительный знак в регулярных выражениях
Как видно, в третьем случае буква «s» встречается дважды, поэтому на слово «tesst» регулярное выражение не реагирует.
Вопросительный знак можно использовать и с классами символов:
$ echo "tst" | awk '/t[ae]?st/{print $0}'
$ echo "test" | awk '/t[ae]?st/{print $0}'
$ echo "tast" | awk '/t[ae]?st/{print $0}'
$ echo "taest" | awk '/t[ae]?st/{print $0}'
$ echo "teest" | awk '/t[ae]?st/{print $0}'
Вопросительный знак и классы символов
Если символов из класса в строке нет, или один из них встречается один раз, регулярное выражение срабатывает, однако стоит в слове появиться двум символам и система уже не находит в тексте соответствия шаблону.
Символ «плюс»
Символ «плюс» в шаблоне указывает на то, что регулярное выражение обнаружит искомое в том случае, если предшествующий символ встретится в тексте один или более раз. При этом на отсутствие символа такая конструкция реагировать не будет:
$ echo "test" | awk '/te+st/{print $0}'
$ echo "teest" | awk '/te+st/{print $0}'
$ echo "tst" | awk '/te+st/{print $0}'
Символ «плюс» в регулярных выражениях
В данном примере, если символа «e» в слове нет, движок регулярных выражений не найдёт в тексте соответствий шаблону. Символ «плюс» работает и с классами символов — этим он похож на звёздочку и вопросительный знак:
$ echo "tst" | awk '/t[ae]+st/{print $0}'
$ echo "test" | awk '/t[ae]+st/{print $0}'
$ echo "teast" | awk '/t[ae]+st/{print $0}'
$ echo "teeast" | awk '/t[ae]+st/{print $0}'
Знак «плюс» и классы символов
В данном случае если в строке имеется любой символ из класса, текст будет сочтён соответствующим шаблону.
Фигурные скобки
Фигурные скобки, которыми можно пользоваться в ERE-шаблонах, похожи на символы, рассмотренные выше, но они позволяют точнее задавать необходимое число вхождений предшествующего им символа. Указывать ограничение можно в двух форматах:
-
n —
число, задающее точное число искомых вхождений -
n, m —
два числа, которые трактуются так: «как минимум n раз, но не больше чем m».
Вот примеры первого варианта:
$ echo "tst" | awk '/te{1}st/{print $0}'
$ echo "test" | awk '/te{1}st/{print $0}'
Фигурные скобки в шаблонах, поиск точного числа вхождений
В старых версиях awk нужно было использовать ключ командной строки--re-interval
для того, чтобы программа распознавала интервалы в регулярных выражениях, но в новых версиях этого делать не нужно.
$ echo "tst" | awk '/te{1,2}st/{print $0}'
$ echo "test" | awk '/te{1,2}st/{print $0}'
$ echo "teest" | awk '/te{1,2}st/{print $0}'
$ echo "teeest" | awk '/te{1,2}st/{print $0}'
Интервал, заданный в фигурных скобках
В данном примере символ «e» должен встретиться в строке 1 или 2 раза, тогда регулярное выражение отреагирует на текст.
Фигурные скобки можно применять и с классами символов. Тут действуют уже знакомые вам принципы:
$ echo "tst" | awk '/t[ae]{1,2}st/{print $0}'
$ echo "test" | awk '/t[ae]{1,2}st/{print $0}'
$ echo "teest" | awk '/t[ae]{1,2}st/{print $0}'
$ echo "teeast" | awk '/t[ae]{1,2}st/{print $0}'
Фигурные скобки и классы символов
Шаблон отреагирует на текст в том случае, если в нём один или два раза встретится символ «a» или символ «e».
Символ логического «или»
Символ|
— вертикальная черта, означает в регулярных выражениях логическое «или». Обрабатывая регулярное выражение, содержащее несколько фрагментов, разделённых таким знаком, движок сочтёт анализируемый текст подходящим в том случае, если он будет соответствовать любому из фрагментов. Вот пример:
$ echo "This is a test" | awk '/test|exam/{print $0}'
$ echo "This is an exam" | awk '/test|exam/{print $0}'
$ echo "This is something else" | awk '/test|exam/{print $0}'
Логическое «или» в регулярных выражениях
В данном примере регулярное выражение настроено на поиск в тексте слов «test» или «exam». Обратите внимание на то, что между фрагментами шаблона и разделяющим их символом|
не должно быть пробелов.
Группировка фрагментов регулярных выражений
Фрагменты регулярных выражений можно группировать, пользуясь круглыми скобками. Если сгруппировать некую последовательность символов, она будет восприниматься системой как обычный символ. То есть, например, к ней можно будет применить метасимволы повторений. Вот как это выглядит:
$ echo "Like" | awk '/Like(Geeks)?/{print $0}'
$ echo "LikeGeeks" | awk '/Like(Geeks)?/{print $0}'
Группировка фрагментов регулярных выражений
В данных примерах слово «Geeks» заключено в круглые скобки, после этой конструкции идёт знак вопроса. Напомним, что вопросительный знак означает «0 или 1 повторение», в результате регулярное выражение отреагирует и на строку «Like», и на строку «LikeGeeks».
Практические примеры
После того, как мы разобрали основы регулярных выражений, пришло время сделать с их помощью что-нибудь полезное.
Подсчёт количества файлов
Напишем bash-скрипт, который подсчитывает файлы, находящиеся в директориях, которые записаны в переменную окруженияPATH
. Для того, чтобы это сделать, понадобится, для начала, сформировать список путей к директориям. Сделаем это с помощью sed, заменив двоеточия на пробелы:
$ echo $PATH | sed 's/:/ /g'
Команда замены поддерживает регулярные выражения в качестве шаблонов для поиска текста. В данном случае всё предельно просто, ищем мы символ двоеточия, но никто не мешает использовать здесь и что-нибудь другое — всё зависит от конкретной задачи.
Теперь надо пройтись по полученному списку в цикле и выполнить там необходимые для подсчёта количества файлов действия. Общая схема скрипта будет такой:
mypath=$(echo $PATH | sed 's/:/ /g') for directory in $mypath do done
Теперь напишем полный текст скрипта, воспользовавшись командойls
для получения сведений о количестве файлов в каждой из директорий:
#!/bin/bash mypath=$(echo $PATH | sed 's/:/ /g') count=0 for directory in $mypath do check=$(ls $directory) for item in $check do count=$[ $count + 1 ] done echo "$directory - $count" count=0 done
При запуске скрипта может оказаться, что некоторых директорий изPATH
не существует, однако, это не помешает ему посчитать файлы в существующих директориях.
Подсчёт файлов
Главная ценность этого примера заключается в том, что пользуясь тем же подходом, можно решать и куда более сложные задачи. Какие именно — зависит от ваших потребностей.
Проверка адресов электронной почты
Существуют веб-сайты с огромными коллекциями регулярных выражений, которые позволяют проверять адреса электронной почты, телефонные номера, и так далее. Однако, одно дело — взять готовое, и совсем другое — создать что-то самому. Поэтому напишем регулярное выражение для проверки адресов электронной почты. Начнём с анализа исходных данных. Вот, например, некий адрес:
Имя пользователя,username
, может состоять из алфавитно-цифровых и некоторых других символов. А именно, это точка, тире, символ подчёркивания, знак «плюс». За именем пользователя следует знак @.
Вооружившись этими знаниями, начнём сборку регулярного выражения с его левой части, которая служит для проверки имени пользователя. Вот что у нас получилось:
Это регулярное выражение можно прочитать так: «В начале строки должен быть как минимум один символ из тех, которые имеются в группе, заданной в квадратных скобках, а после этого должен идти знак @».
Теперь — очередь имени хоста —hostname
. Тут применимы те же правила, что и для имени пользователя, поэтому шаблон для него будет выглядеть так:
Имя домена верхнего уровня подчиняется особым правилам. Тут могут быть лишь алфавитные символы, которых должно быть не меньше двух (например, такие домены обычно содержат код страны), и не больше пяти. Всё это значит, что шаблон для проверки последней части адреса будет таким:
Прочесть его можно так: «Сначала должна быть точка, потом — от 2 до 5 алфавитных символов, а после этого строка заканчивается».
Подготовив шаблоны для отдельных частей регулярного выражения, соберём их вместе:
^([a-zA-Z0-9_-.+]+)@([a-zA-Z0-9_-.]+).([a-zA-Z]{2,5})$
Теперь осталось лишь протестировать то, что получилось:
$ echo "name@host.com" | awk '/^([a-zA-Z0-9_-.+]+)@([a-zA-Z0-9_-.]+).([a-zA-Z]{2,5})$/{print $0}'
$ echo "name@host.com.us" | awk '/^([a-zA-Z0-9_-.+]+)@([a-zA-Z0-9_-.]+).([a-zA-Z]{2,5})$/{print $0}'
Проверка адреса электронной почты с помощью регулярных выражений
То, что переданный awk текст выводится на экран, означает, что система распознала в нём адрес электронной почты.
Bash-скрипты, часть 10: практические примеры
Отправка сообщений в терминал пользователя
В наши дни редко кто прибегает к одной из возможностей Linux, которая позволяет общаться, отправляя сообщения в терминалы пользователей, вошедших в систему. Сама по себе команда отправки сообщений,write
, довольно проста. Для того, чтобы ей воспользоваться, достаточно знать имя пользователя и имя его терминала. Однако, для успешной отправки сообщения, помимо актуальных данных о пользователе и терминале, надо знать, вошёл ли пользователь в систему, не запретил ли он запись в свой терминал. В результате, перед отправкой сообщения нужно выполнить несколько проверок.
Как видите, задача: «отправить сообщение», при ближайшем рассмотрении, оказалась задачей: «проверить возможность отправки сообщения, и, если нет препятствий, отправить его». Займёмся решением задачи, то есть — разработкой bash-скрипта.
Команды who и mesg
Ядром скрипта являются несколько команд, которые мы ещё не обсуждали. Всё остальное должно быть вам знакомо по предыдущим материалам.
Первое, что нам тут понадобится — командаwho
. Она позволяет узнать сведения о пользователях, работающих в системе. В простейшем виде её вызов выглядит так:
Результаты вызова команды who
В каждой строчке, которую выводит командаwho
, нас интересуют первых два показателя — имя пользователя и сведения о его терминале.
По умолчанию запись в терминал разрешена, но пользователь может, с помощью командыmesg
, запретить отправку ему сообщений. Таким образом, прежде чем пытаться что-то кому-то отправить, неплохо будет проверить, разрешена ли отправка сообщений. Если нужно узнать собственный статус, достаточно ввести командуmesg
без параметров:
Команда mesg
В данном случае команда вывела «is y», это значит, что пользователь, под которым мы работаем в системе, может принимать сообщения, отправленные в его терминал. В противном случаеmesg
выведет «is n».
Для проверки того, разрешена ли отправка сообщений какому-то другому пользователю, можно использовать уже знакомую вам командуwho
с ключом-T
:
При этом проверка возможна только для пользователей, которые вошли в систему. Если такая команда, после имени пользователя, выведет чёрточку (-), это означает, что пользователь запретил запись в свой терминал, то есть, сообщения ему отправлять нельзя. О том, что пользователю можно отправлять сообщения, говорит знак «плюс» (+).
Если у вас приём сообщений отключён, а вы хотите позволить другим пользователям отправлять вам сообщения, можно воспользоваться такой командой:
Включение приёма сообщений от других пользователей
После включения приёма сообщенийmesg
возвращает «is y».
Конечно, для обмена сообщениями нужны два пользователя, поэтому мы, после обычного входа в систему, подключились к компьютеру по ssh. Теперь можно поэкспериментировать.
Команда write
Основной инструмент для обмена сообщениями между пользователями, вошедшими в систему — командаwrite
. Если приём сообщений у пользователя разрешён, с помощью этой команды ему можно отправлять сообщения, используя его имя и сведения о терминале.
Обратите внимание на то, что с помощьюwrite
можно отправлять сообщения пользователям, вошедшим в виртуальную консоль. Пользователи, которые работают в графическом окружении (KDE, Gnome, Cinnamon, и так далее), не могут получать подобные сообщения.
Итак, мы, работая под пользователемlikegeeks
, инициируем сеанс связи с пользователемtestuser
, который работает в терминалеpts/1
, следующим образом:
Проверка возможности отправки сообщений и отправка сообщения
После выполнения вышеуказанной команды перед нами окажется пустая строка, в которую нужно ввести первую строку сообщения. Нажав клавишуENTER
, мы можем ввести следующую строку сообщения. После того, как ввод текста завершён, окончить сеанс связи можно, воспользовавшись комбинацией клавишCTRL + D
, которая позволяет ввестисимвол конца файла.
Вот что увидит в своём терминале пользователь, которому мы отправили сообщение.
Новое сообщение, пришедшее в терминал
Получатель может понять от кого пришло сообщение, увидеть время, когда оно было отправлено. Обратите внимание на признак конца файла,EOF
, расположенный в нижней части окна терминала. Он указывает на окончание текста сообщения.
Полагаем, теперь у нас есть всё необходимое для того, чтобы автоматизировать отправку сообщений с помощью сценария командной строки.
Создание скрипта для отправки сообщений
Прежде чем заниматься отправкой сообщений, нужно определить, вошёл ли интересующий нас пользователь в систему. Сделать это можно с помощью такой команды:
logged_on=$(who | grep -i -m 1 $1 | awk '{print $1}')
Здесь результаты работы командыwho
передаются командеgrep
. Ключ-i
этой команды позволяет игнорировать регистр символов. Ключ-m 1
включён в вызов команды на тот случай, если пользователь вошёл в систему несколько раз. Эта команда либо не выведет ничего, либо выведет имя пользователя (его мы укажем при вызове скрипта, оно попадёт в позиционную переменную$1
), соответствующее первому найденному сеансу. Выводgrep
мы передаёмawk
. Эта команда, опять же, либо не выведет ничего, либо выведет элемент, записанный в собственную переменную$1
, то есть — имя пользователя. В итоге то, что получилось, попадает в переменнуюlogged_on
.
Теперь надо проверить переменную logged_on
, посмотреть, есть ли в ней что-нибудь:
if [ -z $logged_on ] then echo "$1 is not logged on." echo "Exit" exit fi
Если вы не вполне уверенно чувствуете себя, работая с конструкциейif
, взгляните наэтотматериал.
Скрипт, содержащий вышеописанный код, сохраним в файлеsenderscript
и вызовем, передав ему, в качестве параметра командной строки, имя пользователяtestuser
.
Проверка статуса пользователя
Тут мы проверяем, является лиlogged_on
переменной с нулевой длиной. Если это так, нам сообщат о том, что в данный момент пользователь в систему не вошёл и скрипт завершит работу с помощью командыexit
. В противном случае выполнение скрипта продолжится.
Проверка возможности записи в терминал пользователя
Теперь надо проверить, принимает ли пользователь сообщения. Для этого понадобится такая конструкция, похожая на ту, которую мы использовали выше:
allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}') if [ $allowed != "+" ] then echo "$1 does not allowing messaging." echo "Exit" exit fi
Проверка возможности отправки сообщений пользователю
Сначала мы вызываем командуwho
с ключом-T
. В строке сведений о пользователе, который может принимать сообщения, окажется знак «плюс» (+), если же пользователь принимать сообщения не может — там будет чёрточка (-). То, что получилось после вызоваwho
, передаётсяgrep
, а потом —awk
, формируя переменнуюallowed
.
Далее, используя условный оператор, мы проверяем то, что оказалось в переменнойallowed
. Если знака «плюс» в ней нет, сообщим о том, что отправка сообщений пользователю запрещена и завершим работу. В противном случае выполнение сценария продолжится.
Проверка правильности вызова скрипта
Первым параметром скрипта является имя пользователя, которому мы хотим отправить сообщение. Вторым — текст сообщения, в данном случае — состоящий из одного слова. Для того, чтобы проверить, передано ли скрипту сообщение для отправки, воспользуемся таким кодом:
if [ -z $2 ] then echo "No message parameter included." echo "Exit" exit fi
Проверка параметров командной строки, указанных при вызове скрипта
Тут, если при вызове скрипта ему не было передано сообщение для отправки, мы сообщаем об этом и завершаем работу. В противном случае — идём дальше.
Получение сведений о терминале пользователя
Прежде чем отправить пользователю сообщение, нужно получить сведения о терминале, в котором он работает и сохранить имя терминала в переменной. Делается это так:
terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
Теперь, после того, как все необходимые данные собраны, осталось лишь отправить сообщение:
echo $2 | write $logged_on $terminal
Вызов готового скрипта выглядит так:
$ ./senderscript testuser welcome
Успешная отправка сообщения с помощью bash-скрипта
Как видно, всё работает как надо. Однако, с помощью такого сценария можно отправлять лишь сообщения, состоящие из одного слова. Хорошо бы получить возможность отправлять более длинные сообщения.
Отправка длинных сообщений
Попробуем вызвать сценарийsenderscript
, передав ему сообщение, состоящее из нескольких слов:
$ ./senderscript likegeeks welcome to shell scripting
Попытка отправки длинного сообщения
Как видно, отправлено было лишь первое слово. Всё дело в том, что каждое слово сообщения воспринимается внутри скрипта как отдельная позиционная переменная. Для того, чтобы получить возможность отправки длинных сообщений, обработаем параметры командной строки, переданные сценарию, воспользовавшиськомандойshift
и цикломwhile
.
shift while [ -n "$1" ] do whole_message=$whole_message' '$1 shift done
После этого, в команде отправки сообщения, воспользуемся, вместо применяемой ранее позиционной переменной$2
, переменнойwhole_message
:
echo $whole_message | write $logged_on $terminal
Вот полный текст сценария:
#!/bin/bash logged_on=$(who | grep -i -m 1 $1 | awk '{print $1}') if [ -z $logged_on ] then echo "$1 is not logged on." echo "Exit" exit fi allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}') if [ $allowed != "+" ] then echo "$1 does not allowing messaging." echo "Exit" exit fi if [ -z $2 ] then echo "No message parameter included." echo "Exit" exit fi terminal=$(who | grep -i -m 1 $1 | awk '{print $2}') shift while [ -n "$1" ] do whole_message=$whole_message' '$1 shift done echo $whole_message | write $logged_on $terminal
Испытаем его:
$ ./senderscript likegeeks welcome to shell scripting
Успешная отправка длинного сообщения:
Длинное сообщение успешно дошло до адресата. Теперь рассмотрим следующий пример.
Скрипт для мониторинга дискового пространства
Сейчас мы собираемся создать сценарий командной строки, который предназначен для поиска в заданных директориях первой десятки папок, на которые приходится больше всего дискового пространства. В этом нам поможеткомандаdu
, которая выводит сведения о том, сколько места на диске занимают файлы и папки. По умолчанию она выводит сведения лишь о директориях, с ключом-a
в отчёт попадают и отдельные файлы. Её ключ-s
позволяет вывести сведения о размерах директорий. Эта команда позволяет, например, узнать объём дискового пространства, который занимают данные некоего пользователя. Вот как выглядит вызов этой команды:
Для наших целей лучше подойдёт ключ-S
(заглавная S), так как он позволяет получить сведения как по корневой папке, так и по вложенным в неё директориям:
Вызов команды du с ключами -s и -S
Нам нужно найти директории, на которые приходится больше всего дискового пространства, поэтому список, который выдаётdu
, надо отсортировать, воспользовавшись командойsort
:
$ du -S /var/log/ | sort -rn
Отсортированный список объектов
Ключ-n
указывает команде на то, что нужна числовая сортировка, ключ-r —
на обратный порядок сортировки (самое большое число окажется в начале списка). Полученные данные вполне подходят для наших целей.
Для того, чтобы ограничить полученный список первыми десятью записями, воспользуемсяпотоковым редакторомsed
, который позволит удалить из полученного списка все строки, начиная с одиннадцатой. Следующий шаг — добавить к каждой полученной строке её номер. Тут также поможетsed
, а именно — его командаN
:
sed '{11,$D; =}' |
sed 'N; s/n/ /' |
Приведём полученные данные в порядок, воспользовавшисьawk
. Передадимawk
то, что получилось после обработки данных с помощьюsed
, применив, как и в других случаях, конвейер, и выведем полученные данные с помощью командыprintf
:
awk '{printf $1 ":" "t" $2 "t" $3 "n"}'
В начале строки выводится её номер, потом идёт двоеточие и знак табуляции, далее — объём дискового пространства, следом — ещё один знак табуляции и имя папки.
Соберём вместе всё то, о чём мы говорили:
$ du -S /var/log/ |
sort -rn |
sed '{11,$D; =}' |
sed 'N; s/n/ /' |
awk '{printf $1 ":" "t" $2 "t" $3 "n"}'
Вывод сведений о дисковом пространстве
Для того, чтобы повысить эффективность работы скрипта, код которого вы совсем скоро увидите, реализуем возможность получения данных сразу по нескольким директориям. Для этого создадим переменнуюMY_DIRECTORIES
и внесём в неё список интересующих нас директорий:
MY_DIRECTORIES="/home /var/log"
Переберём список с помощью циклаfor
и вызовем вышеописанную последовательность команд для каждого элемента списка. Вот что получилось в результате:
#!/bin/bash MY_DIRECTORIES="/home /var/log" echo "Top Ten Disk Space Usage" for DIR in $MY_DIRECTORIES do echo "The $DIR Directory:" du -S $DIR 2>/dev/null | sort -rn | sed '{11,$D; =}' | sed 'N; s/n/ /' | awk '{printf $1 ":" "t" $2 "t" $3 "n"}' done exit
Получение сведений о нескольких директориях
Как видите, скрипт выводит, в виде удобного списка, сведения о директориях, список которых хранится вMY_DIRECTORIES
.
Командуdu
в этом скрипте можно вызвать с другими ключами, полученный список объектов вполне можно отфильтровать, в целом — тут открывается широкий простор для самостоятельных экспериментов. В результате, вместо работы со списком папок, можно, например, найти самые большие файлы с расширением.log,
или реализовать более сложный алгоритм поиска самых больших (или самых маленьких) файлов и папок.
Bash-скрипты, часть 11: expect и автоматизация интерактивных утилит
Основы expect
Если expect в вашей системе не установлен, исправить это, например, в Ubuntu, можно так:
В чём-то вроде CentOs установка выполняется такой командой:
Expect предоставляет набор команд, позволяющих взаимодействовать с утилитами командной строки. Вот его основные команды:
-
spawn
— запуск процесса или программы. Например, это может быть командная оболочка,FTP, Telnet, ssh, scp и так далее. -
expect
— ожидание данных, выводимых программой. При написании скрипта можно указать, какого именно вывода он ждёт и как на него нужно реагировать. -
send
— отправка ответа. Expect-скрипт с помощью этой команды может отправлять входные данные автоматизируемой программе. Она похожа на знакомую вам командуecho
в обычных bash-скриптах. -
interact
— позволяет переключиться на «ручной» режим управления программой.
Автоматизация bash-скрипта
Напишем скрипт, который взаимодействует с пользователем и автоматизируем его с помощью expect. Вот код bash-скриптаquestions
:
#!/bin/bash echo "Hello, who are you?" read $REPLY echo "Can I ask you some questions?" read $REPLY echo "What is your favorite topic?" read $REPLY
Теперь напишем expect-скрипт, который запустит скриптquestions
и будет отвечать на его вопросы:
#!/usr/bin/expect -f
set timeout -1
spawn ./questions
expect "Hello, who are you?r"
send -- "Im Adamr"
expect "Can I ask you some questions?r"
send -- "Surer"
expect "What is your favorite topic?r"
send -- "Technologyr"
expect eof
Сохраним скрипт, дав ему имяanswerbot
.
В начале скрипта находится строка идентификации, которая, в данном случае, содержит путь к expect, так как интерпретировать скрипт будет именно expect.
Во второй строке мы отключаем тайм-аут, устанавливая переменную expecttimeout
в значение -1. Остальной код — это и есть автоматизация работы с bash-скриптом.
Сначала, с помощью командыspawn
, мы запускаем bash-скрипт. Естественно, тут может быть вызвана любая другая утилита командной строки. Далее задана последовательность вопросов, поступающих от bash-скрипта, и ответов, которые даёт на них expect. Получив вопрос от подпроцесса, expect выдаёт ему заданный ответ и ожидает следующего вопроса.
В последней команде expect ожидает признака конца файла, скрипт, дойдя до этой команды, завершается.
Теперь пришло время всё это опробовать. Сделаемanswerbot
исполняемым файлом:
И вызовем его:
Expect-скрипт отвечает на вопросы bash-скрипта
Как видно, expect-скрипт верно ответил на вопросы bash-скрипта. Если на данном этапе вы столкнулись с ошибкой, вызванной тем, что неправильно указано расположение expect, выяснить его адрес можно так:
Обратите внимание на то, что после запуска скриптаanswerbot
всё происходит в полностью автоматическом режиме. То же самое можно проделать для любой утилиты командной строки. Тут надо отметить, что наш bash-скрипт устроен очень просто, мы точно знаем, какие именно данные он выводит, поэтому написать expect-скрипт для взаимодействия с ним несложно. Задача усложняется при работе с программами, которые написаны другими разработчиками. Однако, здесь на помощь приходит средство для автоматизированного создания expect-скриптов.
Autoexpect — автоматизированное создание expect-скриптов
Autoexpect позволяет запускать программы, которые надо автоматизировать, после чего записывает то, что они выводят, и то, что пользователь вводит, отвечая на их вопросы. Вызовем autoexpect, передав этой утилите имя нашего скрипта:
В этом режиме взаимодействие с bash-скриптом ничем не отличается от обычного: мы сами вводим ответы на его вопросы.
Запуск bash-скрипта с помощью autoexpect
После завершения работы с bash-скриптом, autoexpect сообщит о том, что собранные данные записаны в файлscript.exp
. Взглянем на этот файл.
Файл script.exp
В целом, за исключением некоторых деталей, перед нами такой же скрипт, который мы писали самостоятельно. Если запустить этот скрипт, результат будет тем же.
Запуск expect-скрипта, созданного автоматически
При записи сеансов взаимодействия с некоторыми программами, вроде FTP-клиентов, вы можете столкнуться с тем, что они используют в выводимых данных сведения о времени проведения операции, или выводят данные, отражающие процесс выполнения неких продолжительных действий. В целом, речь идёт о том, что вывод программы при каждом её запуске, правильно воспринимаемый человеком и вызывающий ввод одних и тех же ответов, будет, в тех же условиях, выглядеть по-новому для expect.
Если в expect-скрипте строки, ожидаемые от такой программы, будут жёстко зафиксированы, такой скрипт не сможет нормально работать. Справиться с этим можно, либо удалив из expect-скрипта данные, которые выглядят по-новому при каждом запуске программы, либо использовав шаблоны, пользуясь которыми, expect сможет правильно понять то, что хочет от него программа.
Как видите, autoexpect — это весьма полезный инструмент, но и он не лишён недостатков, исправить которые можно только вручную. Поэтому продолжим осваивать язык expect-скриптов.
Работа с переменными и параметрами командной строки
Для объявления переменных в expect-скриптах используется командаset
. Например, для того, чтобы присвоить значение 5 переменнойVAR1
, используется следующая конструкция:
Для доступа к значению переменной перед её именем надо добавить знак доллара —$
. В нашем случае это будет выглядеть как$VAR1
.
Для того, чтобы получить доступ к аргументам командной строки, с которыми вызван expect-скрипт, можно поступить так:
Тут мы объявляем переменнуюVAR
и записываем в неё указатель на первый аргумент командной строки,$argv 0
.
Для целей обновлённого expect-скрипта мы собираемся записать значение первого аргумента, представляющее собой имя пользователя, которое будет использовано в программе, в переменнуюmy_name
. Второй аргумент, символизирующий то, что пользователю нравится, попадёт в переменнуюmy_favorite
. В результате объявление переменных будет выглядеть так:
set my_name [lindex $argv 0]
set my_favorite [lindex $argv 1]
Отредактируем скриптanswerbot
, приведя его к такому виду:
#!/usr/bin/expect -f
set my_name [lindex $argv 0]
set my_favorite [lindex $argv 1]
set timeout -1
spawn ./questions
expect "Hello, who are you?r"
send -- "Im $my_namer"
expect "Can I ask you some questions?r"
send -- "Surer"
expect "What is your favorite topic?r"
send -- "$my_favoriter"
expect eof
Запустим его, передав в качестве первого параметраSomeName
, в качестве второго —Programming
:
$ ./answerbot SomeName Programming
Expect-скрипт, использующий переменные и параметры командной строки
Как видите, всё работает так, как ожидалось. Теперь expect-скрипт отвечает на вопросы bash-скрипта, пользуясь переданными ему параметрами командной строки.
Ответы на разные вопросы, которые могут появиться в одном и том же месте
Если автоматизируемая программа может, в одной ситуации, выдать одну строку, а в другой, в том же самом месте — другую, в expect можно использовать блоки, заключённые в фигурные скобки и содержащие варианты реакции скрипта на разные данные, полученные от программы. Выглядит это так:
expect {
"something" { send -- "send thisr" }
"*another" { send -- "send anotherr" }
}
Здесь, если expect-скрипт увидит строку «something», он отправит ответ «send this». Если же это будет некая строка, оканчивающаяся на «another», он отправит ответ «send another».
Напишем новый скрипт, записав его в файлquestions
, случайным образом задающий в одном и том же месте разные вопросы:
#!/bin/bash let number=$RANDOM if [ $number -gt 25000 ] then echo "What is your favorite topic?" else echo "What is your favorite movie?" fi read $REPLY
Тут мы генерируем случайное число при каждом запуске скрипта, и, проанализировав его, выводим один из двух вопросов.
Для автоматизации такого скрипта нам и пригодится вышеописанная конструкция:
#!/usr/bin/expect -f
set timeout -1
spawn ./questions
expect {
"*topic?" { send -- "Programmingr" }
"*movie?" { send -- "Star warsr" }
}
Ответы на разные вопросы, появляющиеся в одном и том же месте
Как видно, когда автоматизированный скрипт выводит строку, оканчивающуюся на «topic?», expect-скрипт передаёт ему строку «Programming». Получив в том же месте, при другом запуске программы, вопрос, оканчивающийся на «movie?», expect-скрипт отвечает: «Star wars». Это очень полезная техника.
Условный оператор
Expect поддерживает условный операторif-else
и другие управляющие конструкции. Вот пример использования условного оператора:
#!/usr/bin/expect -f
set TOTAL 1
if { $TOTAL < 5 } {
puts "nTOTAL is less than 5n"
} elseif { $TOTAL > 5 } {
puts "nTOTAL greater than 5n"
} else {
puts "nTOTAL is equal to 5n"
}
expect eof
Условный оператор в expect
Тут мы присваиваем переменнойTOTAL
некое число, после чего проверяем его и выводим текст, зависящий от результата проверки.
Обратите внимание на конфигурацию фигурных скобок. Очередная открывающая скобка должна быть расположена на той же строке, что и предыдущие конструкции.
Цикл while
Циклыwhile
в expect очень похожи на те, что используются в обычных bash-скриптах, но, опять же, тут применяются фигурные скобки:
#!/usr/bin/expect -f
set COUNT 0
while { $COUNT <= 5 } {
puts "nCOUNT is currently at $COUNT"
set COUNT [ expr $COUNT + 1 ]
}
puts ""
Цикл while в expect
Цикл for
Циклfor
в expect устроен по-особому. В начале цикла, в самостоятельных парах фигурных скобок, надо указать переменную-счётчик, условие прекращения цикла и правило модификации счётчика. Затем, опять же в фигурных скобках, идёт тело цикла:
#!/usr/bin/expect -f
for {set COUNT 0} {$COUNT <= 5} {incr COUNT} {
puts "nCOUNT is at $COUNT"
}
puts ""
Цикл for в expect
Объявление и использование функций
Expect позволяет программисту объявлять функции, используя ключевое словоproc
:
proc myfunc { MY_COUNT } {
set MY_COUNT [expr $MY_COUNT + 1]
return "$MY_COUNT"
}
Вот как выглядит expect-скрипт, в котором используется объявленная в нём же функция:
#!/usr/bin/expect -f
proc myfunc { MY_COUNT } {
set MY_COUNT [expr $MY_COUNT + 1]
return "$MY_COUNT"
}
set COUNT 0
while {$COUNT <= 5} {
puts "nCOUNT is currently at $COUNT"
set COUNT [myfunc $COUNT]
}
puts ""
Функции в expect
Команда interact
Случается так, что автоматизируемые с помощью expect программы требуют ввода конфиденциальных данных, вроде паролей, которые вам не хотелось бы хранить в виде обычного текста в коде скрипта. В подобной ситуации можно воспользоваться командойinteract
, которая позволит вам, автоматизировав некую часть взаимодействия с программой, самостоятельно ввести, скажем, пароль, а потом опять передать управление expect.
Когда выполняется эта команда, expect-скрипт переключается на чтение ответа на вопрос программы с клавиатуры, вместо того, чтобы передавать ей ранее записанные в нём данные.
Вот bash-скрипт, в общем-то, точно такой же, как мы рассматривали ранее, но теперь ожидающий ввод пароля в ответ на один из своих вопросов:
#!/bin/bash echo "Hello, who are you?" read $REPLY echo "What is you password?" read $REPLY echo "What is your favorite topic?" read $REPLY
Напишем expect-скрипт, который, когда ему предлагают предоставить пароль, передаёт управление нам:
#!/usr/bin/expect -f
set timeout -1
spawn ./questions
expect "Hello, who are you?r"
send -- "Hi Im Adamr"
expect "*password?r"
interact ++ return
send "r"
expect "*topic?r"
send -- "Technologyr"
expect eof
Команда interact в expect-скрипте
Встретив команду interact
, expect-скрипт остановится, предоставив нам возможность ввести пароль. После ввода пароля надо ввести «++» и expect-скрипт продолжит работу, снова получив управление.
bash
Bash-скрипты: начало +53
Настройка Linux, Серверное администрирование, Блог компании RUVDS.com
Рекомендация: подборка платных и бесплатных курсов монтажа видео — https://katalog-kursov.ru/
Сегодня поговорим о bash-скриптах. Это — сценарии командной строки, написанные для оболочки bash. Существуют и другие оболочки, например — zsh, tcsh, ksh, но мы сосредоточимся на bash. Этот материал предназначен для всех желающих, единственное условие — умение работать в командной строке Linux.
Сценарии командной строки — это наборы тех же самых команд, которые можно вводить с клавиатуры, собранные в файлы и объединённые некоей общей целью. При этом результаты работы команд могут представлять либо самостоятельную ценность, либо служить входными данными для других команд. Сценарии — это мощный способ автоматизации часто выполняемых действий.
Итак, если говорить о командной строке, она позволяет выполнить несколько команд за один раз, введя их через точку с запятой:
pwd ; whoami
На самом деле, если вы опробовали это в своём терминале, ваш первый bash-скрипт, в котором задействованы две команды, уже написан. Работает он так. Сначала команда pwd
выводит на экран сведения о текущей рабочей директории, потом команда whoami
показывает данные о пользователе, под которым вы вошли в систему.
Используя подобный подход, вы можете совмещать сколько угодно команд в одной строке, ограничение — лишь в максимальном количестве аргументов, которое можно передать программе. Определить это ограничение можно с помощью такой команды:
getconf ARG_MAX
Командная строка — отличный инструмент, но команды в неё приходится вводить каждый раз, когда в них возникает необходимость. Что если записать набор команд в файл и просто вызывать этот файл для их выполнения? Собственно говоря, тот файл, о котором мы говорим, и называется сценарием командной строки.
Создайте пустой файл с использованием команды touch
. В его первой строке нужно указать, какую именно оболочку мы собираемся использовать. Нас интересует bash
, поэтому первая строка файла будет такой:
#!/bin/bash
В других строках этого файла символ решётки используется для обозначения комментариев, которые оболочка не обрабатывает. Однако, первая строка — это особый случай, здесь решётка, за которой следует восклицательный знак (эту последовательность называют шебанг) и путь к bash
, указывают системе на то, что сценарий создан именно для bash
.
Команды оболочки отделяются знаком перевода строки, комментарии выделяют знаком решётки. Вот как это выглядит:
#!/bin/bash
# This is a comment
pwd
whoami
Тут, так же, как и в командной строке, можно записывать команды в одной строке, разделяя точкой с запятой. Однако, если писать команды на разных строках, файл легче читать. В любом случае оболочка их обработает.
Установка разрешений для файла сценария
Сохраните файл, дав ему имя myscript
, и работа по созданию bash-скрипта почти закончена. Сейчас осталось лишь сделать этот файл исполняемым, иначе, попытавшись его запустить, вы столкнётесь с ошибкой Permission denied
.
Попытка запуска файла сценария с неправильно настроенными разрешениями
Сделаем файл исполняемым:
chmod +x ./myscript
Теперь попытаемся его выполнить:
./myscript
После настройки разрешений всё работает как надо.
Успешный запуск bash-скрипта
Вывод сообщений
Для вывода текста в консоль Linux применяется команда echo
. Воспользуемся знанием этого факта и отредактируем наш скрипт, добавив пояснения к данным, которые выводят уже имеющиеся в нём команды:
#!/bin/bash
# our comment is here
echo "The current directory is:"
pwd
echo "The user logged in is:"
whoami
Вот что получится после запуска обновлённого скрипта.
Вывод сообщений из скрипта
Теперь мы можем выводить поясняющие надписи, используя команду echo
. Если вы не знаете, как отредактировать файл, пользуясь средствами Linux, или раньше не встречались с командой echo
, взгляните на этот материал.
Использование переменных
Переменные позволяют хранить в файле сценария информацию, например — результаты работы команд для использования их другими командами.
Нет ничего плохого в исполнении отдельных команд без хранения результатов их работы, но возможности такого подхода весьма ограничены.
Существуют два типа переменных, которые можно использовать в bash-скриптах:
- Переменные среды
- Пользовательские переменные
Переменные среды
Иногда в командах оболочки нужно работать с некими системными данными. Вот, например, как вывести домашнюю директорию текущего пользователя:
#!/bin/bash
# display user home
echo "Home for the current user is: $HOME"
Обратите внимание на то, что мы можем использовать системную переменную $HOME
в двойных кавычках, это не помешает системе её распознать. Вот что получится, если выполнить вышеприведённый сценарий.
Использование переменной среды в сценарии
А что если надо вывести на экран значок доллара? Попробуем так:
echo "I have $1 in my pocket"
Система обнаружит знак доллара в строке, ограниченной кавычками, и решит, что мы сослались на переменную. Скрипт попытается вывести на экран значение неопределённой переменной $1
. Это не то, что нам нужно. Что делать?
В подобной ситуации поможет использование управляющего символа, обратной косой черты, перед знаком доллара:
echo "I have $1 in my pocket"
Теперь сценарий выведет именно то, что ожидается.
Использование управляющей последовательности для вывода знака доллара
Пользовательские переменные
В дополнение к переменным среды, bash-скрипты позволяют задавать и использовать в сценарии собственные переменные. Подобные переменные хранят значение до тех пор, пока не завершится выполнение сценария.
Как и в случае с системными переменными, к пользовательским переменным можно обращаться, используя знак доллара:
TNW-CUS-FMP — промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней
#!/bin/bash
# testing variables
grade=5
person="Adam"
echo "$person is a good boy, he is in grade $grade"
Вот что получится после запуска такого сценария.
Пользовательские переменные в сценарии
Подстановка команд
Одна из самых полезных возможностей bash-скриптов — это возможность извлекать информацию из вывода команд и назначать её переменным, что позволяет использовать эту информацию где угодно в файле сценария.
Сделать это можно двумя способами.
- С помощью значка обратного апострофа «`»
- С помощью конструкции
$()
Используя первый подход, проследите за тем, чтобы вместо обратного апострофа не ввести одиночную кавычку. Команду нужно заключить в два таких значка:
mydir=`pwd`
При втором подходе то же самое записывают так:
mydir=$(pwd)
А скрипт, в итоге, может выглядеть так:
#!/bin/bash
mydir=$(pwd)
echo $mydir
В ходе его работы вывод команды pwd
будет сохранён в переменной mydir
, содержимое которой, с помощью команды echo
, попадёт в консоль.
Скрипт, сохраняющий результаты работы команды в переменной
Математические операции
Для выполнения математических операций в файле скрипта можно использовать конструкцию вида $((a+b))
:
#!/bin/bash
var1=$(( 5 + 5 ))
echo $var1
var2=$(( $var1 * 2 ))
echo $var2
Математические операции в сценарии
Управляющая конструкция if-then
В некоторых сценариях требуется управлять потоком исполнения команд. Например, если некое значение больше пяти, нужно выполнить одно действие, в противном случае — другое. Подобное применимо в очень многих ситуациях, и здесь нам поможет управляющая конструкция if-then
. В наиболее простом виде она выглядит так:
if команда
then
команды
fi
А вот рабочий пример:
#!/bin/bash
if pwd
then
echo "It works"
fi
В данном случае, если выполнение команды pwd
завершится успешно, в консоль будет выведен текст «it works».
Воспользуемся имеющимися у нас знаниями и напишем более сложный сценарий. Скажем, надо найти некоего пользователя в /etc/passwd
, и если найти его удалось, сообщить о том, что он существует.
#!/bin/bash
user=likegeeks
if grep $user /etc/passwd
then
echo "The user $user Exists"
fi
Вот что получается после запуска этого скрипта.
Поиск пользователя
Здесь мы воспользовались командой grep
для поиска пользователя в файле /etc/passwd
. Если команда grep
вам незнакома, её описание можно найти здесь.
В этом примере, если пользователь найден, скрипт выведет соответствующее сообщение. А если найти пользователя не удалось? В данном случае скрипт просто завершит выполнение, ничего нам не сообщив. Хотелось бы, чтобы он сказал нам и об этом, поэтому усовершенствуем код.
Управляющая конструкция if-then-else
Для того, чтобы программа смогла сообщить и о результатах успешного поиска, и о неудаче, воспользуемся конструкцией if-then-else
. Вот как она устроена:
if команда
then
команды
else
команды
fi
Если первая команда возвратит ноль, что означает её успешное выполнение, условие окажется истинным и выполнение не пойдёт по ветке else
. В противном случае, если будет возвращено что-то, отличающееся от нуля, что будет означать неудачу, или ложный результат, будут выполнены команды, расположенные после else
.
Напишем такой скрипт:
#!/bin/bash
user=anotherUser
if grep $user /etc/passwd
then
echo "The user $user Exists"
else
echo "The user $user doesn’t exist"
fi
Его исполнение пошло по ветке else
.
Запуск скрипта с конструкцией if-then-else
Ну что же, продолжаем двигаться дальше и зададимся вопросом о более сложных условиях. Что если надо проверить не одно условие, а несколько? Например, если нужный пользователь найден, надо вывести одно сообщение, если выполняется ещё какое-то условие — ещё одно сообщение, и так далее. В подобной ситуации нам помогут вложенные условия. Выглядит это так:
if команда1
then
команды
elif команда2
then
команды
fi
Если первая команда вернёт ноль, что говорит о её успешном выполнении, выполнятся команды в первом блоке then
, иначе, если первое условие окажется ложным, и если вторая команда вернёт ноль, выполнится второй блок кода.
#!/bin/bash
user=anotherUser
if grep $user /etc/passwd
then
echo "The user $user Exists"
elif ls /home
then
echo "The user doesn’t exist but anyway there is a directory under /home"
fi
В подобном скрипте можно, например, создавать нового пользователя с помощью команды useradd
, если поиск не дал результатов, или делать ещё что-нибудь полезное.
Сравнение чисел
В скриптах можно сравнивать числовые значения. Ниже приведён список соответствующих команд.
n1 -eq n2
Возвращает истинное значение, еслиn1
равноn2
.
n1 -ge n2
Возвращает истинное значение, еслиn1
больше или равноn2
.
n1 -gt n2
Возвращает истинное значение, еслиn1
большеn2
.
n1 -le n2
Возвращает истинное значение, еслиn1
меньше или равноn2
.
n1 -lt n2
Возвращает истинное значение, если n1 меньшеn2
.
n1 -ne n2
Возвращает истинное значение, еслиn1
не равноn2
.
В качестве примера опробуем один из операторов сравнения. Обратите внимание на то, что выражение заключено в квадратные скобки.
#!/bin/bash
val1=6
if [ $val1 -gt 5 ]
then
echo "The test value $val1 is greater than 5"
else
echo "The test value $val1 is not greater than 5"
fi
Вот что выведет эта команда.
Сравнение чисел в скриптах
Значение переменной val1
больше чем 5, в итоге выполняется ветвь then
оператора сравнения и в консоль выводится соответствующее сообщение.
Сравнение строк
В сценариях можно сравнивать и строковые значения. Операторы сравнения выглядят довольно просто, однако у операций сравнения строк есть определённые особенности, которых мы коснёмся ниже. Вот список операторов.
str1 = str2
Проверяет строки на равенство, возвращает истину, если строки идентичны.
str1 != str2
Возвращает истину, если строки не идентичны.
str1 < str2
Возвращает истину, еслиstr1
меньше, чемstr2
.
str1 > str2
Возвращает истину, еслиstr1
больше, чемstr2
.
-n str1
Возвращает истину, если длинаstr1
больше нуля.
-z str1
Возвращает истину, если длинаstr1
равна нулю.
Вот пример сравнения строк в сценарии:
#!/bin/bash
user ="likegeeks"
if [$user = $USER]
then
echo "The user $user is the current logged in user"
fi
В результате выполнения скрипта получим следующее.
Сравнение строк в скриптах
Вот одна особенность сравнения строк, о которой стоит упомянуть. А именно, операторы «>» и «<» необходимо экранировать с помощью обратной косой черты, иначе скрипт будет работать неправильно, хотя сообщений об ошибках и не появится. Скрипт интерпретирует знак «>» как команду перенаправления вывода.
Вот как работа с этими операторами выглядит в коде:
#!/bin/bash
val1=text
val2="another text"
if [ $val1 > $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
Вот результаты работы скрипта.
Сравнение строк, выведенное предупреждение
Обратите внимание на то, что скрипт, хотя и выполняется, выдаёт предупреждение:
./myscript: line 5: [: too many arguments
Для того, чтобы избавиться от этого предупреждения, заключим $val2
в двойные кавычки:
#!/bin/bash
val1=text
val2="another text"
if [ $val1 > "$val2" ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
Теперь всё работает как надо.
Сравнение строк
Ещё одна особенность операторов «>» и «<» заключается в том, как они работают с символами в верхнем и нижнем регистрах. Для того, чтобы понять эту особенность, подготовим текстовый файл с таким содержимым:
Likegeeks
likegeeks
Сохраним его, дав имя myfile
, после чего выполним в терминале такую команду:
sort myfile
Она отсортирует строки из файла так:
likegeeks
Likegeeks
Команда sort
, по умолчанию, сортирует строки по возрастанию, то есть строчная буква в нашем примере меньше прописной. Теперь подготовим скрипт, который будет сравнивать те же строки:
#!/bin/bash
val1=Likegeeks
val2=likegeeks
if [ $val1 > $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
Если его запустить, окажется, что всё наоборот — строчная буква теперь больше прописной.
Команда sort и сравнение строк в файле сценария
В командах сравнения прописные буквы меньше строчных. Сравнение строк здесь выполняется путём сравнения ASCII-кодов символов, порядок сортировки, таким образом, зависит от кодов символов.
Команда sort
, в свою очередь, использует порядок сортировки, заданный в настройках системного языка.
Проверки файлов
Пожалуй, нижеприведённые команды используются в bash-скриптах чаще всего. Они позволяют проверять различные условия, касающиеся файлов. Вот список этих команд.
-d file
Проверяет, существует ли файл, и является ли он директорией.
-e file
Проверяет, существует ли файл.
-f file
Проверяет, существует ли файл, и является ли он файлом.
-r file
Проверяет, существует ли файл, и доступен ли он для чтения.
-s file П
роверяет, существует ли файл, и не является ли он пустым.
-w file
Проверяет, существует ли файл, и доступен ли он для записи.
-x file
Проверяет, существует ли файл, и является ли он исполняемым.
file1 -nt file2
Проверяет, новее лиfile1
, чемfile2
.
file1 -ot file2
Проверяет, старше лиfile1
, чемfile2
.
-O file
Проверяет, существует ли файл, и является ли его владельцем текущий пользователь.
-G file
Проверяет, существует ли файл, и соответствует ли его идентификатор группы идентификатору группы текущего пользователя.
Эти команды, как впрочем, и многие другие рассмотренные сегодня, несложно запомнить. Их имена, являясь сокращениями от различных слов, прямо указывают на выполняемые ими проверки.
Опробуем одну из команд на практике:
#!/bin/bash
mydir=/home/likegeeks
if [ -d $mydir ]
then
echo "The $mydir directory exists"
cd $ mydir
ls
else
echo "The $mydir directory does not exist"
fi
Этот скрипт, для существующей директории, выведет её содержимое.
Вывод содержимого директории
Полагаем, с остальными командами вы сможете поэкспериментировать самостоятельно, все они применяются по тому же принципу.
Итоги
Сегодня мы рассказали о том, как приступить к написанию bash-скриптов и рассмотрели некоторые базовые вещи. На самом деле, тема bash-программирования огромна. Эта статья является переводом первой части большой серии из 11 материалов. Если вы хотите продолжения прямо сейчас — вот список оригиналов этих материалов. Для удобства сюда включён и тот, перевод которого вы только что прочли.
- Bash Script Step By Step — здесь речь идёт о том, как начать создание bash-скриптов, рассмотрено использование переменных, описаны условные конструкции, вычисления, сравнения чисел, строк, выяснение сведений о файлах.
- Bash Scripting Part 2, Bash the awesome — тут раскрываются особенности работы с циклами for и while.
- Bash Scripting Part 3, Parameters & options — этот материал посвящён параметрам командной строки и ключам, которые можно передавать скриптам, работе с данными, которые вводит пользователь, и которые можно читать из файлов.
- Bash Scripting Part 4, Input & Output — здесь речь идёт о дескрипторах файлов и о работе с ними, о потоках ввода, вывода, ошибок, о перенаправлении вывода.
- Bash Scripting Part 5, Sighals & Jobs — этот материал посвящён сигналам Linux, их обработке в скриптах, запуску сценариев по расписанию.
- Bash Scripting Part 6, Functions — тут можно узнать о создании и использовании функций в скриптах, о разработке библиотек.
- Bash Scripting Part 7, Using sed — эта статья посвящена работе с потоковым текстовым редактором sed.
- Bash Scripting Part 8, Using awk — данный материал посвящён программированию на языке обработки данных awk.
- Bash Scripting Part 9, Regular Expressions — тут можно почитать об использовании регулярных выражений в bash-скриптах.
- Bash Scripting Part 10, Practical Examples — здесь приведены приёмы работы с сообщениями, которые можно отправлять пользователям, а так же методика мониторинга диска.
- Bash Scripting Part 11, Expect Command — этот материал посвящён средству Expect, с помощью которого можно автоматизировать взаимодействие с интерактивными утилитами. В частности, здесь идёт речь об expect-скриптах и об их взаимодействии с bash-скриптами и другими программами.
Полагаем, одно из ценных свойств этой серии статей заключается в том, что она, начинаясь с самого простого, подходящего для пользователей любого уровня, постепенно ведёт к довольно серьёзным темам, давая шанс всем желающим продвинуться в деле создания сценариев командной строки Linux.
Уважаемые читатели! Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.
���������� A. ��������������
������� ���������
� ���� ���������� ������� ��������, ������� �� ������ �
�������� ����� ���������. ������, ��� ����������� ����� ����, ���
�� �� ��������� ����� �� �� ��������.
������ A-1. manview: �������� ������� ����������
man
#!/bin/bash # manview.sh: �������� ������� ���������� man � ��������������� ����. # ������� ��������� ������� ����������, ��������� ����������� �������� � �������� ���� #+ ��� ��� ����� ��������� � �������� ����. E_WRONGARGS=65 if [ -z "$1" ] then echo "������� �������������: `basename $0` ���_�����" exit $E_WRONGARGS fi groff -Tascii -man $1 | less # ���� �������� ����������� �������� � ���� ������� �/��� ���������, # �� ���� �������� "�������". # ��� ����� ������� ����� ������������ ��������� ������. # # gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man # # ������� S.C. exit 0
������ A-2. mailformat: �������������� �����������
�����
#!/bin/bash # mail-format.sh: �������������� ����������� �����. # ������� ������� "^", ��������� � ������������ ��������� ������� ������. # ================================================================= # ����������� �������� ���������� ARGS=1 E_BADARGS=65 E_NOFILE=66 if [ $# -ne $ARGS ] # �������� ����� ���������� then echo "������� �������������: `basename $0` ���_�����" exit $E_BADARGS fi if [ -f "$1" ] # �������� ������� �����. then file_name=$1 else echo "���� "$1" �� ������." exit $E_NOFILE fi # ================================================================= MAXWIDTH=70 # ������������ ����� ������. # �������� �������� "^" ������� � ������� ������� ������, #+ � ���������� ����� ������ 70-� ���������. sed ' s/^>// s/^ *>// s/^ *// s/ *// ' $1 | fold -s --width=$MAXWIDTH # ���� -s ������� "fold" ���������, ���� ��� ��������, ������ �� ����������� �������. # ���� �������� ��� ������� ����� ��������� ������, � ������� �������������� #+ ������� ��� Windows, �������� � 164K, � �������� �����������������. # # ������� ����� ������ ��� ��������� ������ � ����������� #+ ���������� ���� -- ��� ���, ��� ����������, ����� ��������� ��������� ����������� #+ ��������� "��������" ����������. exit 0
������ A-3. rn: ����� ������� ������� ���
�������������� ������
���� �������� �������� ������������ ������ 12-15.
#! /bin/bash # # ����� ������� ������� ��� �������������� ������ # # ������� "ren", ����� Vladimir Lanin (lanin@csd2.nyu.edu), #+ ��������� ��� �� �������� ����� �����. ARGS=2 E_BADARGS=65 ONE=1 # ������������ ��� ������������� ����� (��. ����). if [ $# -ne "$ARGS" ] then echo "������� �������������: `basename $0` ������_������ �����_������" # ��������: "rn gif jpg", �������� ���������� ���� ������ � ������� �������� � gif �� jpg. exit $E_BADARGS fi number=0 # ���������� ��������������� ������. for filename in *$1* # ������ �� ������ ������ � ������� ��������. do if [ -f "$filename" ] then fname=`basename $filename` # ������� ���� � ����� �� �����. n=`echo $fname | sed -e "s/$1/$2/"` # �������� ������ ��� �� �����. mv $fname $n # �������������. let "number += 1" fi done if [ "$number" -eq "$ONE" ] # ���������� ������ ����������. then echo "$number ���� ������������." else echo "������������� ������: $number." fi exit 0 # ����������: # ---------- # � ������ ������ ������ ���� �������� �� ����� ��������? # ��� ��� ���������? # # ����������� �������� ����� �������, ����� �� ��� ������������ ��� ����� � ��������, #+ � ������ ������� ���������� �������, ������� ������� �������� �������������.
������ A-4. blank-rename: �������������� ������, ���
����� �������� �������
��� ���� ����� ������� ������ ����������� �������.
#! /bin/bash # blank-rename.sh # # �������� ������� �������� ������������� � ������ ������ � ������� ��������. ONE=1 # ������������ ��� ������������� ����� (��. ����). number=0 # ���������� ��������������� ������. FOUND=0 # ��� ���������� � ������ ������. for filename in * # ������� ���� ������ � ������� ��������. do echo "$filename" | grep -q " " # ��������� -- �������� �� ��� ����� if [ $? -eq $FOUND ] #+ �������. then fname=$filename # ������� ���� �� ����� �����. n=`echo $fname | sed -e "s/ /_/g"` # �������� ������� �������� �������������. mv "$fname" "$n" # ��������������. let "number += 1" fi done if [ "$number" -eq "$ONE" ] then echo "$number ���� ������������." else echo "������������� ������: $number" fi exit 0
������ A-5. encryptedpw: �������� ����� ��
ftp-������, � �������������� ������
#!/bin/bash # ����������� ������� "ex72.sh", ��������� ���������� ������. # �������� ��������: ���� ������� ��� ��� ������ ������� ����������, #+ ��������� � ���� ������ ������ � ��������������� ����. # ����������� "ssh", ���� ��� ��� ���������. E_BADARGS=65 if [ -z "$1" ] then echo "������� �������������: `basename $0` ���_�����" exit $E_BADARGS fi Username=bozo # �������� �� ����. pword=/home/bozo/secret/password_encrypted.file # ����, ���������� ������ � ������������� ����. Filename=`basename $1` # ������� ���� �� ����� ����� Server="XXX" Directory="YYY" # ���������� ����������� ��� ������� � ��������. Password=`cruft <$pword` # �����������. # ������������ ��������� ��������� "cruft", #+ ���������� �� ��������� "onetime pad", #+ �� ����� ������� � : #+ Primary-site: ftp://ibiblio.org/pub/Linux/utils/file #+ cruft-0.2.tar.gz [16k] ftp -n $Server <<End-Of-Session user $Username $Password binary bell cd $Directory put $Filename bye End-Of-Session # ���� -n, ������� "ftp", ��������� �������������� ����. # "bell" -- ������ (�������� ������) ����� �������� ������� �����. exit 0
������ A-6. copy-cd: ����������� �������-������ �
�������
#!/bin/bash # copy-cd.sh: copying a data CD CDROM=/dev/cdrom # ���������� CD ROM OF=/home/bozo/projects/cdimage.iso # ������������� ���� # /xxxx/xxxxxxx/ �������� ��� ����� �������. BLOCKSIZE=2048 SPEED=2 # ����� ������ ����� ������� ��������, ���� ��������������. echo; echo "�������� �������� CD, �� *��* ���������� ���." echo "������� ENTER, ����� ������ ������. " read ready # ��������. echo; echo "��������� ������������� ���� $OF." echo "��� ����� ������ �����-�� �����. ���������� ���������." dd if=$CDROM of=$OF bs=$BLOCKSIZE # �����������. echo; echo "������ �������� CD." echo "�������� ������ �������� CDR." echo "������� ENTER, ����� ������ ������. " read ready # ��������. echo "���������� ���� $OF �� ��������." cdrecord -v -isosize speed=$SPEED dev=0,0 $OF # ������������ ����� Joerg Schilling -- "cdrecord" . # http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html echo; echo "����������� ���������." echo "������� ������� ������������� ���� (y/n)? " # ��������� ������� ���� ���������. read answer case "$answer" in [yY]) rm -f $OF echo "���� $OF ������." ;; *) echo "���� $OF �� ��� ������.";; esac echo # ����������: # �������� � �������� "case" ����������� ���������, ��������� �������������, "yes" � "Yes". exit 0
������ A-7. ������������������ �������
(Collatz)
#!/bin/bash # collatz.sh # ������ ��������� ������������������ ������� (Collatz) (�������� �������). # ------------------------------------------- # 1) ��������� �� ��������� ������ "���������" ����� �����. # 2) ����� <--- ��������� �������� # 3) ������� �����. # 4) ���� ����� ������, ��������� �� 2, # 5)+ ���� �� ������ -- �������� �� 3 � ��������� 1. # 6) ����� <--- ��������� # 7) ���������, ������� � �. 3, �������� ����� ���. # # ������������, ����� ������������������ ������ ���������, #+ �� �������� �� �������� ���������� ��������, #+ � ���������� ������ "4,2,1...", #+ ���� ����� ������������ ���������� � ����� ������. MAX_ITERATIONS=200 # ��� ������� ��������� �������� (>32000), ��� �������� �������� ���������. h=${1:-$$} # ��������� �������� # ���� �� ��������� ������ ������ �� ������, �� ������� $PID, echo echo "C($h) --- $MAX_ITERATIONS ��������" echo for ((i=1; i<=MAX_ITERATIONS; i++)) do echo -n "$h " # ^^^^^ # ��������� let "remainder = h % 2" if [ "$remainder" -eq 0 ] # ������? then let "h /= 2" # ��������� �� 2. else let "h = h*3 + 1" # �������� �� 3 � ��������� 1. fi COLUMNS=10 # �������� �� 10 �������� � ������. let "line_break = i % $COLUMNS" if [ "$line_break" -eq 0 ] then echo fi done echo exit 0
������ A-8. days-between: ������� ����� ���� �����
����� ������
#!/bin/bash # days-between.sh: ������� ����� ���� ����� ����� ������. # ������� �������������: ./days-between.sh [M]M/[D]D/YYYY [M]M/[D]D/YYYY ARGS=2 # ��������� ��� ��������� �� ��������� ������. E_PARAM_ERR=65 # ������ � ����� ��������� ����������. REFYR=1600 # ��������� ���. CENTURY=100 DIY=365 ADJ_DIY=367 # ������������� �� ���������� ��� + 1. MIY=12 DIM=31 LEAPCYCLE=4 MAXRETVAL=255 # ����������� ��������� ������������ �������� # ��� ������������� �����. diff= # ���������� ���� ����� ������. value= # ���������� ��������. day= # ����, �����, ���. month= year= Param_Error () # ������ � ���������� ��������� ������. { echo "������� �������������: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY" echo " (���� ������ ���� ����� 1/3/1600)" exit $E_PARAM_ERR } Parse_Date () # ������ ����. { month=${1%%/**} dm=${1%/**} # ���� � �����. day=${dm#*/} let "year = `basename $1`" # ���� ��� � �� ��� �����, �� ��������� ��� ��. } check_date () # �������� ����. { [ "$day" -gt "$DIM" ] || [ "$month" -gt "$MIY" ] || [ "$year" -lt "$REFYR" ] && Param_Error # ����� �� �������� ��� ����������� ������. # ������������ ���������� "���-������ / �-������". # # ����������: ���������� ����� ������� �������� ����. } strip_leading_zero () # ������� ������� ���� { val=${1#0} # ����� Bash ����� ������� ����� return $val # ������������� (POSIX.2, sect 2.9.2.1). } day_index () # ������� ������: { # ���������� ���� �� 3 ���. 1600 �� �������� ����. day=$1 month=$2 year=$3 let "month = $month - 2" if [ "$month" -le 0 ] then let "month += 12" let "year -= 1" fi let "year -= $REFYR" let "indexyr = $year / $CENTURY" let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr + $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM" # ����� ��������� ���������� ��������� �� ������� � # http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm if [ "$Days" -gt "$MAXRETVAL" ] # ���� ������ 255, then # �� �������� ���� let "dindex = 0 - $Days" # ����� ������� ������ ������� ������ ��������. else let "dindex = $Days" fi return $dindex } calculate_difference () # ������� ����� ����� ������. { let "diff = $1 - $2" # ���������� ����������. } abs () # ���������� �������� { # ������������ ���������� ���������� "value". if [ "$1" -lt 0 ] # ���� ����� ������������� then # �� let "value = 0 - $1" # �������� ����, else # ����� let "value = $1" # �������� ��� ����. fi } if [ $# -ne "$ARGS" ] # ��������� ��� ��������� ��������� ������. then Param_Error fi Parse_Date $1 check_date $day $month $year # �������� ����. strip_leading_zero $day # ������� ������� ���� day=$? # � ������ ��� �/��� ������. strip_leading_zero $month month=$? day_index $day $month $year date1=$? abs $date1 # ���������� �������� date1=$value Parse_Date $2 check_date $day $month $year strip_leading_zero $day day=$? strip_leading_zero $month month=$? day_index $day $month $year date2=$? abs $date2 # ���������� �������� date2=$value calculate_difference $date1 $date2 abs $diff # ���������� �������� diff=$value echo $diff exit 0 # �������� ���� �������� � ����������� ������� ������ �� C # http://buschencrew.hypermart.net/software/datedif
������ A-9. �������� «�������»
#!/bin/bash # makedict.sh [�������� �������] # ����������� �������� /usr/sbin/mkdict. # ��������� ����� �� ������������ �������� ����������� Alec Muffett. # # ���� ���������������� ������� ������� � �������� �� ������ #+ ��������� "LICENSE" �� ������ "Crack" #+ � ������� ���������������� ������������ ��������. # ���� ������ ������������ ��������� ����� � ������� ��������������� ������ #+ ����, ��������� � ���� ������. # �� ����� ��������� �������� ��� ������ �������� #+ � ���������� ������������������� �������. E_BADARGS=65 if [ ! -r "$1" ] # ��������� ���� �� ���� �������� -- then #+ ��� �����. echo "������� �������������: $0 �����_������" exit $E_BADARGS fi # SORT="sort" # ������������� ������� ������ ���������� ������. #+ ��������, �� ��������� � ������������� ��������. cat $* | # ������ ���������� ������ �� stdout. tr A-Z a-z | # ������������� � ������ �������. tr ' ' '12' | # �����: �������� ������� ��������� �������� ������. # tr -cd '12[a-z][0-9]' | # � ������������ ��������: ������� ��� �������, #+ ������� �� �������� ������� ��� �������. tr -c '12a-z' '12' | # ������ �������� #+ �����������-�������� ������� ���������� �� ������� ������. sort | uniq | # ������� ������������� �����. grep -v '^#' | # ������� ������, ������������ � "#". grep -v '^$' # ������� ������ ������. exit 0
������ A-10. ������ �������
«�����������»
#!/bin/bash # soundex.sh: ������ ������� "�����������" # ======================================================= # �������� Soundex # ����� # Mendel Cooper # thegrendel@theriver.com # 23 ������ 2002 �. # # ������� ���������������: Public Domain. # # ��������� ������������ ������ ����� �������� ���� ������������ #+ ���� ������� (Ed Schaefer) � ���� 2002 ���� � ������� "Shell Corner" #+ "Unix Review" on-line, #+ http://www.unixreview.com/documents/uni1026336632258/ # ======================================================= ARGCOUNT=1 # ��������� �������� ��������� ������. E_WRONGARGS=70 if [ $# -ne "$ARGCOUNT" ] then echo "������� �������������: `basename $0` ���" exit $E_WRONGARGS fi assign_value () # ��������� �������� �������� { #+ �������� � �����. val1=bfpv # 'b,f,p,v' = 1 val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2 val3=dt # � �.�. val4=l val5=mn val6=r # ���������� ����������� � ���, ��� ����� ����������. value=$( echo "$1" | tr -d wh | tr $val1 1 | tr $val2 2 | tr $val3 3 | tr $val4 4 | tr $val5 5 | tr $val6 6 | tr -s 123456 | tr -d aeiouy ) # �������� � ����� ������������� �������� ��������. # ��������� ������������� �����, ���� ��� �� ��������� ��������. # ������� ������������, ���� ��� �� �������� �������������, ������� ��������� � ��������� �������. # ������� 'w' � 'h' ��������� � ������ �������. } input_name="$1" echo echo "��� = $input_name" # ��������� ��� ������� � ����� � ������ �������. # ------------------------------------------------ name=$( echo $input_name | tr A-Z a-z ) # ------------------------------------------------ # ��������� ������ � ������� "��������": ������ ����� � �����. # -------------------------------------------- char_pos=0 # ��������� ������� � �����. prefix0=${name:$char_pos:1} prefix=`echo $prefix0 | tr a-z A-Z` # ������ ����� � ����� -- � ������� �������. let "char_pos += 1" # ����������� "���������" �� ���� ������. name1=${name:$char_pos} # ++++++++++++++++++++++++++++ ���������� ��������� �������� +++++++++++++++++++++++++++++++ # ������ �� ������������� �� ���� ������ ������. # ���� ������ ������ � ����� ��������� � ������ #+ �� ��� ����� ���������. # ����� ����, �� ������ ��������� -- �� �������� �� ������ ������ #+ �������, 'w' ��� 'h'. char1=`echo $prefix | tr A-Z a-z` # ������ ������ -- � ������ �������. assign_value $name s1=$value assign_value $name1 s2=$value assign_value $char1 s3=$value s3=9$s3 # ���� ������ ������ � ����� -- ������� ����� #+ ��� 'w' ��� 'h', #+ �� �� "��������" ����� ���������. #+ ������� ������ 9, ��� ������ #+ �������������� ��������, ������� ����� ����� ���������. if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]] then suffix=$s2 else suffix=${s2:$char_pos} fi # ++++++++++++++++++++++++ ����� ���������� ��������� �������� +++++++++++++++++++++++++++++++ padding=000 # ��������� ����� ������. soun=$prefix$suffix$padding # ���� �������� � ����� ������������� �������. MAXLEN=4 # ���������� ����� ������� 4-�� ���������. soundex=${soun:0:$MAXLEN} echo "������ �������� = $soundex" echo # ������ "��������" - ��� ����� ���������� � ������������� ���� #+ �� ������� ��������. # ������ "��������" ���������� � ������� ������� � �����, #+ �� ������� ������� 3-������� ��������� ���. # �����, ������� ������������ �������� ���������, ����� ������� ������� "��������". # ��������: # Smith � Smythe -- ��� ����� ������ "��������" "S530". # Harrison = H625 # Hargison = H622 # Harriman = H655 # ��� ������� ��� �������� ���� �������� ���������, �� ������� � ��������. # # # �������������� ���������� �� ������� �� #+ "National Archives and Records Administration home page", #+ http://www.nara.gov/genealogy/soundex/soundex.html # ����������: # ---------- # ��������� ���� "���������� ��������� ��������" . exit 0
������ A-11. «����
«�����»»
#!/bin/bash # life.sh: ���� "�����" # ##################################################################### # # ��� Bash-������ ��������� ���� ����� ������ (John Conway) "�����". # # --------------------------------------------------------------------- # # ������������� ������� ���� ������� �� ������, � ������ ������ ����� # #+ ������������� ����� �����. # # ��������������, ������ � ����� ������ ���������� ������, # #+ �� ������� ������ -- �������� ������. # # ����������, ������ ����������� �� ����� -- # #+ ��� ������ ���������, ��� "��������� 0" # # ��������������� ������, � ������ ����������� ���������, # #+ ������������ ���������� ��������� # # 1) ������ ������ ����� "�������" # #+ �����, ������, ������, ����� � 4 �� ����������. # # 123 # # 4*5 # # 678 # # # # 2) ���� ����� ����� ����� 2 ��� 3 ����� �������, �� ��� �������� ����.# # 3) ���� ������ ������ ����� 3 ����� ������� -- # #+ � ��� "���������" ����� ����� # SURVIVE=2 # BIRTH=3 # # 4) � ����� ������ ������, ����� ����� "��������" # # ##################################################################### # startfile=gen0 # ��������� ��������� �� ����� ��-��������� -- "gen0". # ���� �� ����� ������ ����, �� ��������� ������. # if [ -n "$1" ] # ��������� �������� ��������� ������ -- ���� � "����������n 0". then if [ -e "$1" ] # �������� ������� �����. then startfile="$1" fi fi ALIVE1=. DEAD1=_ # ������������� "�����" ������ � ������ ����� � ����� � "���������� 0". # ���� �������� �������� � ������� ����� 10 x 10 grid (����� ���� ���������, #+ �� ������� ������� ���� ����� �������������� ����� ��������). ROWS=10 COLS=10 GENERATIONS=10 # ������������ ����� ���������. NONE_ALIVE=80 # ��� ���������� �� ������, #+ ���� �� �������� �� ����� "�����" �����. TRUE=0 FALSE=1 ALIVE=0 DEAD=1 avar= # ������� ���������. generation=0 # ������������� �������� ���������. # ================================================================= let "cells = $ROWS * $COLS" # ���������� ����� �� ������� ����. declare -a initial # ������� �����. declare -a current display () { alive=0 # ���������� "�����" ������. # ���������� -- ����. declare -a arr arr=( `echo "$1"` ) # ������������� �������� � ������. element_count=${#arr[*]} local i local rowcheck for ((i=0; i<$element_count; i++)) do # ������ �������� ������ -- � ����� ������ ������. let "rowcheck = $i % ROWS" if [ "$rowcheck" -eq 0 ] then echo # ������� ������. echo -n " " # ������������. fi cell=${arr[i]} if [ "$cell" = . ] then let "alive += 1" fi echo -n "$cell" | sed -e 's/_/ /g' # ������� ������, �� ���� ������� ������� ������������� �� �������. done return } IsValid () # �������� ������������ ��������� ������. { if [ -z "$1" -o -z "$2" ] # �������� ������� ������� ����������. then return $FALSE fi local row local lower_limit=0 # ������ �� ������������� ����������. local upper_limit local left local right let "upper_limit = $ROWS * $COLS - 1" # ����� ��������� ������ �� ������� ����. if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ] then return $FALSE # ����� �� ������� �������. fi row=$2 let "left = $row * $ROWS" # ����� �������. let "right = $left + $COLS - 1" # ������ �������. if [ "$1" -lt "$left" -o "$1" -gt "$right" ] then return $FALSE # ����� �� ������ ������. fi return $TRUE # ���������� ���������. } IsAlive () # �������� ������� "�����" ����� � ������. # ��������� ������ � ����� ������ � �������� ������� ����������. { GetCount "$1" $2 # ���������� ���-�� "�����" �������. local nhbd=$? if [ "$nhbd" -eq "$BIRTH" ] # "�����". then return $ALIVE fi if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ] then # "�����" ���� ����� ���� ���� "�����". return $ALIVE fi return $DEAD # ��-���������. } GetCount () # ������� "�����" �������. # ���������� 2 ���������: # $1) ����������-������ # $2) cell ����� ������ { local cell_number=$2 local array local top local center local bottom local r local row local i local t_top local t_cen local t_bot local count=0 local ROW_NHBD=3 array=( `echo "$1"` ) let "top = $cell_number - $COLS - 1" # ������ �������� �����. let "center = $cell_number - 1" let "bottom = $cell_number + $COLS - 1" let "r = $cell_number / $ROWS" for ((i=0; i<$ROW_NHBD; i++)) # �������� �����-�������. do let "t_top = $top + $i" let "t_cen = $center + $i" let "t_bot = $bottom + $i" let "row = $r" # ������ �� ������� � ������� ������. IsValid $t_cen $row # ���������� ���������? if [ $? -eq "$TRUE" ] then if [ ${array[$t_cen]} = "$ALIVE1" ] # "�����"? then # ��! let "count += 1" # ��������� �������. fi fi let "row = $r - 1" # �� ������� ������. IsValid $t_top $row if [ $? -eq "$TRUE" ] then if [ ${array[$t_top]} = "$ALIVE1" ] then let "count += 1" fi fi let "row = $r + 1" # �� ������ ������. IsValid $t_bot $row if [ $? -eq "$TRUE" ] then if [ ${array[$t_bot]} = "$ALIVE1" ] then let "count += 1" fi fi done if [ ${array[$cell_number]} = "$ALIVE1" ] then let "count -= 1" # ���������, ��� ���� ����������� ������ fi #+ �� ���� ����������. return $count } next_gen () # �������� ������, � ������� ���������� ���������� � ����� "���������". { local array local i=0 array=( `echo "$1"` ) # ������������� � ������. while [ "$i" -lt "$cells" ] do IsAlive "$1" $i ${array[$i]} # "�����"? if [ $? -eq "$ALIVE" ] then # ���� "�����", �� array[$i]=. #+ �������� �����. else array[$i]="_" # ����� -- ������ ������������� fi #+ (������� ������� ��������� �� ������). let "i += 1" done # let "generation += 1" # ��������� ������� ���������. # ���������� ����������, ��� �������� � ������� "display". avar=`echo ${array[@]}` # ������������� ������ � ������. display "$avar" # ������� ���. echo; echo echo "��������� $generation -- ����� ������ $alive" if [ "$alive" -eq 0 ] then echo echo "��������������� ����������: �� �������� �� ����� ����� �����!" exit $NONE_ALIVE # ��� ������ ���������� fi #+ ���� �� �������� �� ����� ����� ����� } # ========================================================= # main () # ��������� ��������� ��������� �� �����. initial=( `cat "$startfile" | sed -e '/#/d' | tr -d 'n' | sed -e 's/./. /g' -e 's/_/_ /g'` ) # ������� ������, ������������ � ������� '#' -- �����������. # ������� ������ �������� ������ � �������� ������� ����� ����������. clear # ������� ������. echo # ��������� echo "=======================" echo " $GENERATIONS ���������" echo " �" echo " ���� " �����"" echo "=======================" # -------- ������� ������ ���������. -------- Gen0=`echo ${initial[@]}` display "$Gen0" # ����� �����. echo; echo echo "��������� $generation -- ����� ������ $alive" # ------------------------------------------- let "generation += 1" # ��������� ������� ���������. echo # ------- ������� ������ ���������. ------- Cur=`echo ${initial[@]}` next_gen "$Cur" # �������� � �������. # ------------------------------------------ let "generation += 1" # ��������� ������� ���������. # ------ �������� ���� ���� ------ while [ "$generation" -le "$GENERATIONS" ] do Cur="$avar" next_gen "$Cur" let "generation += 1" done # ============================================================== echo exit 0 # -------------------------------------------------------------- # ���� �������� ����� �����������. # ��������� ������ ������, ����� � ������ �������� �������. # ����������: ����������� �������� ����� �������, ����� , # + ����� � ������ ������� ��� �� "�������������", # + ��� �� � ������� � ������ �������.
������ A-12. ���� � ������ ���������� ��� ���� «�����»
# ��� ����-������, ���������� "��������� 0", ��� �������� "life.sh". # -------------------------------------------------------------- # ������� ���� ����� ������ 10 x 10, ������ ������������ "�����" �����, #+ �������� ������������� -- ������ ������. �� �� ����� ������������ �������, #+ ��� ����������� ������ �����, ��-�� ������������ �������� �������� � Bash. # [���������� ��� ���������: ���������, ������?.] # # ������, ������������ � ������� '#' ��������� �������������, �������� �� ����������. __.__..___ ___._.____ ____.___.. _._______. ____._____ ..__...___ ____._____ ___...____ __.._..___ _..___..__
+++
��������� ��� �������� ����������� Mark Moraes, ��
������������ � �������. ��. ���� «Moraes-COPYRIGHT», �������
�������� �������� �� ��������� �����.
������ A-13. behead: �������� ���������� ��
����������� ����� � ��������
#! /bin/sh # �������� ���������� �� ����������� ����� � �������� �.�. �� ������ # ������ ������ # Mark Moraes, ����������� � ������� # ==> ����� ����������� ��������� ������� ���������. if [ $# -eq 0 ]; then # ==> ���� ������� �������� �� ����� (����), �� �������� ��������� �� stdin. sed -e '1,/^$/d' -e '/^[ ]*$/d' # --> ������� ������ ������ � ��� ������ �������������� �� else # ==> ���� �������� ��������� ������ �����, �� ������������ ��� ��� ��� �����. for i do sed -e '1,/^$/d' -e '/^[ ]*$/d' $i # --> �� ��, ��� � ����. done fi # ==> ����������: �������� �������� �� ������� ������. # ==> # ==> �������� �������� -- ��� ������ ��������� �������� sed, �� ����������� �������� ���������. # ==> ����� �� ��� ������� � ���� �������? ������ �� ��� ������ ���?
������ A-14. ftpget: ���������� ������ ��
ftp
#! /bin/sh # $Id: ftpget,v 1.2 91/05/07 21:15:43 moraes Exp $ # �������� ������������� ��������� ���������� � ftp-��������. # ������� � ������� - ������� ��� ���������� � ftplist # -h -- ��������� ������ (��-��������� prep.ai.mit.edu) # -d -- ������� �� ������� - �� ������ ������� ������������������ �� ���������� ������ -d # ���� �� ����������� ������������� ����, # ������ ����������� ��� ������� ������������������. # (��-��������� -- ������� ������������ ftp) # -v -- "������������" �����, ����� ���������� ��� ������ ftp-������� # -f -- file[:localfile] ��������� ��������� file � ���������� ��� ������ localfile # -m -- ������ ��� mget. �� �������� ����� � �������! # -c -- ��������� ������� # ��������, # ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh # -d ../pub/R3/fixes -c ~/fixes -m 'fix*' # ��� ������� �������� ���� xplaces.shar �� ~ftp/contrib � expo.lcs.mit.edu # � �������� ��� ������ xplaces.sh � ������� ��������, ����� ������� ��� ����������� (fixes) # �� ~ftp/pub/R3/fixes � �������� �� � ������� ~/fixes. # ��������, ��� ������������������ ������ � ���������� ����� �����, ��������� # ��� ���������� ������������������ ��������, ����������� � ��������� ftp-�������� # # Mark Moraes (moraes@csri.toronto.edu), Feb 1, 1989 # # ==> ��� ����������� ��������� ������� ���������. # PATH=/local/bin:/usr/ucb:/usr/bin:/bin # export PATH # ==> ������ ��� ������ � ������������ �������� �������� �������. TMPFILE=/tmp/ftp.$$ # ==> ������ ��������� ���� SITE=`domainname`.toronto.edu # ==> 'domainname' ������� 'hostname' usage="������� �������������: $0 [-h ���������_������] [-d ���������_�������]... [-f ���������_����:���������_����]... [-c ���������_�������] [-m ������_����_������] [-v]" ftpflags="-i -n" verbflag= set -f # ��������� ����������� ���� ������ (globbing) ��� ����� -m set x `getopt vh:d:c:m:f: $*` if [ $? != 0 ]; then echo $usage exit 65 fi shift trap 'rm -f ${TMPFILE} ; exit' 0 1 2 3 15 echo "user anonymous ${USER-gnu}@${SITE} > ${TMPFILE}" # ==> ��������� ������� (�������������). echo binary >> ${TMPFILE} for i in $* # ==> ������ ��������� ������. do case $i in -v) verbflag=-v; echo hash >> ${TMPFILE}; shift;; -h) remhost=$2; shift 2;; -d) echo cd $2 >> ${TMPFILE}; if [ x${verbflag} != x ]; then echo pwd >> ${TMPFILE}; fi; shift 2;; -c) echo lcd $2 >> ${TMPFILE}; shift 2;; -m) echo mget "$2" >> ${TMPFILE}; shift 2;; -f) f1=`expr "$2" : "([^:]*).*"`; f2=`expr "$2" : "[^:]*:(.*)"`; echo get ${f1} ${f2} >> ${TMPFILE}; shift 2;; --) shift; break;; esac done if [ $# -ne 0 ]; then echo $usage exit 65 # ==> � ��������� ���� "exit 2", �������� � ������������ �� �����������. fi if [ x${verbflag} != x ]; then ftpflags="${ftpflags} -v" fi if [ x${remhost} = x ]; then remhost=prep.ai.mit.edu # ==> ����� ������ ������� ���� ftp-������ ��-���������. fi echo quit >> ${TMPFILE} # ==> ��� ������� ��������� �� ��������� �����. ftp ${ftpflags} ${remhost} < ${TMPFILE} # ==> ������ ���������� �������� ����. rm -f ${TMPFILE} # ==> � ����������, ������� ��������� ���� (����� ����������� ��� � ��������� ������). # ==> ����������: # ==> ---------- # ==> 1) �������� ��������� ������. # ==> 2) �������� ����������� �������� ��������.
������ A-15. �������� �� ���������
�����
��������� ���������� �� ��������� ������ ��������� � ����, ���������� � �����, ��������� �� Mark Moraes: "behead.sh" � "ftpget.sh" /* * Copyright University of Toronto 1988, 1989. * �����: Mark Moraes * * ����� ���� ����� �� ������������� ����� ������������ ����������� * ��� ��������� � �������������� �� ���������� �������������: * * 1. ����� � ����������� ������� �� �������� * �� ����������� ������������� ����� ������������ �����������, * ������ �������� �� ��� �� ����, * ���� ���� ��� ������� �������� � ���. * * 2. �������� �� ������������� ������������ ����������� �� ������ ������������ ����������, * ���� ��� �� ����������. ��� ��� ��������� ������������ ���������� � �������� �������, * ��� ����������� ������ ���� �������� � ������������. * * 3. ���������� ������ ������ ��������� ����� ���������� �� ���� � �� ������ * ���������� �� ��������. ��� ��� ��������� ������������ ���������� � �������� �������, * ��� ����������� ������ ���� �������� � ������������. * * 4. ��� ���������� �� ����� ��������� �/��� ����������. */
+
Antek Sawicki ����������� ��������� ��������, �������
������������� �������� ����������� ����������, ������������� � Section 9.3.
������ A-16. password: ��������� ���������� 8-��
����������� ������
#!/bin/bash # ��� ������ ������ ����� ������������� ������� #!/bin/bash2. # # ��������� ��������� ������� ��� bash 2.x # �����: Antek Sawicki <tenox@tenox.tc>, # ������� ����������� �������� ������������ ��� � ������ ���������. # # ==> �����������, ����������� ������� ��������� ==> MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" LENGTH="8" # ==> 'LENGTH' ����� ���������, ��� ��������� ����� ������� �������. while [ "${n:=1}" -le "$LENGTH" ] # ==> ���������, ��� ":=" -- ��� �������� "����������� �������� ��-���������". # ==> ����� �������, ���� 'n' �� ����������������, �� � ��� ��������� 1. do PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}" # ==> �����, �����.... # ==> ������ � ����� ���������� ������... # ==> ${#MATRIX} -- ���������� ����� ������� MATRIX. # ==> $RANDOM%${#MATRIX} -- ���������� ��������� ����� # ==> � ��������� 1 .. �����_�������(MATRIX) - 1. # ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1} # ==> ���������� ������ �� MATRIX, �� ��������� ������� (��������� ����). # ==> ��. ����������� ���������� {var:pos:len} � ������� 3.3.1 # ==> � ������� � ���� �������. # ==> PASS=... -- ���������� ������� � ������ PASS, ���������� �� ���������� ���������. # ==> ����� ��������� ���������� ��� ������ �����, ���������������� ��������� ������ # ==> echo "$PASS" # ==> �� �������, ��� �� ������ ������� �����, # ==> � ������ PASS ����������� �� ������ �������. let n+=1 # ==> ��������� 'n' ����� ������� ��������� ��������. done echo "$PASS" # ==> ��� ������������� � ����, ���� ���������. exit 0
+
James R. Van Zandt ����������� ��������� ��������, �������
������������� ���������� ����������� �������, �� ��� ������,
«�� ����� ���� — ���������� �� ����������
������� � �� �������������».
������ A-17. fifo: �������� ��������� ����� �
������� ����������� �������
#!/bin/bash # ==> �����:James R. Van Zandt # ==> ������������ � ��� ����������. # ==> �����������, ����������� ������� ���������. HERE=`uname -n` # ==> hostname THERE=bilbo echo "������ �������� ��������� ����� �� $THERE, �� `date +%r`" # ==> `date +%r` ���������� ����� � 12-�� ������� �������, �.�. "08:08:34 PM". # ��������� � ���, ��� /pipe -- ��� ������������� �����, � �� ������� ���� rm -rf /pipe mkfifo /pipe # ==> �������� "������������ ������", � ������ "/pipe". # ==> 'su xyz' -- ��������� ������� �� ����� ������������� "xyz". # ==> 'ssh' -- ����� secure shell (���� �� ��������� �������). su xyz -c "ssh $THERE "cat >/home/xyz/backup/${HERE}-daily.tar.gz" < /pipe"& cd / tar -czf - bin boot dev etc home info lib man root sbin share usr var >/pipe # ==> ����������� ����� /pipe, ������������ ��� �������� ������ ����� ����������: # ==> 'tar/gzip' ����� � /pipe, � 'ssh' -- ������ �� /pipe. # ==> � ���������� ����� �������� ��������� ����� ���� �������� ���������. # ==> � ��� ������� ������������ ������������ ������, � ������ ��������, # ==> ����� ������������� ������� "|" ? # ==> ����� �� �������� ������������� ����� � ������ ��������? exit 0
+
Stephane Chazelas ����������� ��������� ��������, �������
������������� ����������� ��������� ������� ����� ���
������������� ��������.
������ A-18. ��������� ������� �����, �
�������������� ��������� ������� �� ������ (������� ��
�������)
#!/bin/bash # primes.sh: ��������� ������� �����, ��� ������������� ��������. # �����: Stephane Chazelas. # ���� �������� �� ���������� ����������� �������� "������ ����������", #+ ������ ���� ������������ ����� �������� ����� �������� ������� ��������� � ������� ����� #+ ����� ������ ���������, � ������� ��������� ���������� ������� �� ������� "%". LIMIT=1000 # ������� �� 2 �� 1000 Primes() { (( n = $1 + 1 )) # ������� � ���������� �����. shift # ��������� �������� � ������. # echo "_n=$n i=$i_" if (( n == LIMIT )) then echo $* return fi for i; do # "i" ��������������� � "@", ���������� �������� $n. # echo "-n=$n i=$i-" (( i * i > n )) && break # �����������. (( n % i )) && continue # ������ ��������� ����� � ������� ��������� "%". Primes $n $@ # ����������� ����� ������ �����. return done Primes $n $@ $n # ����������� ����� �� ��������� �����. # ���������������� ���������� ����������� ����������. # � "$@" ������������� ������� �����. } Primes 1 exit 0 # ��������������� ������ 16 � 24, ��� ������� ������ ���� �������������. # �������� ���������� �������������� ����� �������� � �������� (ex68.sh), # ������������ �������� "������ ����������". # ����������: ���������� ����������� ���� �������� ��� ������������� ��������. # ��� ���� ��������� ������� � ��������.
+
Jordi Sanfeliu ��� �������� �� ���������� ������ ��������
tree.
������ A-19. tree: ����� ������
���������
#!/bin/sh # @(#) tree 1.1 30/11/95 by Jordi Sanfeliu # email: mikaku@fiwix.org # # ��������� ������: 1.0 30/11/95 # ��������� ������: 1.1 24/02/97 Now, with symbolic links # ����������� : Ian Kjos, ��������� ����������� ��������� # email: beth13@mail.utexas.edu # # Tree -- �������� ��������� ������ ��������� (�������� :-) ) # # ==> ������������ � ������ ��������� � ���������� ������ ��������, Jordi Sanfeliu. # ==> �����������, ����������� ������� ���������. # ==> ��������� "������������" ����������. search () { for dir in `echo *` # ==> `echo *` ������ ���� ������ � ������� ��������, ��� �������� �������� ������. # ==> ��� �� ������ ���� for dir in * # ==> �� "dir in `echo *`" �� ����������� �����, ��� ����� �������� �������. do if [ -d "$dir" ] ; then # ==> ���� ��� ������� (-d)... zz=0 # ==> ��������� ����������, ��� ���������� ������ ����������� ��������. while [ $zz != $deep ] # Keep track of inner nested loop. do echo -n "| " # ==> �������� ������ ������������ �����, # ==> � 2 ��������� � ��� �������� ������. zz=`expr $zz + 1` # ==> ��������� zz. done if [ -L "$dir" ] ; then # ==> ���� ������������� ������ �� �������... echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'` # ==> �������� �������������� ����������� � ��� ���������� ��������, ��... # ==> ��� �������� ����/�������. else echo "+---$dir" # ==> ������� �������������� �����������... # ==> � �������� ��������. if cd "$dir" ; then # ==> ���� ����� ����� � �������... deep=`expr $deep + 1` # ==> ��������� ������� �����������. search # �������� ;-) numdirs=`expr $numdirs + 1` # ==> ��������� ������� ���������. fi fi fi done cd .. # ==> ��������� �� ���� ������� �����. if [ "$deep" ] ; then # ==> ���� depth = 0 (���������� TRUE)... swfi=1 # ==> ��������� ������� ��������� ������. fi deep=`expr $deep - 1` # ==> ��������� ������� �����������. } # - Main - if [ $# = 0 ] ; then cd `pwd` # ==> ���� �������� ��������� ������ �����������, �� ������������ ������� �������. else cd $1 # ==> ����� ������� � �������� �������. fi echo "��������� ������� = `pwd`" swfi=0 # ==> ������� ���������� ������. deep=0 # ==> ������� �����������. numdirs=0 zz=0 while [ "$swfi" != 1 ] # ���� ����� �� ��������... do search # ==> ������� ������� ������. done echo "����� ��������� = $numdirs" exit 0 # ==> ���������� ����������� � ��� ��� ���� �������� ��������.
Noah Friedman ��� ���������� �� ���������� ����� ����������
������� ��� ������ �� ��������,
�������, �� ����, ������������� ��������� ������������ �������
����� C.
������ A-20. ������� ��� ������ ��
��������
#!/bin/bash # string.bash --- �������� ���������� ������� string(3) # �����: Noah Friedman <friedman@prep.ai.mit.edu> # ==> ������������ � ��� ����������. # ���� ��������: 1992-07-01 # ���� ��������� �����������: 1993-09-29 # Public domain # �������������� � ��������� bash v2 �������� Chet Ramey # �����������: # ���: #:docstring strcat: # ������� �������������: strcat s1 s2 # # Strcat ��������� ���������� ���������� s2 � ���������� s1. # # ������: # a="foo" # b="bar" # strcat a b # echo $a # => foobar # #:end docstring: ###;;;autoload function strcat () { local s1_val s2_val s1_val=${!1} # ��������� ������ s2_val=${!2} eval "$1"='"${s1_val}${s2_val}"' # ==> eval $1='${s1_val}${s2_val}' �� ��������� �������, # ==> ���� ���� �� ���������� �������� ��������� �������. } #:docstring strncat: # ������� �������������: strncat s1 s2 $n # # ������ strcat, �� ��������� �� ����� n �������� �� # ���������� s2. ��������� ��������� �� stdout. # # ������: # a=foo # b=barbaz # strncat a b 3 # echo $a # => foobar # #:end docstring: ###;;;autoload function strncat () { local s1="$1" local s2="$2" local -i n="$3" local s1_val s2_val s1_val=${!s1} # ==> ��������� ������ s2_val=${!s2} if [ ${#s2_val} -gt ${n} ]; then s2_val=${s2_val:0:$n} # ==> ��������� ��������� fi eval "$s1"='"${s1_val}${s2_val}"' # ==> eval $1='${s1_val}${s2_val}' �� ��������� �������, # ==> ���� ���� �� ���������� �������� ��������� �������. } #:docstring strcmp: # ������� �������������: strcmp $s1 $s2 # # Strcmp ���������� ��� ������ � ���������� ����� ������, ����� # ��� ������ ����, � ����������� �� ����������� ���������. #:end docstring: ###;;;autoload function strcmp () { [ "$1" = "$2" ] && return 0 [ "${1}" '<' "${2}" ] > /dev/null && return -1 return 1 } #:docstring strncmp: # ������� �������������: strncmp $s1 $s2 $n # # ������� strcmp, �� ���������� �� ����� n �������� #:end docstring: ###;;;autoload function strncmp () { if [ -z "${3}" -o "${3}" -le "0" ]; then return 0 fi if [ ${3} -ge ${#1} -a ${3} -ge ${#2} ]; then strcmp "$1" "$2" return $? else s1=${1:0:$3} s2=${2:0:$3} strcmp $s1 $s2 return $? fi } #:docstring strlen: # ������� �������������: strlen s # # ���������� ���������� �������� � ������ s. #:end docstring: ###;;;autoload function strlen () { eval echo "${#${1}}" # ==> ���������� ����� ����������, # ==> ��� ��� ���������� ��� ��������. } #:docstring strspn: # ������� �������������: strspn $s1 $s2 # # Strspn ���������� ������������ ����� �������� � ������ s1, # ������� ��������� ������� �� �������� ������ s2. #:end docstring: ###;;;autoload function strspn () { # ����� ����������� ���������� IFS ��������� ������������ ������� ��� ������� �������. local IFS= local result="${1%%[!${2}]*}" echo ${#result} } #:docstring strcspn: # ������� �������������: strcspn $s1 $s2 # # Strcspn ���������� ������������ ����� �������� � ������ s1, # ������� ��������� �� �������� ������� �� ������ s2. #:end docstring: ###;;;autoload function strcspn () { # ����� ����������� ���������� IFS ��������� ������������ ������� ��� ������� �������. local IFS= local result="${1%%[${2}]*}" echo ${#result} } #:docstring strstr: # ������� �������������: strstr s1 s2 # # Strstr ������� ��������� ������� ��������� ������ s2 # � ������ s1, ��� ������ �� �������, ���� ��������� s2 � ������ s1 �� �������. # ���� s2 �������� ������ ������� �����, �� strstr ������� ������ s1. #:end docstring: ###;;;autoload function strstr () { # ���� s2 -- ������ ������� �����, �� ������� ������ s1 [ ${#2} -eq 0 ] && { echo "$1" ; return 0; } # �� �������� ������, ���� s2 �� ������� � s1 case "$1" in *$2*) ;; *) return 1;; esac # ������������ ������, ��� �������� ���� �������������� ����� s2 � s1 first=${1/$2*/} # ����� ������� ��� �������������� � ������ ������ echo "${1##$first}" } #:docstring strtok: # ������� �������������: strtok s1 s2 # # Strtok ������������� ������ s1, ��� ������������������ �� 0, ��� �����, # ������ (�������), ����������� ��������� ������ s2 # ��� ������ ������ (� �������� ���������� s1) # ������� ������ ������� �� stdout. # ������� ���������� ���� ��������� � ������ s1 �� ������ � ������, # ��� ��� ����������� ������ ������ ������������� � ������ ������ ����������, # ����� ���������� ��������� ������ �� ������ s1. # ����� ������ ��������� �������, ��� ����������� ������ ����� �������� �� stdout # ������ ��������. ������-����������� ����� ���������� �� ������ � ������. #:end docstring: ###;;;autoload function strtok () { : } #:docstring strtrunc: # ������� �������������: strtrunc $n $s1 {$s2} {$...} # # ������������ ������� ���������, ������ ��� strncmp, ����� ������ "������" �������. # ������� ������ n �������� � ������ �� ����� s1 s2 ... �� stdout. #:end docstring: ###;;;autoload function strtrunc () { n=$1 ; shift for z; do echo "${z:0:$n}" done } # provide string # string.bash ����� ���������� # ========================================================================== # # ==> ���, ��� ��������� ����, ��������� ������� ���������. # ==> ����� ���� �������� ����� ���� ������������ ��� "����������", ���������� # ==> ������� ���, ��� ��������� ���� � "source" ���� ���� � ����� ��������. # strcat string0=one string1=two echo echo "�������� ������� "strcat" :" echo "���������� "string0" = $string0" echo ""string1" = $string1" strcat string0 string1 echo "������ "string0" = $string0" echo # strlen echo echo "�������� ������� "strlen" :" str=123456789 echo ""str" = $str" echo -n "����� ������ "str" = " strlen str echo # ����������: # --------- # �������� �������� ��������� �������. exit 0
Michael Zick ����������� ����� ������� ������ ������ �
��������� � �������� md5sum, ������������ ���
����������� �������� � ��������.
�� �����������:
� ������ ����� �������� ����������, ��� ������� ������������
�������� ��� �� «�� �����», ������� �������� ����
�������� ��� ��������.
������ A-21. Directory information
#! /bin/bash # directory-info.sh # Parses and lists directory information. # NOTE: Change lines 273 and 353 per "README" file. # Michael Zick is the author of this script. # Used here with his permission. # Controls # If overridden by command arguments, they must be in the order: # Arg1: "Descriptor Directory" # Arg2: "Exclude Paths" # Arg3: "Exclude Directories" # # Environment Settings override Defaults. # Command arguments override Environment Settings. # Default location for content addressed file descriptors. MD5UCFS=${1:-${MD5UCFS:-'/tmpfs/ucfs'}} # Directory paths never to list or enter declare -a EXCLUDE_PATHS=${2:-${EXCLUDE_PATHS:-'(/proc /dev /devfs /tmpfs)'}} # Directories never to list or enter declare -a EXCLUDE_DIRS=${3:-${EXCLUDE_DIRS:-'(ucfs lost+found tmp wtmp)'}} # Files never to list or enter declare -a EXCLUDE_FILES=${3:-${EXCLUDE_FILES:-'(core "Name with Spaces")'}} # Here document used as a comment block. : << LSfieldsDoc # # # # # List Filesystem Directory Information # # # # # # # ListDirectory "FileGlob" "Field-Array-Name" # or # ListDirectory -of "FileGlob" "Field-Array-Filename" # '-of' meaning 'output to filename' # # # # # String format description based on: ls (GNU fileutils) version 4.0.36 Produces a line (or more) formatted: inode permissions hard-links owner group ... 32736 -rw------- 1 mszick mszick size day month date hh:mm:ss year path 2756608 Sun Apr 20 08:53:06 2003 /home/mszick/core Unless it is formatted: inode permissions hard-links owner group ... 266705 crw-rw---- 1 root uucp major minor day month date hh:mm:ss year path 4, 68 Sun Apr 20 09:27:33 2003 /dev/ttyS4 NOTE: that pesky comma after the major number NOTE: the 'path' may be multiple fields: /home/mszick/core /proc/982/fd/0 -> /dev/null /proc/982/fd/1 -> /home/mszick/.xsession-errors /proc/982/fd/13 -> /tmp/tmpfZVVOCs (deleted) /proc/982/fd/7 -> /tmp/kde-mszick/ksycoca /proc/982/fd/8 -> socket:[11586] /proc/982/fd/9 -> pipe:[11588] If that isn't enough to keep your parser guessing, either or both of the path components may be relative: ../Built-Shared -> Built-Static ../linux-2.4.20.tar.bz2 -> ../../../SRCS/linux-2.4.20.tar.bz2 The first character of the 11 (10?) character permissions field: 's' Socket 'd' Directory 'b' Block device 'c' Character device 'l' Symbolic link NOTE: Hard links not marked - test for identical inode numbers on identical filesystems. All information about hard linked files are shared, except for the names and the name's location in the directory system. NOTE: A "Hard link" is known as a "File Alias" on some systems. '-' An undistingushed file Followed by three groups of letters for: User, Group, Others Character 1: '-' Not readable; 'r' Readable Character 2: '-' Not writable; 'w' Writable Character 3, User and Group: Combined execute and special '-' Not Executable, Not Special 'x' Executable, Not Special 's' Executable, Special 'S' Not Executable, Special Character 3, Others: Combined execute and sticky (tacky?) '-' Not Executable, Not Tacky 'x' Executable, Not Tacky 't' Executable, Tacky 'T' Not Executable, Tacky Followed by an access indicator Haven't tested this one, it may be the eleventh character or it may generate another field ' ' No alternate access '+' Alternate access LSfieldsDoc ListDirectory() { local -a T local -i of=0 # Default return in variable # OLD_IFS=$IFS # Using BASH default ' tn' case "$#" in 3) case "$1" in -of) of=1 ; shift ;; * ) return 1 ;; esac ;; 2) : ;; # Poor man's "continue" *) return 1 ;; esac # NOTE: the (ls) command is NOT quoted (") T=( $(ls --inode --ignore-backups --almost-all --directory --full-time --color=none --time=status --sort=none --format=long $1) ) case $of in # Assign T back to the array whose name was passed as $2 0) eval $2=( "${T[@]}" ) ;; # Write T into filename passed as $2 1) echo "${T[@]}" > "$2" ;; esac return 0 } # # # # # Is that string a legal number? # # # # # # # IsNumber "Var" # # # # # There has to be a better way, sigh... IsNumber() { local -i int if [ $# -eq 0 ] then return 1 else (let int=$1) 2>/dev/null return $? # Exit status of the let thread fi } # # # # # Index Filesystem Directory Information # # # # # # # IndexList "Field-Array-Name" "Index-Array-Name" # or # IndexList -if Field-Array-Filename Index-Array-Name # IndexList -of Field-Array-Name Index-Array-Filename # IndexList -if -of Field-Array-Filename Index-Array-Filename # # # # # : << IndexListDoc Walk an array of directory fields produced by ListDirectory Having suppressed the line breaks in an otherwise line oriented report, build an index to the array element which starts each line. Each line gets two index entries, the first element of each line (inode) and the element that holds the pathname of the file. The first index entry pair (Line-Number==0) are informational: Index-Array-Name[0] : Number of "Lines" indexed Index-Array-Name[1] : "Current Line" pointer into Index-Array-Name The following index pairs (if any) hold element indexes into the Field-Array-Name per: Index-Array-Name[Line-Number * 2] : The "inode" field element. NOTE: This distance may be either +11 or +12 elements. Index-Array-Name[(Line-Number * 2) + 1] : The "pathname" element. NOTE: This distance may be a variable number of elements. Next line index pair for Line-Number+1. IndexListDoc IndexList() { local -a LIST # Local of listname passed local -a -i INDEX=( 0 0 ) # Local of index to return local -i Lidx Lcnt local -i if=0 of=0 # Default to variable names case "$#" in # Simplistic option testing 0) return 1 ;; 1) return 1 ;; 2) : ;; # Poor man's continue 3) case "$1" in -if) if=1 ;; -of) of=1 ;; * ) return 1 ;; esac ; shift ;; 4) if=1 ; of=1 ; shift ; shift ;; *) return 1 esac # Make local copy of list case "$if" in 0) eval LIST=( "${$1[@]}" ) ;; 1) LIST=( $(cat $1) ) ;; esac # Grok (grope?) the array Lcnt=${#LIST[@]} Lidx=0 until (( Lidx >= Lcnt )) do if IsNumber ${LIST[$Lidx]} then local -i inode name local ft inode=Lidx local m=${LIST[$Lidx+2]} # Hard Links field ft=${LIST[$Lidx+1]:0:1} # Fast-Stat case $ft in b) ((Lidx+=12)) ;; # Block device c) ((Lidx+=12)) ;; # Character device *) ((Lidx+=11)) ;; # Anything else esac name=Lidx case $ft in -) ((Lidx+=1)) ;; # The easy one b) ((Lidx+=1)) ;; # Block device c) ((Lidx+=1)) ;; # Character device d) ((Lidx+=1)) ;; # The other easy one l) ((Lidx+=3)) ;; # At LEAST two more fields # A little more elegance here would handle pipes, #+ sockets, deleted files - later. *) until IsNumber ${LIST[$Lidx]} || ((Lidx >= Lcnt)) do ((Lidx+=1)) done ;; # Not required esac INDEX[${#INDEX[*]}]=$inode INDEX[${#INDEX[*]}]=$name INDEX[0]=${INDEX[0]}+1 # One more "line" found # echo "Line: ${INDEX[0]} Type: $ft Links: $m Inode: # ${LIST[$inode]} Name: ${LIST[$name]}" else ((Lidx+=1)) fi done case "$of" in 0) eval $2=( "${INDEX[@]}" ) ;; 1) echo "${INDEX[@]}" > "$2" ;; esac return 0 # What could go wrong? } # # # # # Content Identify File # # # # # # # DigestFile Input-Array-Name Digest-Array-Name # or # DigestFile -if Input-FileName Digest-Array-Name # # # # # # Here document used as a comment block. : <<DigestFilesDoc The key (no pun intended) to a Unified Content File System (UCFS) is to distinguish the files in the system based on their content. Distinguishing files by their name is just, so, 20th Century. The content is distinguished by computing a checksum of that content. This version uses the md5sum program to generate a 128 bit checksum representative of the file's contents. There is a chance that two files having different content might generate the same checksum using md5sum (or any checksum). Should that become a problem, then the use of md5sum can be replace by a cyrptographic signature. But until then... The md5sum program is documented as outputting three fields (and it does), but when read it appears as two fields (array elements). This is caused by the lack of whitespace between the second and third field. So this function gropes the md5sum output and returns: [0] 32 character checksum in hexidecimal (UCFS filename) [1] Single character: ' ' text file, '*' binary file [2] Filesystem (20th Century Style) name Note: That name may be the character '-' indicating STDIN read. DigestFilesDoc DigestFile() { local if=0 # Default, variable name local -a T1 T2 case "$#" in 3) case "$1" in -if) if=1 ; shift ;; * ) return 1 ;; esac ;; 2) : ;; # Poor man's "continue" *) return 1 ;; esac case $if in 0) eval T1=( "${$1[@]}" ) T2=( $(echo ${T1[@]} | md5sum -) ) ;; 1) T2=( $(md5sum $1) ) ;; esac case ${#T2[@]} in 0) return 1 ;; 1) return 1 ;; 2) case ${T2[1]:0:1} in # SanScrit-2.0.5 *) T2[${#T2[@]}]=${T2[1]:1} T2[1]=* ;; *) T2[${#T2[@]}]=${T2[1]} T2[1]=" " ;; esac ;; 3) : ;; # Assume it worked *) return 1 ;; esac local -i len=${#T2[0]} if [ $len -ne 32 ] ; then return 1 ; fi eval $2=( "${T2[@]}" ) } # # # # # Locate File # # # # # # # LocateFile [-l] FileName Location-Array-Name # or # LocateFile [-l] -of FileName Location-Array-FileName # # # # # # A file location is Filesystem-id and inode-number # Here document used as a comment block. : <<StatFieldsDoc Based on stat, version 2.2 stat -t and stat -lt fields [0] name [1] Total size File - number of bytes Symbolic link - string length of pathname [2] Number of (512 byte) blocks allocated [3] File type and Access rights (hex) [4] User ID of owner [5] Group ID of owner [6] Device number [7] Inode number [8] Number of hard links [9] Device type (if inode device) Major [10] Device type (if inode device) Minor [11] Time of last access May be disabled in 'mount' with noatime atime of files changed by exec, read, pipe, utime, mknod (mmap?) atime of directories changed by addition/deletion of files [12] Time of last modification mtime of files changed by write, truncate, utime, mknod mtime of directories changed by addtition/deletion of files [13] Time of last change ctime reflects time of changed inode information (owner, group permissions, link count -*-*- Per: Return code: 0 Size of array: 14 Contents of array Element 0: /home/mszick Element 1: 4096 Element 2: 8 Element 3: 41e8 Element 4: 500 Element 5: 500 Element 6: 303 Element 7: 32385 Element 8: 22 Element 9: 0 Element 10: 0 Element 11: 1051221030 Element 12: 1051214068 Element 13: 1051214068 For a link in the form of linkname -> realname stat -t linkname returns the linkname (link) information stat -lt linkname returns the realname information stat -tf and stat -ltf fields [0] name [1] ID-0? # Maybe someday, but Linux stat structure [2] ID-0? # does not have either LABEL nor UUID # fields, currently information must come # from file-system specific utilities These will be munged into: [1] UUID if possible [2] Volume Label if possible Note: 'mount -l' does return the label and could return the UUID [3] Maximum length of filenames [4] Filesystem type [5] Total blocks in the filesystem [6] Free blocks [7] Free blocks for non-root user(s) [8] Block size of the filesystem [9] Total inodes [10] Free inodes -*-*- Per: Return code: 0 Size of array: 11 Contents of array Element 0: /home/mszick Element 1: 0 Element 2: 0 Element 3: 255 Element 4: ef53 Element 5: 2581445 Element 6: 2277180 Element 7: 2146050 Element 8: 4096 Element 9: 1311552 Element 10: 1276425 StatFieldsDoc # LocateFile [-l] FileName Location-Array-Name # LocateFile [-l] -of FileName Location-Array-FileName LocateFile() { local -a LOC LOC1 LOC2 local lk="" of=0 case "$#" in 0) return 1 ;; 1) return 1 ;; 2) : ;; *) while (( "$#" > 2 )) do case "$1" in -l) lk=-1 ;; -of) of=1 ;; *) return 1 ;; esac shift done ;; esac # More Sanscrit-2.0.5 # LOC1=( $(stat -t $lk $1) ) # LOC2=( $(stat -tf $lk $1) ) # Uncomment above two lines if system has "stat" command installed. LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11} ${LOC2[@]:1:2} ${LOC2[@]:4:1} ) case "$of" in 0) eval $2=( "${LOC[@]}" ) ;; 1) echo "${LOC[@]}" > "$2" ;; esac return 0 # Which yields (if you are lucky, and have "stat" installed) # -*-*- Location Discriptor -*-*- # Return code: 0 # Size of array: 15 # Contents of array # Element 0: /home/mszick 20th Century name # Element 1: 41e8 Type and Permissions # Element 2: 500 User # Element 3: 500 Group # Element 4: 303 Device # Element 5: 32385 inode # Element 6: 22 Link count # Element 7: 0 Device Major # Element 8: 0 Device Minor # Element 9: 1051224608 Last Access # Element 10: 1051214068 Last Modify # Element 11: 1051214068 Last Status # Element 12: 0 UUID (to be) # Element 13: 0 Volume Label (to be) # Element 14: ef53 Filesystem type } # And then there was some test code ListArray() # ListArray Name { local -a Ta eval Ta=( "${$1[@]}" ) echo echo "-*-*- List of Array -*-*-" echo "Size of array $1: ${#Ta[*]}" echo "Contents of array $1:" for (( i=0 ; i<${#Ta[*]} ; i++ )) do echo -e "tElement $i: ${Ta[$i]}" done return 0 } declare -a CUR_DIR # For small arrays ListDirectory "${PWD}" CUR_DIR ListArray CUR_DIR declare -a DIR_DIG DigestFile CUR_DIR DIR_DIG echo "The new "name" (checksum) for ${CUR_DIR[9]} is ${DIR_DIG[0]}" declare -a DIR_ENT # BIG_DIR # For really big arrays - use a temporary file in ramdisk # BIG-DIR # ListDirectory -of "${CUR_DIR[11]}/*" "/tmpfs/junk2" ListDirectory "${CUR_DIR[11]}/*" DIR_ENT declare -a DIR_IDX # BIG-DIR # IndexList -if "/tmpfs/junk2" DIR_IDX IndexList DIR_ENT DIR_IDX declare -a IDX_DIG # BIG-DIR # DIR_ENT=( $(cat /tmpfs/junk2) ) # BIG-DIR # DigestFile -if /tmpfs/junk2 IDX_DIG DigestFile DIR_ENT IDX_DIG # Small (should) be able to parallize IndexList & DigestFile # Large (should) be able to parallize IndexList & DigestFile & the assignment echo "The "name" (checksum) for the contents of ${PWD} is ${IDX_DIG[0]}" declare -a FILE_LOC LocateFile ${PWD} FILE_LOC ListArray FILE_LOC exit 0
Stephane Chazelas ������������� ����������� ��������
���������������� ������� � ���������������� � Bash-���������.
������ A-22. �������� ��������������� ����
������
#!/bin/bash # obj-oriented.sh: �������� �������������� ������ � ���������������� � ���������. # �����: Stephane Chazelas. person.new() # ����� ������ �� ���������� ������ � C++. { local obj_name=$1 name=$2 firstname=$3 birthdate=$4 eval "$obj_name.set_name() { eval "$obj_name.get_name() { echo $1 }" }" eval "$obj_name.set_firstname() { eval "$obj_name.get_firstname() { echo $1 }" }" eval "$obj_name.set_birthdate() { eval "$obj_name.get_birthdate() { echo $1 }" eval "$obj_name.show_birthdate() { echo $(date -d "1/1/1970 0:0:$1 GMT") }" eval "$obj_name.get_age() { echo $(( ($(date +%s) - $1) / 3600 / 24 / 365 )) }" }" $obj_name.set_name $name $obj_name.set_firstname $firstname $obj_name.set_birthdate $birthdate } echo person.new self Bozeman Bozo 101272413 # ��������� ��������� ������ "person.new" (���������� -- ����� ������� � �����������). self.get_firstname # Bozo self.get_name # Bozeman self.get_age # 28 self.get_birthdate # 101272413 self.show_birthdate # Sat Mar 17 20:13:33 MST 1973 echo # typeset -f # ����� ����������� �������� ��������� �������. exit 0