Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
unix_shell_lab2.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
262.66 Кб
Скачать

операционные системы. UNIX Shell

§2 Unix Shell – лабораторные работы

Для выполнения лабораторной работы из любого раздела необходимо: 1) изучить соответствующий теоретический материал; 2) выполнить задание; 3) подготовить ответы на контрольные вопросы; 4) защитить работу, продемонстрировав ее преподавателю и ответив на его вопросы.

    1. Лабораторная работа №2: Написание скриптов. Практика использования Unix Shell. Обработка журнальных файлов.

      1. Некоторые базовые элементы и средства языка Unix Shell

Переменные

Как и большинство интерпретируемых языков программирования, Shell не поддерживает типы данных. Это позволяет программисту по-разному интерпретировать значение переменной в зависимости от ситуации. Все необходимые преобразования по приведению типов будут выполнены интерпретатором Shell автоматически. Такой подход существенно упрощает скрипты и повышает скорость их разработки. Минусом являются накладные расходы при неявном преобразовании типов.

Именем переменной может быть любая комбинация символов, не являющаяся зарезервированным словом (например if, for и т.д.).

В качестве оператора присваивания используется знак = (пробелы до и после знака не допускаются)

A=5; B=”test”;

Для получения значения переменной используется знак $.

A=4; B=$A; С=${B}; echo $A;

(В переменную А помещаем значение 4, в переменную B помещаем значение переменной А, в переменную C помещаем значение переменной B, выводим на консоль значение переменной А – будет выдано: 4)

Можно заключать имя переменной в фигурные скобки. Полезно в тех случаях, когда необходимо отделить имя переменной от следующего за ним текста.

A=10; echo “${A}ka

(На экран будет выведено: 10ka. Если опустить фигурные скобки, то интерпретатор будет считать, что знак $ относится к строчке Aka, т.е. мы работаем со значением переменной “Aka”, а не “A”)

Длину переменной в символах можно получить при помощи ${#NAME}

A=”this is a long sequence”; echo “length of A = ${#A}”

(На экран будет выведено: length of A = 23)

Переменная создается автоматически при присваивании ей значения. Можно (но не обязательно) предварительно объявить переменную при помощи встроенной команды Shell declare.

declare A

declarea B

A=5; B[0]=”test

(создаем переменную А и массив B, заносим значение 5 в А и строчку test в нулевой элемент массива B)

Переменные окружения

Существует набор заранее определенных служебных переменных, которые называются «окружением» или «переменными окружения». Эти переменные содержат множество полезной для программ служебной информации, например: имя машины (HOSTNAME), версия ОС (OSTYPE), имя пользователя (USER), пути поиска исполняемых программ (PATH), настройки локализации (LANG), параметры консоли (COLUMNS, LINES) и т.д.

Список всех переменных окружения вместе с их значениями можно получить при помощи команды set.

Переменные окружения являются одним из способов организации передачи информации от родительского процесса к дочернему.

Программы (в том числе и скрипты на Shell) могут изменять значения переменных окружения, создавать новые переменные со своими значениями, и т.д. Все переменные по умолчанию «видны» только внутри того скрипта, где они были созданы и использованы. Для того, чтобы переменная была доступна дочернему процессу, необходимо предварительно выполнить экспортирование переменной, используя встроенную команду export, после чего можно запускать какой-либо дочерний процесс (например, другую программу или скрипт) – ему будет доступна переменная со своим значением. В противном случае (без предварительного экспортирования) дочерний процесс не увидит переменной.

export NAME=”Vasily Pupkin”

NAME2=”Ivan Ivanov”; export NAME2

./child_script.sh

(Переменным NAME и NAME2 присваиваются значения, после чего они экспортируются и запускается программа child_script.sh, для которой эти переменные будут доступны)

Некоторые специальные переменные

Shell поддерживает множество специальных переменных. Некоторые из них:

$? – код возврата. Хранит значение кода возврата предыдущей выполненной команды (программы). Часто используется для контроля за возникновением ошибок. Если код = 0, считается, что программа выполнилась успешно, это соглашение обычно выполняется для всех программ. Ненулевое значение говорит об ошибке. Разные ненулевые коды возврата могут означать различные ошибки в ходе выполнения программы и обычно описываются автором в документации. Один и тот же ненулевой код возврата в разных программах может означать совершенно разные события, выбор целиком зависит от фантазии и желания авторов.

$@ – переменная, хранящая одной строкой все параметры командной строки, переданные скрипту.

$0, $1, $2, $3, … – каждая из переменных хранит соответствующий ее номеру параметр командной строки, переданный скрипту. Счет параметров начинается с единицы. $0 содержит имя исполняемого файла самого скрипта.

$# – содержит количество параметров, переданных скрипту. Часто используется при разборе параметров: зная количество параметров можно для считывания и обработки параметров запустить цикл for, который выполнится $# раз

$_ – содержит имя последней выполненной команды (программы)

$RANDOM – специальная переменная, которая возвращает случайное число при каждом обращении

Массивы

Массив может быть определен с помощью declarea <ИМЯ ПЕРЕМЕННОЙ> или при непосредственном присваивании значения элементам массива.

Индекс массива указывается в квадратных скобках. Элементы могут быть разного типа.

A[0]=5; A[1]=10; echoA = ${A[0]} ${A[1]}”

(На экран будет выведено A = 5 10)

B[0]=5; B[1]=”test word”; echo “B = ${B[0]} ${B[1]}”

(На экран будет выведено B = 5 test word)

Специальный индекс * позволяет получить значение всех элементов массива одной строкой:

B[0]=5; B[1]=”test word”; echo “B = ${B[*]}”

(На экран будет выведено B = 5 test word)

Специальное обозначение ${#NAME[*]} позволяет получить количество элементов массива:

B[0]=5; B[1]=”test word”; echo “Array B has ${#B[*]} elements”

(На экран будет выведено Array B has 2 elements)

Арифметические выражения

Оболочка bash может производить арифметические вычисления в целых числах (не работает с числами с плавающей точкой). Для вычисления выражения его необходимо поместить внутрь конструкции $[ ].

A=2; B=3; C=$[${A} + 2*${B}]; echo ${C}

(В переменную С помещается результат сложения значения переменной А и удвоенного значения переменной B. На экран будет выведено: 8)

Кавычки

Знаки кавычек играют важную роль в Shell! Существует 3 типа кавычек:

Двойные кавычки: “”. Используются для запрета интерпретации специальных символов (см. Лаб. №1). Знак $ будет действовать внутри строки, заключенной в двойные кавычки.

[mike@bublik ~]$ a=5; echo "a=$a"

a=5

[mike@bublik ~]$

Одинарные кавычки: ‘’ Действуют «еще сильнее» двойных кавычек. Внутри одинарных кавычек не интерпретируются никакие символы или последовательности, строчка целиком воспринимается «как есть».

[mike@bublik ~]$ a=5; echo ‘a=$a’

a=$a

[mike@bublik ~]$

Обратные кавычки: `` В то место, где находится строчка, заключенная в обратные кавычки, подставляется результат ее выполнения (содержимое строки рассматривается Shell-ом как команда и исполняется)

[mike@bublik ~]$ date "+%d/%m/%Y %H:%M:%S"

03/03/2009 07:39:41

[mike@bublik ~]$ echo "Today is: `date "+%d/%m/%Y %H:%M:%S"`"

Today is: 03/03/2009 07:40:03

[mike@bublik ~]$

(Замечание: строка-параметр "+%d/%m/%Y %H:%M:%S" задает формат вывода данных командой date, что видно из примера. Подробнее см. man date)

Перенаправление ввода-вывода

Перенаправление и конвейер позволяют сохранять вывод программ в файлы, использовать файлы как источник для ввода данных из них в программы, организовать взаимодействие двух и более программ, передачу данных от предыдущей к следующей по цепочке и т.д. Реальное практическое применение Unix Shell невозможно без использования методов перенаправления потоков.

Любая программа (даже не работающая с файлами) всегда связана с тремя файлами (потоками):

  1. Стандартный поток ввода. Имеет файловый дескриптор = 0. Часто называется stdin

  2. Стандартный поток вывода. Имеет файловый дескриптор = 1. Часто называется stdout

  3. Стандартный поток ошибок. Имеет файловый дескриптор = 2. Часто называется stderr

По умолчанию, все три дескриптора консольной программы связаны с терминалом: ввод данных на stdin производится с клавиатуры, при выводе данных на stdout и/или stderr информация попадает в терминал и отображается в терминальном окне.

Средства перенаправления Shell – это гибкий, прозрачный и незаметный для самой программы механизм, при помощи которого возможно изменение привязки дескрипторов, что позволяет получать входные данные на stdin из других источников (например, из файлов или от другой программы) и отправлять информацию, выдаваемую программой на stdout и/или stderr в альтернативный приемник (например, в файл, на другие дескрипторы или на stdin другой программы).

Перенаправление вывода:

  1. [m]>name – перенаправить информацию, выводимую программой в поток с дескриптором №m, в файл с именем name. Если файл уже существовал до этого, то его содержимое будет перезаписано новыми данными. Если файл не существовал, то он будет создан, и данные будут записаны в него. Если m опущено ( >name), то полагается m=1, т.е. в файл перенаправляется информация, выводимая на stdout.

  1. [m]>>name – перенаправить информацию, выводимую программой в поток с дескриптором №m, в файл с именем name. Если файл уже существовал до этого, то его содержимое будет дополнено новыми данными. Если файл не существовал, то он будет создан, и данные будут записаны в него. Если m опущено ( >>name), то полагается m=1, т.е. в файл перенаправляется информация, выводимая на stdout.

  1. [m]>&n – перенаправить («перенести», а не «скопировать») информацию, выводимую программой на дескриптор №m, в поток с дескриптором №n. Если m опущено ( >&n), то полагается m=1, т.е. в поток с дескриптором №n перенаправляется информация, выводимая программой на stdout.

Серия примеров, демонстрирующих перенаправление вывода (примеры связаны, необходимо смотреть все по порядку сверху вниз):

(Создаем файл /tmp/test.txt)

[mike@bublik ~]$ touch /tmp/test.txt

(Проверяем, что файл создался. Программа ls выдает сообщение на stdout! Вывод идет на консоль, пока невозможно визуально определить, на какой именно дескриптор были выведены данные)

[mike@bublik ~]$ ls -1 /tmp/test.txt

/tmp/test.txt

(Проверяем существование файла /tmp/test2.txt. Файл не существует, сообщение об этом программа ls выдает на stderr! Вывод идет на консоль, пока невозможно визуально определить, на какой именно дескриптор были выведены данные)

[mike@bublik ~]$ ls -1 /tmp/test2.txt

ls: /tmp/test2.txt: No such file or directory

Выполняем те же действия, поочередно перенаправляя stdout и stderr в файлы:

(Перенаправляем stdout в файл /tmp/res1_1.txt, на консоль ничего не выдается, весь вывод перенаправился в файл)

[mike@bublik ~]$ ls -1 /tmp/test.txt >/tmp/res1_1.txt

(Перенаправляем stderr в файл /tmp/res1_2.txt, информация выдается на консоль, т.к. программа ls выводит ее в stdout, а не stderr)

[mike@bublik ~]$ ls -1 /tmp/test.txt 2>/tmp/res1_2.txt

/tmp/test.txt

(Перенаправляем stdout в файл /tmp/res2_1.txt, информация выдается на консоль, т.к. программа ls выводит ее в stderr, а не stdout)

[mike@bublik ~]$ ls -1 /tmp/test2.txt >/tmp/res2_1.txt

ls: /tmp/test2.txt: No such file or directory

(Перенаправляем stderr в файл /tmp/res2_2.txt, на консоль ничего не попадает, весь вывод ушел в файл)

[mike@bublik ~]$ ls -1 /tmp/test2.txt 2>/tmp/res2_2.txt

(Проверяем результаты: одни из созданных файлов оказались пустыми, т.к. в них не была перенаправлено ничего, в других сохранились перенаправленные данные)

[mike@bublik ~]$ cat /tmp/res1_1.txt

/tmp/test.txt

[mike@bublik ~]$ cat /tmp/res1_2.txt

[mike@bublik ~]$ cat /tmp/res2_1.txt

[mike@bublik ~]$ cat /tmp/res2_2.txt

ls: /tmp/test2.txt: No such file or directory

(Пробуем прочитать информацию о несуществующем файле /tmp/test2.txt, при этом ls выдаст сообщение на stderr, после чего информация с дескриптора №2 (stderr) переносится в дескриптор №1 (stdout), затем весь stdout перенаправляется в файл /tmp/res3.txt. Как видно, в результате в файле /tmp/res3.txt сохраняется сообщение, выданное ls)

[mike@bublik ~]$ ls -1 /tmp/test2.txt >/tmp/res3.txt 2>&1

[mike@bublik ~]$ cat /tmp/res3.txt

ls: /tmp/test2.txt: No such file or directory

[mike@bublik ~]$

Перенаправление ввода:

  1. [m]<nameперенаправить содержимое файла с именем name в поток программы с дескриптором №m. Если m опущено ( <name), то полагается m=0, т.е. содержимое файла name попадает в stdin программы.

  1. [m]<<wordперенаправлять текст, поступающий на stdin программы в поток программы с дескриптором №m до тех пор, пока в тексте не встретится слово word. Если m опущено ( <<word), то подразумевается m=0, т.е. текст перенаправляется с stdin в stdin программы (при m=0 метод может показаться глупым и ненужным, однако на самом деле он представляет собой весьма удобный прием, часто применяемый в скриптах для ввода большого количества текста на stdin программы, см. примеры)

Примеры, демонстрирующие перенаправление ввода (необходимо смотреть все и по порядку сверху вниз):

(Для начала запишем строчку в файл xx.txt)

[mike@bublik ~]$ echo "test string" >xx.txt

(Способ №1: применяем конвейер (про конвейер см. далее) для передачи содержимого файла xx.txt программе wc. Программа wc считает количество байт, поступивших на stdin и выводит число)

[mike@bublik ~]$ cat xx.txt | wc -c

12

(Способ №2: вместо запуска cat и передачи информации через конвейер выполним перенаправление ввода для передачи содержимого файла программе wc. Программа wc считает количество байт, поступивших на stdin и выводит число)

[mike@bublik ~]$ wc -c < xx.txt

12

(Программа cat получает данные с stdin до тех пор, пока не встретится слово END. Далее вывод программы cat перенаправляется в файл newfile.txt. В этом примере сами данные вводятся с клавиатуры)

[mike@bublik ~]$ cat >newfile.txt <<END

> test!

> END

(Проверка: вывод содержимого файла newfile.txt)

[mike@bublik ~]$ cat newfile.txt

test!

Другой вариант применения “<<” – в тексте внутри скрипта:

Способ №1 (без “<<”, при помощи многочисленных команд echo):

echo “Перенаправление и конвейер позволяют сохранять вывод программ”

echo “в файлы, использовать файлы как источник для ввода данных “

echo “из них в программы, организовать взаимодействие двух и “

echo “более программ, передачу данных от предыдущей к следующей “

echo “по цепочке и т.д.”

недостаток – необходимость писать команду echo для каждой строки (строк может быть много!!!), заключать каждую строчку в кавычки.

Способ №2 (с применением перенаправления ввода “<<”):

cat <<END

Перенаправление и конвейер позволяют сохранять вывод программ

в файлы, использовать файлы как источник для ввода данных

из них в программы, организовать взаимодействие двух и

более программ, передачу данных от предыдущей к следующей

по цепочке и т.д.

END

Очевидно, способ №2 более удобный. Текст пишется «как есть» внутри скрипта, после него дописывается признак завершения (в данном случае - строка “END”), все данные поступают на stdin команды cat, которая выводит их на экран.

Результат выполнения скриптов, описанных в обоих способах будет одним и тем же – текст выводится на экран.

Конвейер:

Символ конвейера “|” предназначен для связывания двух программ путем перенаправления потока stdout предыдущей команды в поток stdin следующей команды. Эта цепочка может быть продолжена и далее, т.о. конвейер позволяет выполнять достаточно сложные действия, связывая множество простых программ-компонентов путем передачи результатов работы предыдущей программы следующей программе и т.д. (МОДУЛЬНОСТЬ, ОДИН ИЗ ОСНОВОПОЛАГАЮЩИХ ПРИНЦИПОВ ОРГАНИЗАЦИИ UNIX-подобных систем)

Задача подсчета статистики входов пользователей в систему – несколько примеров, демонстрирующих использование конвейера:

Обратите внимание, в примерах постепенно шаг за шагом собирается цепочка из нескольких команд, связанных конвейерами, в каждом следующем примере к предыдущей цепочке добавляется новое звено и демонстрируется результат работы:

(Шаг №1. Команда last выводит информацию о входах пользователей в систему в виде множества строк, каждая строка содержит информацию об одном событии регистрации пользователя в системе)

[mike@bublik ~]$ last

mike ttyp1 10.10.65.11 вт 3 мар 02:34 still logged in

p73_13 ttyp2 89.251.107.24 вт 3 мар 01:38 - 01:45 (00:06)

mike ttyp1 10.10.65.11 вт 3 мар 01:00 - 02:34 (01:33)

mike ttyp3 10.10.65.11 вт 3 мар 00:12 - 00:56 (00:44)

p74_03 ttyp2 91.196.246.2 пн 2 мар 23:26 - 00:50 (01:24)

p74_03 ttyp2 91.196.246.2 пн 2 мар 23:19 - 23:22 (00:03)

mike ttyp1 10.10.65.11 пн 2 мар 21:51 - 00:12 (02:21)

p73_25 ttyp1 91.196.247.14 пн 2 мар 20:23 - 20:51 (00:27)

p73_25 ttyp1 91.196.247.14 пн 2 мар 20:15 - 20:22 (00:07)

mike ttyp0 10.10.65.11 пн 2 мар 19:23 still logged in

p74_03 ttyp0 91.196.246.2 пн 2 мар 16:30 - 17:08 (00:38)

p72_15 ttyp0 pi46-34-33.cn.ru пн 2 мар 01:33 - 02:03 (00:30)

p72_15 ttyp0 pi46-34-33.cn.ru вс 1 мар 22:32 - 22:40 (00:08)

p72_15 ttyp0 pi46-34-33.cn.ru вс 1 мар 22:27 - 22:29 (00:01)

p72_16 ttyp0 92.127.178.51 вс 1 мар 16:27 - 16:58 (00:30)

wtmp begins воскресенье, 1 марта 2009 г. 16:27:44 (NOVT)

(Шаг №2. При помощи grep убираем из предыдущего вывода ненужное: пустые строки и строки, содержащие подстрочку “wtmp begins”, таким образом остается только информация о пользователях)

[mike@bublik ~]$ last | grep -v -E "(^$|wtmp begins)"

mike ttyp1 10.10.65.11 вт 3 мар 02:34 still logged in

p73_13 ttyp2 89.251.107.24 вт 3 мар 01:38 - 01:45 (00:06)

mike ttyp1 10.10.65.11 вт 3 мар 01:00 - 02:34 (01:33)

mike ttyp3 10.10.65.11 вт 3 мар 00:12 - 00:56 (00:44)

p74_03 ttyp2 91.196.246.2 пн 2 мар 23:26 - 00:50 (01:24)

p74_03 ttyp2 91.196.246.2 пн 2 мар 23:19 - 23:22 (00:03)

mike ttyp1 10.10.65.11 пн 2 мар 21:51 - 00:12 (02:21)

p73_25 ttyp1 91.196.247.14 пн 2 мар 20:23 - 20:51 (00:27)

p73_25 ttyp1 91.196.247.14 пн 2 мар 20:15 - 20:22 (00:07)

mike ttyp0 10.10.65.11 пн 2 мар 19:23 still logged in

p74_03 ttyp0 91.196.246.2 пн 2 мар 16:30 - 17:08 (00:38)

p72_15 ttyp0 pi46-34-33.cn.ru пн 2 мар 01:33 - 02:03 (00:30)

p72_15 ttyp0 pi46-34-33.cn.ru вс 1 мар 22:32 - 22:40 (00:08)

p72_15 ttyp0 pi46-34-33.cn.ru вс 1 мар 22:27 - 22:29 (00:01)

p72_16 ttyp0 92.127.178.51 вс 1 мар 16:27 - 16:58 (00:30)

(Шаг №3. При помощи программы tr убираем дубликаты пробелов – это понадобится для корректной работы программы cut на следующем шаге, т.к. она не считает несколько последовательных разделителей одним разделителем)

[mike@bublik ~]$ last | grep -v -E "(^$|wtmp begins)" | tr -s " "

mike ttyp1 10.10.65.11 вт 3 мар 02:34 still logged in

p73_13 ttyp2 89.251.107.24 вт 3 мар 01:38 - 01:45 (00:06)

mike ttyp1 10.10.65.11 вт 3 мар 01:00 - 02:34 (01:33)

mike ttyp3 10.10.65.11 вт 3 мар 00:12 - 00:56 (00:44)

p74_03 ttyp2 91.196.246.2 пн 2 мар 23:26 - 00:50 (01:24)

p74_03 ttyp2 91.196.246.2 пн 2 мар 23:19 - 23:22 (00:03)

mike ttyp1 10.10.65.11 пн 2 мар 21:51 - 00:12 (02:21)

p73_25 ttyp1 91.196.247.14 пн 2 мар 20:23 - 20:51 (00:27)

p73_25 ttyp1 91.196.247.14 пн 2 мар 20:15 - 20:22 (00:07)

mike ttyp0 10.10.65.11 пн 2 мар 19:23 still logged in

p74_03 ttyp0 91.196.246.2 пн 2 мар 16:30 - 17:08 (00:38)

p72_15 ttyp0 pi46-34-33.cn.ru пн 2 мар 01:33 - 02:03 (00:30)

p72_15 ttyp0 pi46-34-33.cn.ru вс 1 мар 22:32 - 22:40 (00:08)

p72_15 ttyp0 pi46-34-33.cn.ru вс 1 мар 22:27 - 22:29 (00:01)

p72_16 ttyp0 92.127.178.51 вс 1 мар 16:27 - 16:58 (00:30)

(Шаг №4. Используем программу cut для того, чтобы из всего вывода взять только столбец №1, в качестве разделителя столбцов считать пробел)

[mike@bublik ~]$ last | grep -v -E "(^$|wtmp begins)" | tr -s " " | cut -f 1 -d " "

mike

p73_13

mike

mike

p74_03

p74_03

mike

p73_25

p73_25

mike

p74_03

p72_15

p72_15

p72_15

p72_16

(Шаг №5. Полученный на предыдущем этапе текст отправляем на вход программы sort, которая меняет порядок строк и выдает все в лексико-графическом порядке)

[mike@bublik ~]$ last | grep -v -E "(^$|wtmp begins)" | tr -s " " | cut -f 1 -d " " | sort

mike

mike

mike

mike

mike

p72_15

p72_15

p72_15

p72_16

p73_13

p73_25

p73_25

p74_03

p74_03

p74_03

(Шаг №6. Предыдущий вывод отправить программе uniq, которая выкидывает дубликаты строк, при этом для корректной работы необходимо, чтобы повторяющиеся строки шли подряд, а не были раскиданы в тексте – это обеспечивается на предыдущем шаге с помощью sort. Параметр –c у программы uniq заставляет посчитать и вывести перед каждой строкой количество повторений, встреченных в тексте)

[mike@bublik ~]$ last | grep -v -E "(^$|wtmp begins)" | tr -s " " | cut -f 1 -d " " | sort | uniq -c

5 mike

3 p72_15

1 p72_16

1 p73_13

2 p73_25

3 p74_03

(Шаг №7. Предыдущий вывод отсортируем при помощи sort по первому столбцу (-k1), по убыванию (-r), предполагая числовые данные (-n) в том столбце, по которому будем сортировать)

[mike@bublik ~]$ last | grep -v -E "(^$|wtmp begins)" | tr -s " " | cut -f 1 -d " " | sort | uniq -c | sort -rnk1

5 mike

3 p74_03

3 p72_15

2 p73_25

1 p73_13

1 p72_16

[mike@bublik ~]$

Итог: несложная цепочка команд легко выполняет последовательность операций по обработке текста, в результате получаем необходимую статистику входов пользователей в систему.

На создание, отладку и доведение до работоспособного состояния такого скрипта опытному системному администратору требуется не более 1-2 минут.

Заметим, что применение Shell – далеко не единственный способ решения задач. Всегда возможно создание цельной программы с аналогичным функционалом «под конкретную задачу», написанной на каком-либо другом языке, например, на C/C++. Вы можете самостоятельно оценить количество времени, которое нужно будет затратить на разработку в этом случае.