Документация по ОС FreeBSD Четверг, 18.04.2024, 06:30
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
Shell [40]

Главная » Статьи » Программирование » Shell

Работа с командным интерпретатором Shell
Синтаксис языка Bourne Shell

 Работа в современной версии ОС UNIX существенно отличается от работы с ОС UNIX пятнадцатилетней давности. Широкое распространение получил графический интерфейс, множество цветов, всевозможные графические утилиты настройки различных подсистем и т.д. Тем не менее, основой основ пользовательской среды и соответственно взаимодействия между пользователем и ОС является командная строка. Любому пользователю UNIX рано или поздно придется столкнуться с командным интерпретатором.

Командный интерпретатор shell

  В настоящее время все версии ОС UNIX включают в себя как минимум несколько различных командных интерпретаторов, такие как Bourne Shell (/bin/sh), C Shell (/bin/csh) и Korn Shell (/bin/ksh). Для систем UNIX, базирующихся на ядре Linux распространение так же получил командный интерпретатор Bourne Again Shell (bash). Роль командного интерпретатора в любой версии UNIX огромна. Перечислим основные значения для ОС UNIX командного интерпретатора.

1. При регистрации пользователя в системе первой запускаемой программой вляется командный интерпретатор. С него начинается "жизнь" всех остальных пользовательских процессов. Рассмотрим более подробно примерную схему работы пользователя в ОС UNIX.

После запуска ОС инициализирует процесс getty(1M) (сервер терминального доступа), запускающий программу login(1), запрашивающую у пользователя имя и пароль для входа в систему.

Если пользователь ввел корректные имя и пароль, login(1) запустит программу, путь к которой указан в последнем поле файла passwd, как правило, являющейся командным интерпретатором.

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

После завершения работы командного интерпретатора (например командой exit) пользователь завершает свою работу в системе.

2. Командный интерпретатор - это удобный и простой язык программирования очень высокого уровня, позволяющий конструировать из различных утилит UNIX сложные структуры. Исполняемые файлы, содержащие команды Shell, называются скриптами.

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

Командный интерпретатор

Bourne Shell     .profile

C Shell            .login и .cshrc

Korn Shell        .login и .kshrc

Bourne Again Shell  .profile и .bashrc

  Скрипты .profile и .login выполняются командным интерпретатором один раз при первом входе пользователя в систему, а .cshrc, .kshrc и .bashrc выполняются при каждом запуске командного интерпретатора.

4. Вся инициализация ОС UNIX после загрузки ядра осуществляется с помощью инициализационных скриптов shell. Для внесения изменений в конфигурацию системы придется изучить и понимать то, что творится в этих скриптах.

Синтаксис языка Bourne Shell

  Bourne Shell является достаточно развитым языком программирования даже для выполнения достаточно сложных заданий. Тем не менее следует помнить, что из за особенностей работы скриптов shell (аналогично вводу с пользовательского терминала) не следует ждать высокой скорости обработки. Для этих задач существует например язык Си.

Общий синтаксис скрипта.

  Синтаксис вызова команды из командного интерпретатора в самом общем случае обычно выглядит следующим образом:

  <command> <flags> <arguments>

  $ ls -l /home/user1

  Признаком флага является знак "-" перед флагом. Опять таки данное описание не совсем точно, как и в любой другой системе есть исключения. Например, такие команды как dd, find и т.д., синтаксис вызова которых сложился исторически, имеют совершенно иной принцип передачи флагов.

  Командный интерпретатор позволяет группировать команды.

  cmd1; cmd2 Последовательное выполнение команд

  cmd1 & Выполнение в фоновом режиме (демон)

  cmd1 && cmd2 Выполнение cmd2 в случае успешного завершения cmd1

  cmd1 || cmd2 Выполнение cmd2 в случае ненормального завершения cmd1

  Для группировки команд можно также использовать скобки "{}".

  cmd1 && cmd2; cmd3

cmd2 будет выполнена только при условии успешного завершения cmd1, и не зависимо от этого будет далее выполнена cmd3.

  cmd1 && {cmd2; cmd3}

 Обе команды cmd2 и cmd3 будут последовательно выполнены только в случае успешного завершения cmd1.

  Скрипт представляет собой самый обычный текстовый файл. Командный интерпретатор последовательно считывает строки этого файла и последовательно их исполняет. Для того чтобы можно было запустить скрипт на исполнение необходимо иметь права на чтение (командный интерпретатор читает строки файла) и на исполнение (запуск команд и утилит) этого скрипта.

  Как и в других языках программирования, не стоит пренебрегать комментариями при создании текста программы. Комментарием в shell является строка, начинающаяся со знака "#".

  # ЭТО КОММЕНТАРИЙ
  # А ЗДЕСЬ МОГЛА БЫТЬ ВАША РЕКЛАМА :-)))

  Конечно, комментарий не обязательно должен занимать всю строку, например:

  cat /var/run/inetd.pid # вывести на консоль PID интернет-супердемона

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

  #!/bin/sh

 Переменные

  Все переменный в Shell текстовые. Т.е. представляют собой в общем случае строку символов определенной длины. Вобщем то сам по себе Shell может работать только с двумя типами данных, это строка и файл. Тем не менее это не умаляет роль командного интерпретатора в системе. Имя переменной аналогично представлению идентификатора, т.е. может состоять из букв, цифр и знаков подчеркивания, причем начинаться имя переменной может либо со знака подчеркивания либо с буквы, но никак не с цифры. Для присваивания значения переменной используется знак присвоения "=". Для того, чтобы воспользоваться переменной следует перед ее именем поставить знак "$". Использование переменных в Shell называется подстановкой.

  variab1=25 # Не число 25, а строка "25"

  variab2="SYS ADMIN" # Кавычки потому что пробел

  Обратите внимание, что имя переменной и ее значение относительно знака присваивания "=" должно быть записано без пробелов.

  $ echo $variab2
  SYS ADMIN

  Используя утилиту echo(1) выполняем вывод на консоль переменной. Конечно, значение одной переменной можно присвоить другой переменной.

  variab1=$variab2

  Здесь переменной variab1 было присвоено значение переменной variab2. Существуют и иные способы присвоения значений переменным. Одно неизменно - значение перменной это строка. Например, значение переменной можно присвоить используя строку вывода некоторой команды.

  variab3=`cat /var/run/inetd.pid`

  Здесь переменной variab3 присваивается значение вывода, полученной от команды, заключенной в одинарные кавычки. В данном случае значение переменной variab3 будет PID демона inetd. Значение переменной можно получить и интерактивно, т.е. с клавиатуры.

  echo -n "Введите свое имя"
  read username

  При запуске командный интерпретатор выведет строку "Введите свое имя" и будет ждать ввода с клавиатуры. Строка, введенная пользователем, и будет значением переменной username. Вообще говоря, read может присвоить значения сразу нескольким перменным.

  echo -n "Введите свое имя и фамилию"

  read username userfamname

  Если пользователь введет свое имя и фамилию через пробелы, то переменным username и userfamname присвоятся соответственно строки с именем и фамилией пользователя. Если же введенных значений больше чем определено read, они игнорируются. В случае же пустых присвоений, значение переменной также пусто.

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

  $variable Значение variable, или ничего если переменная неопределена.

  ${variable} Аналогично, с отделением имени variable от последующих символов.

  ${variable:-string} Значение variable, если переменная определена. В противном случае string. Значение variable остается неизменным.

  ${variable:=sring} Значение variable, если переменная определена. В противном случае string, при этом variable присваивается значение string.

  ${variable:?string} Так называемая "обязательная переменная". Если variable не определена, выводится строка string и интерпретатор прекращает работу. Если же и строка string пуста, в таком случае выводится variable: parameter not set

  ${variable:+string} Строка string, если переменная variable определена. В противном случае ничего.

  Встроенные переменные

  Кроме переменных, которые можно определить в явном виде, для Shell существуют также встроенные переменные, сокровенный смысл которых существует только в контексте получения ее значения.

  Встроенные переменные

  $1, $2, $3 ... Позиционные параметры скрипта

  $# Число позиционных параметров (десятичное)

  $? Код возврата предыдущего процесса

  $$ PID текущего Shell

  $! PID последнего процесса, запущенного в фоновом режиме

  $* Все позиционные параметры скрипта, передаваемые заключенными в кавычки:
  "$*" - "$1 $2 $3 ..."

  $@ Все позиционные параметры скрипта, передаваемые как отдельные строки, заключенные в кавычки.
  $@ - "$1" "$2" "$3" ....

  Поясним встроенные переменные Shell на примере.

 Текст скрипта            Запуск скрипта

  #!/bin/sh $                   ./script1.sh par1 par2 par3
  echo script $0               script ./script1.sh
  echo $1 $2 $3               par1 par2 par3

  Здесь наблюдается полная аналогия с argv при вызове функции main() в языке Си. Встроенную переменную $# используют в том случае, когда необходимо убедится в том, что скрипту было передано нужное количество параметров.

  Текст скрипта

  #!/bin/sh

  if [ $# -lt 3 ]; then
      echo missing parameters! usage: $0 par1 par2 par3
  exit 1
  fi

  В данном скрипте используется условный параметр if и команда test. Интуитивно понятно, что происходит сравнение количества переданных параметров (переменная $#) и если это значение меньше 3, тогда на консоль выводится сообщение. Строка "exit 1" формирует код возврата скрипта равный единице. В OC UNIX за успешное завершение принят код возврата "0". Если код возврата не равен нулю, это означает нештатное завершение или иначе говоря, ошибку. Таким образом в скриптах возможно использование кода возврата предыдущего процесса. Поясним на примере.

  #!/bin/sh
  ps -ax | grep inetd
     if [ $? -ne 0 ]; then
         echo сервис inetd не запущен
     fi

  В этом примере также используется условный параметр if и команда test. Кроме того также использован и конвеер Shell который будет описан позже. Итак, разберемся, какие действия выполняет это скрипт. В первой строке c помощью команды "ps -ax" выводится список всех запущенных в системе процессов, и вывод передается как входной параметр команде grep(1), которая производит поиск строки содержащей в себе "inetd" - имя программы. Если такая строчка была найдена, что соответствует запущенному демону inetd, grep(1) завершается с кодом возврата "0" (успешное завершение) свидетельствуя о том, что inetd был запущен. Далее следует условный цикл if, и команда test, которая пользуясь встроенной переменной "$?" получает значение кода возврата предыдущего процесса, в данном случае, код возврата команды grep(1). Сравнивая значение кода возврата " -ne 0" (не равен нулю) shell переходит к условию.

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

  Три направления ввода-вывода являются выделенными - стандартный ввод, вывод и поток ошибок (stdin, stdout, stderr). По умолчанию, все три потока связаны (ассоциированы) с терминалом. При этом программа выводит все свои сообщения в том числе и об ошибках на терминал. Shell позволяет перенаправить эти стандартные потоки, установить ввод-вывод и объединение потоков.

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

  >file Перенаправление стандартного потока вывода в файл file

  >>file Перенаправление стандартного потока вывода в файл file с добавлением в конец файла

  <file Получение стандартного потока ввода из файла file

  prog1 | prog2 Передача выходного потока программы prog1 во входной поток программы prog2

  n>file Перенаправление стандартного потока с дескриптором n в файл file

  n>>file Перенаправление стандартного потока с дескриптором n в файл file с добавлением в   конец файла

  n>&m Объединение потоков с дескрипторами n и m

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

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

  $ mail vasya@pupkin.ru < message.txt

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

  Другой пример. Пусть у вас есть некоторая "капризная" программа, при запуске выводящая сообщения об ошибках. Вам, как системному администратору, необходимо связаться с разработчиками, послав им это самое сообщение.

  $ bad_prog >> /var/log/bad_prog.log

  Вывод программы bad_prog будет производиться в конец файла bad_prog.log, т.е. все предыдущие записи сохраняются. Если же файла bad_prog.log не существует, он будет создан.

  Всем трем стандартным потокам ввода, вывода и ошибок назначены также численные значения, так называемые дескрипторы, соответственно 0, 1, 2. Используя эти дескрипторы можно объединять и перенаправлять конкретные потоки. Например, вернемся к предыдущему случаю:

  $ bad_prog >>/var/log/bad_prog.log 2>&1

  В таком случае происходит объединение потоков вывода и ошибок в один, и происходит перенаправление в файл bad_prog.log. Знак "&" перед дескриптором потока необходим для того, чтобы отличить его например от файла с именем "1". В инициализационных скриптах системы также очень часто используется подавление потока вывода и ошибок.

  $ prog1 >/dev/null 2>&1

  /dev/null это псевдоустройство, которое уничтожает направленный в него поток.

 Передача потока вывода одной программы в поток ввода другой используется часто. Например:

  $ gzip -d archive.tar.gz | tar -xf

  Здесь происходит разархивация файла archive.tar.gz, запакованного двумя архиваторами. Выходной поток от утилиты gzip передается во входной поток утилите tar. Аналогично эту же операцию можно было выполнить и по другому:

  $ gzip -d archive.tar.gz
  $ tar -xf archive.tar

  Поскольку язык Bourne Shell является процедурным языком программирования, в нем также как и в других подобных языках есть операторы, позволяющие управлять последовательностью выполнения команд. Необходимым оператором является проверка некоторого условия, в зависимости от выполнения которого определяется дальнейший ход программы. Таким оператором является команда test. Эта команда проверяет выполнение некоторого условия. У команды test существует два варианта вызова

  test условие

  или

  [ условие ]

  Следует отметить, что между скобкой и условием необходимо наличие пробелов, иначе Shell не сможет опознать "[" как команду test. При успешном завершении test возвращает "0".

  Условия проверки файлов.

  -f file Файл "file" является обычным файлом.

  -d file Файл "file" является каталогом

  -c file Файл "file" является специальным файлом

  -r file Файл "file" имеет разрешение на чтение

  -w file Файл "file" имеет разрешение на запись

  -x file Файл "file" имеет разрешение на исполнение

  -s file Файл "file" не пустой

  Условия проверки строк

  string1=string2  Строки string1 и string2 совпадают

  string1!=string2 Строки string1 и string2 не совпадают.

  -n string1          Строка string1 существует

  -z string1          Строка string1 не существует

  Условия операций с целыми числами

  x -eq y            x равно y

  x -ne y            x не равно y

  x -gt y            x больше y

  x -ge y            x больше или равно y

  x -lt y             x меньше y

  x -le y             x меньше или равно y

  В этом случае команда test воспринимает строки именно как целые числа. Нулевому значению так же соответствует пустая строка.

  Логические операции в контексте test

  ! (not) Логическое "НЕ"

  -o (or) Логическое "ИЛИ"

  -a (and) Логическое "И"

Условный оператор "if"

  Общий вид использования условного оператора if представляется следующим образом:

  if <условие>
  then <список команд>
  [ elif <условие>
  then <список> ]
  [else <список> ]
  fi

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

  if <условие>
  then <список команд>
  fi

  В этом случае если <условие> выполнено (код завершения 0 ) то выполняется <список команд>. В противном случае <список команд> пропускается.

  Рассмотрим пример использования операторов test и if.

  #!/bin/sh
  #

  echo -n "Что получил на экзамене?"
  read x
    if [ $x=отл ]; then
       echo "Обманул товарища лектора!"
          elseif [ $x=хор ]; then
            echo "Хотел обмануть - разоблачили!"
              elseif [ $x=уд ]; then
                echo "Удалось договорится"
                  elseif [ $x=неуд ]; then
                    echo "Не удалось договорится... &-("
    fi

  Для удобства чтения текста скрипта принято использовать табуляцию для разделения условных операторов.

  Еще пример:

  #!/bin/sh
  #

  if [ ! -f $HOME/.login ]; then
     echo "Файло .login куда то задевалось... Копируем шаблон!"
       cp /etc/skell/login $HOME/.login
  fi

  И в заключении еще один пример - инициализация SAMBA в BSD UNIX:

  #!/bin/sh
  #
   if [ -x /usr/local/samba/bin/smbd ] && [ -x /usr/local/samba/bin/nmbd ]; then
       
       /usr/local/samba/bin/smbd -D ; echo -n ` smbd`
       /usr/local/samba/bin/nmbd -D ; echo -n ` nmbd`

   fi


  Оператор вызова case

  В общем случае синтаксис оператора case выглядит следующим образом:

  case <строка> in
    шаблон1)
           cmd1
           cmd2
           ........
               ;;

   шаблон2)
           cmd3
           cmd4
           ........
               ;;
           ....
     
   *)
          cmdn
           ........
               ;;
   esac

  Значение <строка> сравнивается с шаблонами, по порядку. Если было найдено совпадение, тогда выполняются команды соответствующего раздела. Следует отметить, что шаблоны допускают использование масок. Если совпадения не было найдено, тогда выполняются команды из раздела с шаблоном "*" ( аналогично default селектора switch в Си).

  Для примера приведем кусочек инициализационного скрипта BSD UNIX. Здесь перменные ( inetd_enable и inetd_flags) были получены из другого файла (rc.conf).

  ....

  . /etc/rc.conf

  ...

  case {$inetd_enable} in
    [Yy][Ee][Ss])
       if [ -x /usr/sbin/inetd ]; then
       /usr/sbin/inetd $inetd_flags

       fi

      ;;

  esac

 Оператор цикла с перечислением for

  Синтаксис оператора for в общем случае имеет следующую структуру:

  for <имя> [in список значений]
     do
       <список команд>
     done

  Фрагмент, выделенный в квадратные скобки, может отсутствовать. Оператор for обеспечивает выполнение цикла столько раз, сколько слов в списке значений. При этом переменная <имя> принимает последовательно значения слов из списка. Сам по себе список может формироваться из вывода других команд. Если же список отсутствует, тогда <имя> принимает значения, переданные как позиционные параметры скрипта.

  Оператор цикла с истиным условием while

  Синтаксис оператора while в общем случае имеет следующую структуру:

  while <условие>
     do
      <список команд>
    done

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

  Оператор цикла с ложным условием until

  Синтаксис опертора until в общем случае имеет следующую структуру:

  until <условие>
    do
     <список команд>
   done

  Список команд будет выполняться в цикле до тех пор, пока сохраняется ложность условия. При первом входе в цикл условие не должно выполняться. Следует заметить, что условие цикла проверяется на ложность после каждого, в т.ч. и первой выполненной команды в цикле.
Категория: Shell | Добавил: oleg (27.10.2007)
Просмотров: 3725 | Рейтинг: 4.8/8 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024