• Почему программа в командной оболочке?
  • Немного теории
  • Что такое командная оболочка?
  • Каналы и перенаправление
  • Перенаправление вывода
  • Перенаправление ввода
  • Каналы 
  • Командная оболочка как язык программирования
  • Интерактивные программы
  • Создание сценария
  • Превращение сценария в исполняемый файл
  • Синтаксис командной оболочки
  • Переменные
  • Условия
  • Управляющие структуры
  • Функции
  • Команды
  • Выполнение команд
  • Встроенные документы
  • Отладка сценариев
  • По направлению к графическому режиму — утилита dialog
  • Соединяем все вместе
  • Требования
  • Проектирование
  • Резюме 
  • Глава 2

    Программирование средствами командной оболочки

    Начав книгу с программирования в ОС Linux на языке С, теперь мы сделаем отступление и остановимся на написании программ в командной оболочке. Почему? ОС Linux не относится к системам, у которых интерфейс командной строки — запоздалое детище графического интерфейса. У систем UNIX, прообраза Linux, первоначально вообще не было графического интерфейса; все выполнялось из командной строки. Поэтому оболочка командной строки UNIX все время развивалась и превратилась в очень мощный инструмент. Эти свойства перекочевали и в Linux, и некоторые самые серьезные задачи вы можете выполнить наиболее легким способом именно из командной оболочки. Поскольку она так важна для ОС Linux и столь полезна для автоматизации простых задач, программирование средствами командной оболочки рассматривается прежде всего.

    В этой главе мы познакомим вас с синтаксисом, структурами и командами, доступными при программировании в командной оболочке, как правило, используя интерактивные (основанные на экранах) примеры. Они помогут продемонстрировать функциональные возможности командной оболочки и собственные действия. Мы также бросим беглый взгляд на пару особенно полезных утилит режима командной строки, часто вызываемых из командной оболочки: grep и find. Рассматривая утилиту grep, мы познакомимся с основными положениями, касающимися регулярных выражений, которые появляются в утилитах ОС Linux и языках программирования, таких как Perl, Ruby и PHP. В конце главы вы узнаете, как писать настоящие сценарии, которые будут перепрограммироваться и расширяться на языке С на протяжении всей книги. В этой главе рассматриваются следующие темы:

    □ что такое командная оболочка;

    □ теоретические основы;

    □ тонкости синтаксиса: переменные, условия и управление программой;

    □ списки;

    □ функции;

    □ команды и их выполнение;

    □ встроенные (here) документы;

    □ отладка;

    □ утилита grep и регулярные выражения;

    □ утилита find.

    Если вы столкнулись со сложным сценарием командной оболочки при администрировании системы, хотите смоделировать вашу последнюю грандиозную (но удивительно простую) идею или просто повысить производительность какой-либо итеративной задачи, эта глава вам будет полезна.

    Почему программа в командной оболочке?

    Одна из причин применения командной оболочки — возможность быстрого и простого программирования. Более того, командная оболочка всегда есть даже в самых упрощенных установках ОС Linux, поэтому благодаря простому моделированию вы сможете понять, работает ли ваша идея. Командная оболочка очень удобна и для небольших утилит, выполняющих относительно простую задачу, для которой производительность менее важна, чем простые настройка, сопровождение и переносимость. Вы можете применять оболочку для управления процессами, обеспечивая выполнение команд в заданном порядке, зависящем от успешного завершения каждого этапа выполнения.

    Хотя внешне командная оболочка очень похожа на режим командной строки в ОС Windows, она гораздо мощнее и способна выполнять самостоятельно очень сложные программы. Вы можете не только выполнять команды и вызывать утилиты ОС Linux; но и разрабатывать их. Командная оболочка выполняет программы оболочки, часто называемые сценариями или скриптами, которые интерпретируются во время выполнения. Такой подход облегчает отладку, потому что вы легко можете выполнять программу построчно и не тратить время на перекомпиляцию. Но для задач, которым важно время выполнения или необходимо интенсивное использование процессора, командная оболочка оказывается неподходящей средой.

    Немного теории

    Вот мы и добрались до теоретических основ UNIX и, конечно, Linux. ОС UNIX основана на интенсивном многократном применении кода и зависит от него. Вы разработали маленькую простую утилиту, и пользователи применяют ее как одну из ссылок в строке, формирующей команду. Одно из наслаждений, доставляемых ОС Linux, — разнообразие имеющихся отличных средств. Примером может служить следующая команда:

    $ ls -al | more

    Эта команда применяет утилиты

    ls
    и
    more
    и передает вывод списка файлов для поэкранного отображения. Каждая утилита — это отдельный блок. Зачастую вы можете применять множество мелких утилит для создания больших и сложных комплексов программ.

    Например, если вы хотите напечатать контрольную копию справочного руководства оболочки bash, примените следующую команду:

    man bash | col -b | lpr

    Более того, благодаря автоматической обработке типов файлов пользователям этих утилит обычно не нужно знать, на каком языке данные программы написаны. Если необходимо ускорить выполнение утилиты, как правило, ее сначала моделируют в командной оболочке и затем, когда работоспособность утилиты проверена, реализуют ее на языке С или С++, Perl, Python или каком-либо другом, обеспечивающем более быстрое выполнение. В противном случае, если в командной оболочке утилита действует адекватно, вы вполне можете оставить ее в покое.

    Необходимость переделки сценария зависит от того, нуждается ли он в оптимизации, должен ли он быть переносимым, необходимо ли обеспечить легкую модификацию и не перерос ли он (как это обычно случается) первоначально поставленную задачу.

    Примечание

    Для удовлетворения вашего любопытства в вашу ОС Linux уже загружены многочисленные примеры сценариев, включая инсталляторы пакетов,

    .xinitrc
    и
    startx
    , и сценарии в каталоге /etc/rc.d, предназначенные для настройки системы в процессе загрузки.

    Что такое командная оболочка?

    Прежде чем переходить к обсуждению того, как программа использует оболочку, давайте рассмотрим, как функционирует оболочка и какие оболочки есть в Linux-подобных системах. Командная оболочка — это программа, которая действует как интерфейс между вами и ОС Linux, позволяя вам вводить команды, которые должна выполнить операционная система. В этом смысле она похожа на командную строку в ОС Windows, но, как уже упоминалось, командные оболочки Linux гораздо мощнее. Например, ввод и вывод можно перенаправить с помощью символов

    <
    и
    >
    , передавать данные между двумя одновременно выполняющимися программами с помощью символа
    |
    , а перехватывать вывод подпроцесса с помощью конструкции
    $(...)
    . В ОС Linux вполне может сосуществовать несколько установленных командных оболочек, и разные пользователи могут выбрать ту, которая им больше нравится. На рис. 2.1 показано, как командная оболочка (на самом деле, две командные оболочки: bash и csh) и другие программы располагаются вокруг ядра Linux.

    Рис. 2.1

    Поскольку ОС Linux — модульная система, вы можете вставить и применять одну из множества различных стандартных командных оболочек, хотя большинство из них — потомки первоначальной оболочки Bourne. В Linux стандартная командная оболочка, всегда устанавливаемая как /bin/sh и входящая в комплект средств проекта GNU, называется bash (GNU Bourne-Again SHell). Именно ее мы будем применять, т. к. это отличная командная оболочка, всегда устанавливаемая в системах Linux, со свободно распространяемым программным кодом и переносимая почти на все варианты UNIX-систем. В данной главе используется оболочка bash версии 3, и в большинстве случаев применяются ее функциональные возможности, общие для всех командных оболочек, удовлетворяющих требованиям стандарта POSIX. Мы полагаем, что командная оболочка, установленная как /bin/sh и для вашей учетной записи, считается командной оболочкой по умолчанию. В большинстве дистрибутивов Linux программа /bin/sh, командная оболочка по умолчанию, — это ссылка на программу /bin/bash.

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

    $ /bin/bash --version

    GNU bash, version 3.2.9(1)-release (i686-pc-linux-gnu)

    Copyright (C) 2005 Free Software Foundation, Inc.

    Примечание

    Для перехода на другую командную оболочку, если в вашей системе по умолчанию установлена не bash, просто выполните программу нужной вам командной оболочки (т.е. /bin/bash) для запуска новой оболочки и смены приглашения в командной строке. Если вы используете ОС UNIX, и командная оболочка bash не установлена, вы можете бесплатно загрузить ее с Web-сайта www.gnu.org. Исходный код обладает высокой степенью переносимости, и велика вероятность, что он откомпилируется в вашей версии UNIX прямо в готовую к использованию программу.

    Когда создаются учетные записи пользователей ОС Linux, вы можете задать командную оболочку, которой они будут пользоваться, в момент создания учетной записи пользователя или позже, откорректировав ее параметры. На рис. 2.2 показан выбор командной оболочки для пользователя дистрибутива Fedora.

    Рис. 2.2


    Существует много других командных оболочек, распространяемых свободно или на коммерческой основе. В табл. 2.1 предлагается краткая сводка некоторых самых распространенных командных оболочек.


    Таблица 2.1

    Название командной оболочки Краткие исторические сведения
    sh (Bourne) Первоначальная оболочка в ранних версиях ОС UNIX
    csh, tcsh, zsh Командная оболочка C-shell (и ее производные), первоначально созданная Биллом Джойем (Bill Joy) для систем Berkeley UNIX. C-shell, возможно, третья по популярности командная оболочка после оболочек bash и Korn
    ksh, pdksh Командная оболочка Korn и ее безлицензионный родственник. Написанная Дэвидом Корном (David Korn) эта оболочка применяется по умолчанию во многих коммерческих версиях UNIX
    bash Основная командная оболочка ОС Linux из проекта GNU или Bourne Again SHell со свободно распространяемым программным кодом. Если в настоящий момент она не выполняется в вашей системе UNIX, вероятно, есть вариант оболочки, перенесенный на вашу систему. У bash много сходств с оболочкой Korn

    За исключением оболочки C-shell и небольшого числа ее производных все перечисленные оболочки очень похожи и очень близки к оболочке, определенной в спецификациях Х/Оpen 4.2 и POSIX 1003.2. В спецификации POSIX 1003.2 задан минимум, необходимый для создания командной оболочки, а в спецификации Х/Open представлена более дружественная и мощная оболочка.

    Каналы и перенаправление

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

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

    Возможно, вы уже знакомы с некоторыми видами перенаправления, например, таким как:

    $ ls -l > lsoutput.txt

    сохраняющим вывод команды ls в файле с именем lsoutput.txt.

    Однако перенаправление позволяет сделать гораздо больше, чем демонстрирует этот простой пример. В главе 3 вы узнаете больше о дескрипторах стандартных файлов, а сейчас вам нужно знать только то, что дескриптор файла 0 соответствует стандартному вводу программы, дескриптор файла 1 — стандартному выводу, а дескриптор файла 2 — стандартному потоку ошибок. Каждый из этих файлов можно перенаправлять независимо друг от друга. На самом деле можно перенаправлять и другие дескрипторы файлов, но, как правило, нет нужды перенаправлять любые другие дескрипторы, кроме стандартных: 0, 1 и 2.

    В предыдущем примере стандартный вывод перенаправлен в файл с помощью оператора

    >
    . По умолчанию, если файл с заданным именем уже есть, он будет перезаписан. Если вы хотите изменить поведение по умолчанию, можно применить команду
    set -о noclobber
    (или
    set -С
    ), которая устанавливает опцию
    noclobber
    , чтобы помешать перезаписи при перенаправлении. Отменить эту опцию можно с помощью команды
    set +о noclobber
    . Позже в этой главе будут приведены другие опции команды
    set
    .

    Для дозаписи в конец файла используйте оператор

    >>
    . Например, команда

    ps >> lsoutput.txt

    добавит вывод команды

    ps
    в конец заданного файла.

    Для перенаправления стандартного потока ошибок перед оператором

    >
    вставьте номер дескриптора файла, который хотите перенаправить. Поскольку у стандартного потока ошибок дескриптор файла 2, укажите оператор
    2>
    . Часто бывает полезно скрывать стандартный поток ошибок, запрещая вывод его на экран.

    Предположим, что вы хотите применить команду

    kill
    для завершения процесса из сценария. Всегда существует небольшой риск, что процесс закончится до того, как выполнится команда
    kill
    . Если это произойдет, команда kill выведет сообщение об ошибке в стандартный поток ошибок, который по умолчанию появится на экране. Перенаправив стандартный вывод команды и ошибку, вы сможете помешать команде
    kill
    выводить какой бы то ни было текст на экран.

    Команда

    $ kill -HUP 1234 >killout. txt 2>killer.txt

    поместит вывод и информацию об ошибке в разные файлы.

    Если вы предпочитаете собрать оба набора выводимых данных в одном файле, можно применить оператор

    >&
    для соединения двух выводных потоков. Таким образом, команда

    $ kill -1 1234 >killerr.txt 2>&1

    поместит свой вывод и стандартный поток ошибок в один и тот же файл. Обратите внимание на порядок следования операторов. Приведенный пример читается как "перенаправить стандартный вывод в файл killerr.txt, а затем перенаправить стандартный поток ошибок туда же, куда и стандартный вывод". Если вы нарушите порядок, перенаправление выполнится не так, как вы ожидаете.

    Поскольку обнаружить результат выполнения команды

    kill
    можно с помощью кода завершения (который будет подробно обсуждаться далее в этой главе), часто вам не потребуется сохранять какой бы то ни было стандартный вывод или стандартный поток ошибок. Для того чтобы полностью отбросить любой вывод, вы можете использовать универсальную "мусорную корзину" Linux, /dev/null, следующим образом:

    $ kill -l 1234 >/dev/null 2>&1

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

    Также как вывод вы можете перенаправить ввод. Например,

    $ more < killout.txt

    Понятно, что это тривиальнейший пример для ОС Linux; команда

    more
    в системе Linux в отличие от своего эквивалента командной строки в ОС Windows с радостью принимает имена файлов в качестве параметров.

    Каналы 

    Вы можете соединять процессы с помощью оператора канала (

    |
    ). В ОС Linux, в отличие от MS-DOS, процессы, соединенные каналами, могут выполняться одновременно и автоматически переупорядочиваться в соответствии с потоками данных между ними. Как пример, можно применить команду
    sort
    для сортировки вывода команды
    ps
    .

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

    $ ps > psout.txt

    $ sort psout.txt > pssort.out

    Соединение процессов каналом даст более элегантное решение:

    $ ps | sort > pssort.out

    Поскольку вы, вероятно, захотите увидеть на экране вывод, разделенный на страницы, можно подсоединить третий процесс,

    more
    , и все это в одной командной строке:

    $ ps | sort | more

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

    $ ps -хо соmm | sort | uniq | grep -v sh | more

    В ней берется вывод команды

    ps
    , сортируется в алфавитном порядке, из него извлекаются процессы с помощью команды
    uniq
    , применяется утилита
    grep -v sh
    для удаления процесса с именем
    sh
    и в завершение полученный список постранично выводится на экран.

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

    cat mydata.txt | sort | uniq > mydata.txt

    то в результате получите пустой файл, т.к. вы перезапишете файл mydata.txt, прежде чем прочтете его.

    Командная оболочка как язык программирования

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

    Интерактивные программы

    Легкий и очень полезный во время обучения или тестирования способ проверить работу небольших фрагментов кода — просто набрать с клавиатуры в командной строке сценарий командной оболочки.

    Предположим, что у вас большое количество файлов на языке С, и вы хотите проверить наличие в них строки

    POSIX
    . Вместо того чтобы искать в файлах строку с помощью команды
    grep
    и затем выводить на экран отдельно каждый файл, вы можете выполнить всю операцию в интерактивном сценарии, похожем на следующий:

    $ for file in *

    > do

    > if grep -l POSIX $file

    > then

    > more $file

    > fi

    > done

    posix

    This is a file with POSIX in it - treat it well

    $

    Обратите внимание на то, как меняется знак

    $
    , стандартная подсказка или приглашение командной оболочки, на символ
    >
    , когда оболочка ожидает очередной ввод. Вы можете продолжить набор, дав оболочке понять, когда закончите, и сценарий немедленно выполнится.

    В этом примере команда

    grep
    выводит на экран найденные ею имена файлов, содержащих строку
    POSIX
    , а затем команда
    more
    отображает на экране содержимое файла. В конце на экран возвращается приглашение командной оболочки. Обратите внимание также на то, что вы ввели переменную командной оболочки, которая обрабатывает каждый файл для самодокументирования сценария. С таким же успехом вы могли бы использовать переменную
    i
    , но имя
    file
    более информативно с точки зрения пользователей.

    Командная оболочка также обрабатывает групповые символы или метасимволы (часто называемые знаками подстановки). Вы почти наверняка знаете о применении символа

    *
    как знака подстановки, соответствующего строке символов. Но вы можете не знать о существовании односимвольного знака подстановки,
    ?
    , а конструкция
    [set]
    позволяет проверить любое количество одиночных символов,
    [^set]
    — применяет логическую операцию "НЕ" к множеству, т.е. включает все, кроме того, что вы задали. Подстановочный шаблон из фигурных скобок
    {}
    (доступен в некоторых командных оболочках, включая bash) позволяет формировать множество из произвольных строк, которое командная оболочка раскроет. Например, команда

    $ ls my_{finger, toe}s

    будет выводить файлы my_fingers и my_toes. Эта команда использует оболочку для проверки всех файлов в текущем каталоге. Мы вернемся к этим правилам соответствия шаблонам в конце главы, когда будем более подробно рассматривать утилиту

    grep
    и возможности регулярных выражений.

    Опытные пользователи ОС Linux, вероятно, выполнят эту операцию более эффективным способом, возможно, с помощью следующей команды:

    $ more `grep -l POSIX *`

    или синонимической конструкции

    $ more $(grep -l POSIX *)

    В дополнение команда

    $ grep -l POSIX * | more

    выведет на экран имя файла, содержащего строку

    POSIX
    . В этом сценарии мы видим, что командная оболочка для выполнения трудной работы привлекает другие команды, например,
    grep
    и
    more
    . Оболочка просто позволяет собрать вместе несколько имеющихся команд новыми оригинальными способами. В следующих сценариях вы увидите использование знаков подстановки неоднократно. Мы подробно рассмотрим целую область подстановок, когда будем знакомиться с регулярными выражениями в разделе, посвященном команде
    grep
    .

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

    Создание сценария

    С помощью любого текстового редактора необходимо создать файл, содержащий команды. Создайте файл с именем first с таким содержимым:

    #!/bin/sh


    # first

    # Этот файл просматривает все файлы в текущем каталоге

    # для поиска строки POSIX, а затем выводит имена

    # найденных файлов в стандартный вывод.


    for file in *

    do

     if grep -q POSIX $file

     then

      echo $file

     fi

    done


    exit 0

    Комментарий начинается со знака

    #
    и продолжается до конца строки. Принято знак
    #
    ставить в первой символьной позиции строки. Сделав такое общее заявление, далее отметим, что первая строка
    #!/bin/sh
    — это особая форма комментария; символы
    #!
    сообщают системе о том, что следующий за ними аргумент — программа, применяемая для выполнения данного файла. В данном случае программа
    /bin/sh
    — командная оболочка, применяемая по умолчанию. 

    Примечание

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

    #!
    , хотя у ОС Linux обычно нет подобного ограничения.

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

    PATH
    .

    Команда

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

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

    exit
    .

    В сценарии не используются никакие расширения и суффиксы имен файлов; ОС Linux и UNIX, как правило, редко применяют при именовании файлов расширения для указания типа файла. Вы могли бы использовать расширение sh или любое другое, командную оболочку это не волнует. У большинства предустановленных сценариев нет никакого расширения в именах файлов и лучший способ проверить, сценарий это или нет применить

    команду
    file
    , например,
    file first
    или
    file /bin/bash
    . Пользуйтесь любыми правилами, принятыми в вашей организации или удобными для вас.

    Превращение сценария в исполняемый файл

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

    $ /bin/sh first

    Этот вариант будет работать, но гораздо лучше запускать сценарий, введя его имя и тем самым присвоив ему статус других команд Linux. Сделаем это с помощью команды chmod, изменив режим файла (file mode) и сделав его исполняемым для всех пользователей:

    $ chmod +х first
     

    Примечание

    Конечно, превращение файла в исполняемый — это не единственный вариант применения команды

    chmod
    . Для того чтобы узнать больше о восьмеричных аргументах и других опциях команды, наберите
    man chmod
    .

    После этого вы можете выполнять файл с помощью команды

    $ first

    При этом может появиться сообщение об ошибке, говорящее о том, что команда не найдена. Почти наверняка причина в том, что в переменной

    PATH
    не задан текущий каталог для поиска выполняемых команд. Исправить это можно либо введя с клавиатуры в командной строке
    PATH=$PATH:.
    , либо добавив данную команду в конец файла .bash_profile. Затем выйдите из системы и зарегистрируйтесь снова. В противном случае введите
    ./first
    в каталог, содержащий сценарий, чтобы задать командной оболочке полный относительный путь к файлу.

    Указание пути, начинающегося с символов

    ./
    , дает еще одно преимущество: в этом случае вы случайно не сможете выполнить другую команду с тем же именем, что и у вашего файла сценария.

    Примечание

    Не следует вносить подобные изменения в переменную

    PATH
    для суперпользователя, как правило, с именем root. Это лазейка в системе безопасности, т.к. системного администратора, зарегистрировавшегося как root, обманным путём могут заставить запустить фиктивную версию стандартной команды. Один из авторов однажды разрешил сделать это — конечно только для того, чтобы поставить перед системным администратором вопрос о безопасности! В случае обычных учетных записей включение текущего каталога в полный путь сопряжено с очень небольшим риском, поэтому, если вам это нужно, примите за правило добавление комбинации символов
    ./
    перед всеми командами, находящимися в локальном каталоге.

    После того как вы убедитесь в корректной работе вашего сценария, можете переместить его в более подходящее место, чем текущий каталог. Если команда предназначена только для собственных нужд, можете создать каталог bin в своем исходном каталоге и добавить его в свой путь. Если вы хотите, чтобы сценарий выполняли другие пользователи, можно использовать каталог /usr/local/bin или другой системный каталог как удобное, хранилище для вновь созданных программ. Если в вашей системе у вас нет прав суперпользователя, можно попросить системного администратора скопировать ваш файл для вас, хотя сначала, возможно, придется убедить его в неоспоримых достоинствах вашего файла. Для того чтобы установить владельца и права доступа к файлу, администратору придется задать такую последовательность команд:

    # ср first /usr/local/bin

    # chown root /usr/local/bin/first

    # chgrp root /usr/local/bin/first

    # chmod 755 /usr/local/bin/first

    Обратите внимание на то, что в данном случае вместо изменения определенной части флагов прав доступа вы используете абсолютную форму команды chmod, потому что точно знаете, какие требуются права доступа.

    Если захотите, можно применить более длинную, но более понятную форму команды

    chmod
    :

    # chmod u=rwx, go=rx /usr/local/bin/first

    Более подробную информацию можно найти в справочном руководстве команды

    chmod
    .

    Примечание

    В ОС Linux вы можете удалить файл, если у вас есть право записи в каталог, содержащий этот файл. Для безопасности убедитесь в том, что право записи в каталоги, содержащие файлы, которые вы хотели бы защитить, есть только у суперпользователя. В этом есть смысл, потому что каталог — это всего лишь еще один файл, и наличие права записи в файл каталога позволяет пользователям добавлять и удалять имена.

    Синтаксис командной оболочки

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

    □ переменные: строки, числа, переменные окружения и параметры;

    □ условия: булевы или логические выражения (Booleans);

    □ управление выполнением программы:

    if
    ,
    elif
    ,
    for
    ,
    while
    ,
    until
    ,
    case
    ;

    □ списки;

    □ функции;

    □ команды, встроенные в командную оболочку;

    □ получение результата выполнения команды;

    □ встроенные (here) документы.

    Переменные

    В командной оболочке переменные перед применением обычно не объявляются. Вместо этого вы создаете их, просто используя (например, когда присваиваете им начальное значение). По умолчанию все переменные считаются строками и хранятся как строки, даже когда им присваиваются числовые значения. Командная оболочка и некоторые утилиты преобразуют строки, содержащие числа, в числовые значения, когда нужно их обработать должным образом. Linux — система, чувствительная к регистру символов, поэтому командная оболочка считает

    foo
    и
    Foo
    двумя разными переменными, отличающимися от третьей переменной
    FOO
    .

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

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

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

    salutation
    :

    $ salutation=Hello

    $ echo $salutation

    Hello

    $ salutation="Yes Dear"

    $ echo $salutation

    Yes Dear

    $ salutation=7+5

    $ echo $salutation

    7+5

    Примечание

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

    Вы можете присвоить переменной пользовательский ввод с помощью команды

    read
    . Она принимает один параметр — имя переменной, в которую будут считываться данные, и затем ждет, пока пользователь введет какой-либо текст. Команда
    read
    обычно завершается после нажатия пользователем клавиши <Enter>. При чтении переменной с терминала, как правило, заключать ее значения в кавычки не требуется:

    $ read salutation

    Wie geht's?

    $ echo $salutation

    Wie geht's?

    Заключение в кавычки

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

    Обычно параметры в сценариях отделяются неотображаемыми символами или знаками форматирования (например, пробелом, знаком табуляции или символом перехода на новую строку). Если вы хотите, чтобы параметр содержал один или несколько неотображаемых символов, его следует заключить в кавычки.

    Поведение переменных, таких как

    $foo
    , заключенных в кавычки, зависит от вида используемых кавычек. Если вы заключаете в двойные кавычки
    $
    -представление переменной, оно во время выполнения командной строки заменяется значением переменной. Если вы заключаете его в одинарные кавычки или апострофы, никакой замены не происходит. Вы также можете отменить специальное назначение символа
    $
    , вставив перед ним символ
    \
    (обратный слэш).

    Выполним упражнение 2.1.

    Упражнение 2.1. Игра с переменными

    В этом упражнении показано, как кавычки влияют на вывод переменной:

    #!/bin/sh

    myvar="Hi there"

    echo $myvar

    echo "$myvar"

    echo '$myvar'

    echo \$myvar


    echo Enter some text

    read myvar


    echo '$myvar' now equals $myvar

    exit 0

    Данный сценарий ведет себя следующим образом:

    $ ./variable

    Hi there

    Hi there

    $myvar

    $myvar

    Enter some text

    Hello World

    $myvar now equals Hello World

    Как это работает

    Создается переменная

    myvar
    , и ей присваивается строка
    Hi there
    . Содержимое переменной выводится на экран с помощью команды
    echo
    , демонстрирующей, как символ
    $
    раскрывает содержимое переменной. Вы видите, что применение двойных кавычек не влияет на раскрытие содержимого переменной, а одинарные кавычки и обратный слэш влияют. Вы также применяете команду
    read
    для получения строки от пользователя.

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

    Когда стартует сценарий командной оболочки, некоторым переменным присваиваются начальные значения из окружения или рабочей среды. Обычно такие переменные обозначают прописными буквами, чтобы отличать их в сценариях от определенных пользователем переменных (командной оболочки), которые принято обозначать строчными буквами. Формируемые переменные зависят от ваших персональных настроек. Многие из них перечислены на страницах справочных руководств, а основные приведены в табл. 2.2.


    Таблица 2.2

    Переменная окружения Описание
    $НОМЕ
    Исходный каталог текущего пользователя
    $PATH
    Разделенный двоеточиями список каталогов для поиска команд
    $PS1
    Подсказка или приглашение командной строки. Часто знак
    $
    , но в оболочке bash можно применять и более сложные варианты. Например, строка
    [\u@\h \w]$
    — популярный стандарт, сообщающий в подсказке пользователя имя компьютера и текущий каталог, а также знак
    $
    $PS2
    Дополнительная подсказка или приглашение, применяемое как приглашение для дополнительного ввода; обычно знак
    >
    $IFS
    Разделитель полей ввода. Список символов, применяемых для разделения слов при чтении оболочкой ввода, как правило, пробел, знак табуляции и символ перехода на новую строку
    $0
    Имя сценария командной оболочки
    $#
    Количество передаваемых параметров
    $$
    ID (идентификатор) процесса сценария оболочки, часто применяемый внутри сценария для генерации уникальных имен временных файлов; например, /tmp/tmpfile_$$
    Примечание

    Если вы хотите проверить с помощью команды

    env <команда>
    , как работает программа в разных окружениях, познакомьтесь с интерактивным справочным руководством к команде
    env
    . Далее в этой главе вы увидите, как задавать переменные окружения в подоболочках (subshells), применяя команду
    export
    .

    Переменные-параметры

    Если ваш сценарий вызывается с параметрами, создается несколько дополнительных переменных. Если параметры не передаются, переменная окружения

    $#
    все равно существует, но равна 0.

    Переменные-параметры перечислены в табл. 2.3.


    Таблица 2.3

    Переменная-параметр Описание
    $1
    ,
    $2
    , ...
    Параметры, передаваемые сценарию
    $*
    Список всех параметров в единственной переменной, разделенных первым символом из переменной окружения
    IFS
    . Если
    IFS
    корректируется, способ разделения командной строки на параметры в переменной
    $*
    изменяется
    $@
    Едва различимая вариация
    $*
    ; не использует переменную окружения
    IFS
    , поэтому параметры не сольются, даже если переменная
    IFS
    пуста

    Легче всего увидеть разницу между переменными-параметрами

    $*
    и
    $@
    , опробовав их.

    $ IFS=''

    $ set foo bar bam

    $ echo "$@"

    foo bar bam

    $ echo "$*"

    foobarbam

    $ unset IFS

    $ echo "$*"

    foo bar bam

    Как видите, заключенная в двойные кавычки переменная-параметр

    $@
    представляет позиционные параметры как отдельные поля, независимо от значения переменной окружения
    IFS
    . Как правило, если вы хотите получить доступ к параметрам, лучше использовать переменную-параметр.

    Помимо вывода на экран содержимого переменных с помощью команды

    echo
    , вы также можете прочитать его командой
    read
    (упражнение 2.2).

    Упражнение 2.2. Манипулирование параметрами и переменными окружения

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

    chmod +х try_var
    .

    #!/bin/sh


    salutation="Hello"

    echo $salutation

    echo "The program $0 is now running"

    echo "The second parameter was $2"

    echo "The first parameter was $1"

    echo "The parameter list was

    echo "The user's home directory is $HOME"

    echo "Please enter a new greeting"

    read salutation


    echo $salutation

    echo "The script is now complete"

    exit 0

    Если вы выполните этот сценарий, то получите следующий вывод:

    $ ./try_var foo bar baz

    Hello

    The program ./try_var is now running

    The second parameter was bar

    The first parameter was foo

    The parameter list was foo bar baz

    The user's home directory is /home/rick

    Please enter a new greeting

    Sire

    Sire

    The script is now complete $

    Как это работает

    Сценарий создает переменную

    salutation
    , выводит на экран ее содержимое и затем показывает, что уже сформированы и имеют соответствующие значения различные переменные-параметры и переменная окружения
    $НОМЕ
    .

    Далее в этой главе мы рассмотрим более подробно подстановку параметров.

    Условия

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

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

    Команда test или [

    На практике в большинстве сценариев широко используется команда

    [
    или
    test
    — логическая проверка командной оболочки. В некоторых системах команды
    [
    и
    test
    — синонимы, за исключением того, что при использовании команды
    [
    для удобочитаемости применяется и завершающая часть
    ]
    . Наличие команды
    [
    может показаться странным, но в программном коде она упрощает синтаксис и делает его более похожим на другие языки программирования.

    Примечание

    Эти команды в некоторых ранних оболочках UNIX вызывают внешнюю программу, но в более современных версиях их стараются делать встроенными. Мы вернемся к этому, когда будем рассматривать команды в следующем разделе.

    Поскольку команда

    test
    не часто применяется за пределами сценариев командной оболочки, многие пользователи ОС Linux, никогда раньше не писавшие сценариев, пытаются создавать простые программы и называют их test. Если такая программа не работает, вероятно, она конфликтует с командой оболочки
    test
    . Для того чтобы выяснить, есть ли в вашей системе внешняя команда с данным именем, попытайтесь набрать что-нибудь вроде
    which test
    и проверить, какая именно команда
    test
    выполняется в данный момент, или используйте форму
    ./test
    , чтобы быть уверенным в том, что вы выполняете сценарий из текущего каталога. Если сомневаетесь, примите за правило выполнять свои сценарии, предваряя при запуске их имена комбинацией символов
    ./
    .

    Мы представим команду

    test
    на примере одного простейшего условия: проверки наличия файла. Для нее понадобится следующая команда:
    test -f <имя_файла>
    , поэтому в сценарии можно написать

    if test -f fred.c

    then

     ...

    fi

    To же самое можно записать следующим образом:

    if [ -f fred.c ]

    then

     ...

    fi

    Код завершения команды

    test
    (выполнено ли условие) определяет, будет ли выполняться условный программный код.

    Примечание

    Имейте в виду, что вы должны вставлять пробелы между квадратной скобкой

    [
    и проверяемым условием. Это легко усвоить, если запомнить, что вставить символ
    [
    — это все равно, что написать
    test
    , а после имени команды вы всегда должны вставлять пробел.

    Если вы предпочитаете помещать слово

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

    if [ -f fred.c ]; then

     ...

    fi

    Варианты условий, которые вы можете применять в команде

    test
    , делятся на три типа: строковые сравнения, числовые сравнения и проверка файловых флагов (file conditionals). Эти типы условий описаны в табл. 2.4.


    Таблица 2.4

    Варианты условий Результат
    Сравнения строк
    Строка1 = Строка2
    True
    (истина), если строки одинаковы
    Строка1 != Строка2
    True
    (истина), если строки разные
    -n Строка
    True
    (истина), если
    Строка
    не
    null
    -z Строка
    True
    (истина), если
    Строка null
    (пустая строка)
    Сравнения чисел
    Выражение1 -eq Выражение2
    True
    (истина), если выражения равны
    Выражение1 -ne Выражение2
    True
    (истина), если выражения не равны
    Выражение1 -gt Выражение2
    True
    (истина), если
    Выражение1
    больше, чем
    Выражение2
    Выражение1 -ge Выражение2
    True
    (истина), если
    Выражение1
    не меньше
    Выражение2
    Выражение1 -lt Выражение2
    True
    (истина), если
    Выражение1
    меньше, чем
    Выражение2
    Выражение1 -lе Выражение2
    True
    (истина), если
    Выражение1
    не больше
    Выражение2
    ! Выражение
    True
    (истина), если
    Выражение
    ложно, и наоборот
    Файловый флаг
    -d файл
    True
    (истина), если
    файл
    — каталог
    файл
    True
    (истина), если
    файл
    существует. Исторически, опция
    -e
    не была переносима на другие платформы, поэтому обычно применяется
    -f
    -f файл
    True
    (истина), если
    файл
    — обычный файл
    -g файл
    True
    (истина), если для
    файла
    установлен бит set-group-id
    -r файл
    True
    (истина), если
    файл
    доступен для чтения
    -s файл
    True
    (истина), если
    файл
    ненулевого размера
    -u файл
    True
    (истина), если для
    файла
    установлен бит set-user-id
    -v файл
    True
    (истина), если
    файл
    доступен для записи
    файл
    True
    (истина), если
    файл
    — исполняемый файл
    Примечание

    Вас могли заинтересовать непонятные биты set-group-id и set-user-id (также называемые set-gid и set-uid). Бит set-uid предоставляет программе права владельца, а не просто ее пользователя, бит set-gid предоставляет программе права группы. Эти биты устанавливаются командой

    chmod
    с помощью опций
    s
    и
    g
    . На файлы, содержащие сценарии, флаги set-gid и set-uid не влияют, они оказывают влияние только на исполняемые двоичные файлы.

    Мы немного сами себя обогнали, но далее следует пример тестирования состояния файла /bin/bash, так что вы сможете увидеть, как это выглядит на практике.

    #!/bin/sh


    if [ -f /bin/bash ]

    then

     echo "file /bin/bash exists"

    fi


    if [ -d /bin/bash ]

    then

     echo "/bin/bash is a directory"

    else

     echo "/bin/bash is NOT a directory"

    fi

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

    test
    , полный список можно найти в интерактивном справочном руководстве. Если вы применяете оболочку bash, в которую встроена команда
    test
    , используйте команду
    help test
    для получения дополнительных сведений. Позже в этой главе мы применим некоторые из этих опций.

    Теперь, когда вы познакомились с условиями, можно рассмотреть управляющие структуры, использующие эти условия.

    Управляющие структуры

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

    Примечание

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

    if

    Управляющий оператор

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

    if условие

    then

     операторы

    else

     операторы

    fi

    Наиболее часто оператор

    if
    применяется, когда задается вопрос, и решение принимается в зависимости от ответа:

    #!/bin/sh


    echo "Is it morning? Please answer yes or no "

    read timeofday

    if [ $timeofday = "yes" ]; then

     echo "Good morning"

    else

     echo "Good afternoon"

    fi

    exit 0

    В результате будет получен следующий вывод на экран:

    Is it morning? Please answer yes or no

    yes

    Good morning

    $

    В этом сценарии для проверки содержимого переменной

    timeofday
    применяется команда
    [
    . Результат оценивается оператором командной оболочки
    if
    , который затем разрешает выполнять разные строки программного кода.

    Примечание

    Обратите внимание на дополнительные пробелы, используемые для формирования отступа внутри оператора

    if
    . Это делается только для удобства читателя; командная оболочка игнорирует дополнительные пробелы.

    elif

    К сожалению, с этим простым сценарием связано несколько проблем. Во-первых, он принимает в значении

    no
    (нет) любой ответ за исключением
    yes
    (да). Можно помешать этому, воспользовавшись конструкцией
    elif
    , которая позволяет добавить второе условие, проверяемое при выполнении части
    else
    оператора
    if
    (упражнение 2.3). 

    Упражнение 2.3. Выполнение проверок с помощью
    elif

    Вы можете откорректировать предыдущий сценарий так, чтобы он выводил сообщение об ошибке, если пользователь вводит что-либо отличное от

    yes
    или
    no
    . Для этого замените ветку
    else
    веткой
    elif
    и добавьте еще одно условие:

    #!/bin/sh

    echo "Is it morning? Please answer yes or no "

    read timeofday


    if [ $timeofday = "yes" ]

    then

     echo "Good morning"

    elif [ $timeofday = "no" ]; then

     echo "Good afternoon"

    else

     echo "Sorry, $timeofday not recognized. Enter yes or no "

     exit 1

    fi

    exit 0

    Как это работает

    Этот пример очень похож на предыдущий, но теперь, если первое условие не равно

    true
    , оператор командной оболочки
    elif
    проверяет переменную снова. Если обе проверки не удачны, выводится сообщение об ошибке, и сценарий завершается со значением 1, которое в вызывающей программе можно использовать для проверки успешного выполнения сценария.

    Проблема, связанная с переменными

    Данный сценарий исправляет наиболее очевидный дефект, а более тонкая проблема остается незамеченной. Запустите новый вариант сценария, но вместо ответа на вопрос просто нажмите клавишу <Enter> (или на некоторых клавиатурах клавишу <Return>). Вы получите сообщение об ошибке:

    [: =: unary operator expected

    Что же не так? Проблема в первой ветви оператора

    if
    . Когда проверялась переменная
    timeofday
    , она состояла из пустой строки. Следовательно, ветвь оператора
    if
    выглядела следующим образом:

    if [ = "yes" ]

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

    if [ "$timeofday" = "yes" ]

    Теперь проверка с пустой переменной будет корректной:

    if [ "" = "yes" ]

    Новый сценарий будет таким:

    #!/bin/sh


    echo "Is it morning? Please answer yes or no "

    read timeofday


    if [ "$timeofday" = "yes" ]

    then

     echo "Good morning"

    elif [ "$timeofday" = "no" ]; then

     echo "Good afternoon"

    else

     echo "Sorry, $timeofday not recognized. Enter yes or no "

     exit 1

    fi


    exit 0

    Этот вариант безопасен, даже если пользователь в ответ на вопрос просто нажмет клавишу <Enter>.

    Примечание

    Если вы хотите, чтобы команда

    echo
    удалила новую строку в конце, наиболее легко переносимый вариант — применить команду
    printf
    (см. разд. "printf" далее в этой главе) вместо команды
    echo
    . В некоторых командных оболочках применяется команда
    echo -е
    , но она поддерживается не всеми системами. В оболочке bash для запрета перехода на новую строку допускается команда
    echo -n
    , поэтому, если вы уверены, что вашему сценарию придется трудиться только в оболочке bash, предлагаем вам использовать следующий синтаксис:

    echo -n "Is it morning? Please answer yes or no: "

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

    for

    Применяйте конструкцию

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

    Синтаксис этого оператора прост:

    for переменная in значения

    do

     операторы

    done

    Выполните упражнения 2.4 и 2.5.

    Упражнение 2.4. Применение цикла
    for
    к фиксированным строкам

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

    #!/bin/sh


    for foo in bar fud 43

    do

     echo $foo

    done

    exit 0

    В результате будет получен следующий вывод:

    bar

    fud

    43

    Примечание

    Что произойдет, если вы измените первую строку с

    for foo in bar fud 43
    на
    for foo in "bar fud 43"
    ? Напоминаем, что вставка кавычек заставляет командную оболочку считать все, что находится между ними, единой строкой. Это один из способов сохранения пробелов в переменной.

    Как это работает

    В данном примере создается переменная

    foo
    и ей в каждом проходе цикла
    for
    присваиваются разные значения. Поскольку оболочка считает по умолчанию все переменные строковыми, применять строку 43 так же допустимо, как и строку
    fud
    .

    Упражнение 2.5. Применение цикла
    for
    с метасимволами

    Как упоминалось ранее, цикл

    for
    обычно используется в командной оболочке вместе с метасимволами или знаками подстановки для имен файлов. Это означает применение метасимвола для строковых значений и предоставление оболочке возможности подставлять все значения на этапе выполнения.

    Вы уже видели этот прием в первом примере first. В сценарии применялись средства подстановки командной оболочки — символ

    *
    для подстановки имен всех файлов из текущего каталога. Каждое из этих имен по очереди используется в качестве значения переменной
    $file
    внутри цикла
    for
    .

    Давайте бегло просмотрим еще один пример подстановки с помощью метасимвола. Допустим, что вы хотите вывести на экран все имена файлов сценариев в текущем каталоге, начинающиеся с буквы "f", и вы знаете, что имена всех ваших сценариев заканчиваются символами .sh. Это можно сделать следующим образом:

    #!/bin/sh


    for file in $(ls f*.sh); do

     lpr $file

    done

    exit 0

    Как это работает

    В этом примере показано применение синтаксической конструкции

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

    Командная оболочка раскрывает

    f*.sh
    , подставляя имена всех файлов, соответствующих данному шаблону.

    Примечание

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

    while

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

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

    Если нужно повторить выполнение последовательности команд, но заранее не известно, сколько раз следует их выполнить, вы, как правило, будете применять цикл

    while
    со следующей синтаксической записью:

    while  условие

    do

     операторы

    done

    Далее приведен пример довольно слабой программы проверки паролей.

    #!/bin/sh


    echo "Enter password"

    read trythis

    while [ "$trythis" != "secret" ]; do

     echo "Sorry, try again"

     read trythis

    done

    exit 0

    Следующие строки могут служить примером вывода данного сценария:

    Enter password

    password

    Sorry, try again

    secret

    $

    Ясно, что это небезопасный способ выяснения пароля, но он вполне подходит для демонстрации применения цикла

    while
    . Операторы, находящиеся между операторами
    do
    и
    done
    , выполняются бесконечное число раз до тех пор, пока условие остается истинным (
    true
    ). В данном случае вы проверяете, равно ли значение переменной
    trythis
    строке
    secret
    . Цикл будет выполняться, пока
    $trythis
    не равно
    secret
    . Затем выполнение сценария продолжится с оператора, следующего сразу за оператором
    done
    .

    until

    У цикла

    until
    следующая синтаксическая запись:

    until  условие

    do

     операторы

    done

    Она очень похожа на синтаксическую запись цикла

    while
    , но с обратным проверяемым условием. Другими словами, цикл продолжает выполняться, пока
    условие
    не станет истинным (true).

    Примечание

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

    while
    ; если такой необходимости нет, используют цикл
    until
    .

    Как пример цикла

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

    #!/bin/bash


    until who | grep "$1" > /dev/null

    do

     sleep 60

    done

    # Теперь звонит колокольчик и извещает о новом пользователе

    echo -е '\а'

    echo "**** $1 has just logged in ****"

    exit 0

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

    until
    , а не цикл
    while
    .

    case

    Оператор

    case
    немного сложнее уже рассмотренных нами операторов. У него следующая синтаксическая запись:

    case переменная  in

     образец [ | образец] ...) операторы;;

     образец [ | образец] ...) операторы;;

    esac

    Конструкция оператора

    case
    выглядит слегка устрашающей, но она позволяет довольно изощренным способом сопоставлять содержимое переменной с образцами и затем выполнять разные операторы в зависимости от того, с каким образцом найдено соответствие. Это гораздо проще, чем проверять несколько условий, применяемых во множественных операторах
    if
    ,
    elif
    и
    else
    .

    Примечание

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

    ;;
    ). В каждой ветви оператора
    case
    можно поместить несколько операторов, поэтому сдвоенная точка с запятой необходима для отметки завершения очередного оператора и начала следующей ветви с новым образцом в операторе
    case
    .

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

    case
    очень удобной для обработки пользовательского ввода. Лучше всего увидеть, как работает конструкция
    case
    на примерах. Мы будем применять ее в упражнениях 2.6–2.8, каждый раз совершенствуя сопоставление с образцами.

    Примечание

    Применяя конструкцию

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

    Упражнение 2.6. Вариант 1: пользовательский ввод

    Вы можете написать новую версию сценария проверки пользовательского ввода с помощью конструкции

    case
    , сделав сценарий немного более избирательным и терпимым к неожиданным вариантам ввода.

    #!/bin/sh


    echo "Is it morning? Please answer yes or no "

    read timeofday


    case "$timeofday" in

     yes) echo "Good Morning";;

     no ) echo "Good Afternoon";;

     y  ) echo "Good Morning";;

     n  ) echo "Good Afternoon";;

     *  ) echo "Sorry, answer not recognized";;

    esac


    exit 0

    Как это работает

    Когда выполняется оператор

    case
    , он берет содержимое переменной
    timeofday
    и сравнивает его поочередно с каждой строкой-образцом. Как только строка совпадает с введенной информацией, оператор
    case
    выполняет код, следующий за
    )
    , и завершается.

    Оператор

    case
    выполняет обычную подстановку в строках, которые он использует для сравнения. Следовательно, вы можете задать часть строки с последующим метасимволом
    *
    . Применение единственного символа
    *
    будет соответствовать совпадению с любой введенной строкой, поэтому поместите этот вариант после всех остальных образцов строк для того, чтобы задать некоторое стандартное поведение оператора
    case
    , если не будут найдены совпадения с другими строками-образцами. Это возможно, потому что оператор
    case
    сравнивает с каждой строкой-образцом поочередно. Он не ищет наилучшее соответствие, а всего лишь первое встретившееся. Условие, принятое по умолчанию, часто оказывается невыполнимым, поэтому применение метасимвола 
    *
    может помочь в отладке сценариев.

    Упражнение 2.7. Вариант 3: объединение образцов

    Предыдущая версия конструкции case, безусловно, элегантнее варианта с множественными операторами

    if
    , но, объединив все образцы, можно создать более красивую версию.

    #!/bin/sh

    echo "Is it morning? Please answer yes or no "

    read timeofday


    case "$timeofday" in

     yes | y | Yes | YES ) echo "Good Morning";;

     n* | N*)              echo "Good Afternoon";;

     * )                   echo "Sorry, answer not recognized";;

    esac


    exit 0

    Как это работает

    Данный сценарий в операторе

    case
    использует несколько строк-образцов в каждой ветви, таким образом,
    case
    проверяет несколько разных строк для каждого возможного оператора. Этот прием делает сценарий короче и, как показывает практика, облегчает его чтение. Приведенный программный код также показывает, как можно использовать метасимвол
    *
    , несмотря на то, что он может соответствовать непредусмотренным образцам. Например, если пользователь введет строку
    never
    , она будет соответствовать образцу
    n*
    , и на экран будет выведено приветствие Good Afternoon (Добрый день), хотя такое поведение в сценарии не предусматривалось. Учтите также, что заключенный в кавычки знак подстановки
    *
    не действует.

    Упражнение 2.8. Вариант 3: выполнение нескольких операторов

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

    #!/bin/sh


    echo "Is it -morning? Please answer yes or no"

    read timeofday


    case "$timeofday" in

     yes | y | Yes | YES )

      echo "Good Morning"

      echo "Up bright and early this morning"

      ;;

     [nN]*)

      echo "Good Afternoon"

      ;;

     *)

      echo "Sorry, answer not recognized"

      echo "Please answer yes or no"

      exit 1

      ;;

    esac


    exit 0

    Как это работает

    Для демонстрации другого способа определения соответствия образцу в этом программном коде изменен вариант определения соответствия для ветви

    no
    . Также видно, как в каждой ветви оператора case может выполняться несколько операторов. Следует быть внимательным и располагать в операторе самые точные образцы строк первыми, а самые общие варианты образцов последними. Это очень важно, потому что оператор
    case
    выполняется, как только найдено первое, а не наилучшее соответствие. Если вы поставите ветвь
    *)
    первой, совпадение с этим образцом будет определяться всегда, независимо от варианта введенной строки.

    Примечание

    Учтите, что сдвоенная точка с запятой

    ;;
    перед служебным словом
    esac
    необязательна. В отличие от программирования на языке С, в котором пропуск маркера завершения считается плохим стилем программирования, пропуск
    ;;
    не создает проблем, если последняя ветвь оператора
    case
    — это вариант, принятый по умолчанию, поскольку другие образцы уже не будут анализироваться.

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

    [yY] | [Yy][Ее][Ss])

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

    *
    .

    Списки

    Иногда может понадобиться сформировать последовательность команд. Например, вы хотите выполнить оператор, только если удовлетворяется несколько условий.

    if [ -f this_file ]; then

     if [ -f that_file ]; then

      if [ -f the_other_file ]; then

       echo "All files present, and correct"

      fi

     fi

    fi

    Или вы хотите, чтобы хотя бы одно условие из последовательности условий было истинным.

    if [ -f this_file ]; then

     foo="True"

    elif [ -f that_file ]; then

     foo="True"

    elif [ -f the_other_file ];

     then foo="True"

    else

     foo="False"

    fi

    if ["$foo" = "True" ]; then

     echo "One of the files exists"

    fi

    Несмотря на то, что это можно реализовать с помощью нескольких операторов

    if
    , как видите, результаты получаются очень громоздкими. В командной оболочке есть пара специальных конструкций для работы со списками команд: И-список (AND list) и ИЛИ-список (OR list). Обе они часто применяются вместе, но мы рассмотрим синтаксическую запись каждой из них отдельно.

    И-cписок

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

    оператор1 && оператор2 && оператор3  && ...

    Выполнение операторов начинается с самого левого, если он возвращает значение

    true
    (истина), выполняется оператор, расположенный справа от первого оператора. Выполнение продолжается до тех пор, пока очередной оператор не вернет значение
    false
    (ложь), после чего никакие операторы списка не выполняются. Операция
    &&
    проверяет условие предшествующей команды.

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

    Выполните упражнение 2.9.

    Упражнение 2.9. И-списки

    В следующем сценарии вы обращаетесь к файлу file_one (для проверки его наличия, и если файл не существует, создаете его) и затем удаляете файл file_two. Далее И-список проверяет наличие каждого файла и между делом выводит на экран кое-какой текст.

    #!/bin/sh


    touch file_one

    rm -f file_two


    if [ -f file_one ] && echo "hello" [ -f file_two ] && echo " there"

    then

     echo "in if"

    else

     echo "in else"

    fi


    exit 0

    Попробуйте выполнить сценарий, и вы получите следующий вывод:

    hello

    in else

    Как это работает

    Команды

    touch
    и
    rm
    гарантируют, что файлы в текущем каталоге находятся в известном состоянии. Далее И-список выполняет команду
    [ -f file one ]
    , которая возвращает значение
    true
    , потому что вы только что убедились в наличии файла. Поскольку предыдущий оператор завершился успешно, теперь выполняется команда
    echo
    . Она тоже завершается успешно (
    echo
    всегда возвращает
    true
    ). Затем выполняется третья проверка
    [ -f file_two ]
    . Она возвращает значение
    false
    , т.к. файл не существует. Поскольку последняя команда вернула
    false
    , заключительная команда
    echo
    не выполняется. В результате И-список возвращает значение
    false
    , поэтому в операторе
    if
    выполняется вариант
    else
    .

    ИЛИ-список

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

    оператор1 || оператор2 || оператор3 || ...

    Операторы выполняются слева направо. Если очередной оператор возвращает значение

    false
    , выполняется следующий за ним оператор. Это продолжается до тех пор, пока очередной оператор не вернет значение
    true
    , после этого никакие операторы уже не выполняются.

    ИЛИ-список очень похож на И-список, за исключением того, что правило для выполнения следующего оператора — выполнение предыдущего оператора со значением

    false
    .

    Рассмотрим упражнение 2.10.

    Упражнение 2.10. ИЛИ-списки

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

    #!/bin/sh


    rm -f file_one


    if [ -f file_one ] || echo "hello" || echo " there" then

     echo "in if"

    else

     echo "in else"

    fi


    exit 0

    В результате выполнения данного сценария будет получен следующий вывод:

    hello

    in if

    Как это работает

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

    [ -f file one ]
    возвращает значение
    false
    , потому что файла в каталоге нет. Далее выполняется команда
    echo
    . Вот это да — она возвращает значение
    true
    , и больше в ИЛИ-списке не выполняются никакие команды. Оператор
    if
    получает из списка значение true, поскольку одна из команд ИЛИ-списка (команда
    echo
    ) вернула это значение.

    Результат, возвращаемый обоими этими списками, — это результат последней выполненной команды списка.

    Описанные конструкции списков выполняются так же, как аналогичные конструкции в языке С, когда проверяются множественные условия. Для определения результата выполняется минимальное количество операторов. Операторы, не влияющие на конечный результат, не выполняются. Обычно этот подход называют оптимизацией вычислений (short circuit evaluation).

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

    [ -f file_one ] && команда в случае true || команда в случае false

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

    Операторные блоки

    Если вы хотите применить несколько операторов в том месте программного кода, где разрешен только один, например в ИЛИ-списке или И-списке, то можете сделать это, заключив операторы в фигурные скобки

    {}
    и создав тем самым операторный блок. Например, в приложении, представленном далее в этой главе, вы увидите следующий фрагмент программного кода:

    get_confirm && {

     grep -v "$cdcatnum" $tracks_file > $temp_file

     cat $temp_file > $tracks_file

     echo

     add record_tracks

    }

    Функции

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

    Примечание

    Как альтернативу можно использовать разбиение большого сценария на много маленьких, каждый из которых выполняет небольшую задачу. У этого подхода есть несколько недостатков: выполнение вложенного в сценарий другого сценария будет гораздо медленнее, чем выполнение функции. Значительно труднее возвращать результаты, и может появиться большое количество маленьких сценариев. Следует рассмотреть наименьшую практически самостоятельную часть вашего сценария и использовать ее как эталон для того, чтобы определить, когда возникает необходимость разбиения большого сценария на коллекцию меньших по размеру сценариев.

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

    Имя_функции() {

    операторы

    }

    Выполните упражнения 2.11 и 2.12.

    Упражнение 2.11. Простая функция

    Давайте начнем с действительно простой функции.

    #!/bin/sh


    foo() {

     echo "Function foo is executing"

    }


    echo "script starting"

    foo

    echo "script ended"

    exit 0

    Выполняющийся сценарий, выведет на экран следующий текст:

    script starting

    Function foo is executingscript ended

    Как это работает

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

    foo() {
    , он знает, что здесь дается определение функции, названной
    foo
    . Он запоминает ссылку на функцию и foo продолжает выполнение после обнаружения скобки
    }
    . Когда выполняется строка с единственным именем
    foo
    , командная оболочка знает, что нужно выполнить предварительно определенную функцию. Когда функция завершится, выполнение сценария продолжится в строке, следующей за вызовом функции
    foo
    .

    Вы должны всегда определить функцию прежде, чем сможете ее запустить, немного похоже на стиль, принятый в языке программирования Pascal, когда вызову функции предшествует ее определение, за исключением того, что в командной оболочке нет опережающего объявления (forward) функции. Это ограничение не создает проблем, потому что все сценарии выполняются с первой строки, поэтому если просто поместить все определения функций перед первым вызовом любой функции, все функции окажутся определенными до того, как будут вызваны.

    Когда функция вызывается, позиционные параметры сценария

    $*
    ,
    $@
    ,
    $#
    ,
    $1
    ,
    $2
    и т.д. заменяются параметрами функции. Именно так вы считываете параметры, передаваемые функции. Когда функция завершится, они восстановят свои прежние значения.

    Примечание

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

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

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

    foo() { echo JAY;}

    ...

    result="$(foo)"

    Вы можете объявлять локальные переменные в функциях командной оболочки с помощью ключевого слова

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

    #!/bin/sh


    sample_text="global variable"


    foo() {

     local sample_text="local variable"

     echo "Function foo is executing"

     echo $sample_text

    }

    echo "script starting"

    echo $sample_text


    foo


    echo "script ended"

    echo $sample_text


    exit 0

    При отсутствии команды

    return
    , задающей возвращаемое значение, функция возвращает статус завершения последней выполненной команды,

    Упражнение 2.12. Возврат значения

    В следующем сценарии, my_name, показано, как в функцию передаются параметры и как функции могут вернуть логический результат

    true
    или
    false
    . Вы можете вызвать этот сценарий с параметром, задающим имя, которое вы хотите использовать в вопросе.

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

    yes_or_no
    .

    #!/bin/sh


    yes_or_no() {

     echo "Is your name $* ? "

     while true

     do

      echo -n "Enter yes or no: "

      read x

      case "$x" in

       y | yes ) return 0;;

       n | no )  return 1;;

       * )       echo "Answer yes or no"

      esac

     done

    }

    2. Далее начинается основная часть программы.

    echo "Original parameters are $*"

    if yes_or_no "$1"

    then

     echo "Hi $1, nice name"

    else

     echo "Never mind"

    fi

    exit 0

    Типичный вывод этого сценария может выглядеть следующим образом:

    $ ./my_name Rick Neil

    Original parameters are Rick Neil

    Is your name Rick ?

    Enter yes or no: yes

    Hi Rick, nice name

    $

    Как это работает

    Когда сценарий начинает выполняться, функция определена, но еще не выполняется. В операторе

    if
    сценарий вызывает функцию
    yes_or_no
    , передавая ей оставшуюся часть строки как параметры после замены
    $1
    первым параметром исходного сценария строкой
    Rick
    . Функция использует эти параметры, в данный момент хранящиеся в позиционных параметрах
    $1
    ,
    $2
    и т.д., и возвращает значение в вызывающую программу. В зависимости от возвращенного функцией значения конструкция
    if
    выполняет один из операторов.

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

    Команды

    В сценариях командной оболочки можно выполнять два сорта команд. Как уже упоминалось, существуют "обычные" команды, которые могут выполняться и из командной строки (называемые внешними командами), и встроенные команды (называемые внутренними командами). Внутренние команды реализованы внутри оболочки и не могут вызываться как внешние программы. Но большинство внутренних команд представлено и в виде автономных программ, это условие — часть требований стандарта POSIX. Обычно, не важно, команда внешняя или внутренняя, за исключением того, что внутренние команды действуют эффективнее.

    В этом разделе представлены основные команды, как внутренние, так и внешние, которые мы используем при написании сценариев. Как пользователь ОС Linux, вы, возможно, знаете много других команд, которые принимает командная строка. Всегда помните о том, что вы можете любую из них применить в сценарии в дополнение к встроенным командам, представленным в данном разделе.

    break

    Используйте команду

    break
    для выхода из циклов
    for
    ,
    while
    и
    until
    до того, как будет удовлетворено управляющее условие. В команде
    break
    можно задать дополнительный числовой параметр, указывающий на число циклов, из которых предполагается выход. Однако это может сильно усложнить чтение сценариев, поэтому мы не советуем вам использовать его. По умолчанию
    break
    обеспечивает выход из одного цикла.

    #!/bin/sh


    rm -rf fred*

    echo > fred1

    echo > fred2

    mkdir fred3

    echo > fred4


    for file in fred*

    do

     if [ -d "$file" ]; then

      break;

     fi

    done


    echo first directory starting fred was $file


    m -rf fred*

    exit 0

    Команда :

    Команда "двоеточие" — фиктивная команда. Она иногда полезна для упрощения логики в условиях, будучи псевдонимом команды

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

    Вы можете найти эту команду в условии для циклов

    while
    . Конструкция
    while :
    выполняет бесконечный цикл вместо более общего
    while true
    .

    Конструкция

    :
    также полезна для условного задания переменных. Например,

    : ${var:=value}

    Без

    :
    командная оболочка попытается интерпретировать
    $var
    как команду.

    Примечание

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

    #
    , поскольку этот вариант действует эффективнее.

    #!/bin/sh


    rm -f fred

    if [ -f fred ]; then

     :

    else

     echo file fred did not exist

    fi


    exit 0

    continue

    Как и одноименный оператор языка С, эта команда заставляет охватывающий ее цикл

    for
    ,
    while
    или
    until
    начать новый проход или следующую итерацию. При этом переменная цикла принимает следующее значение в списке.

    #!/bin/sh


    rm -rf fred*

    echo > fred1

    echo > fred2

    mkdir fred3

    echo > fred4


    for file in fred*

    do

     if [ -d "$file" ]; then

      echo "skipping directory $file"

      continue

     fi

     echo file is $file

    done


    rm -rf fred*

    exit 0

    Команда

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

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

    for x in 1 2 3

    do

     echo before $x

     continue 1

     echo after $x

    done

    У приведенного фрагмента будет следующий вывод:

    before 1

    before 2

    before 3

    Команда .

    Команда "точка" (

    .
    ) выполняет команду в текущей оболочке:

    . ./shell_script

    Обычно, когда сценарий выполняет внешнюю команду или сценарий, создается новое окружение (подоболочка), команда выполняется в новом окружении и затем окружение удаляется, за исключением кода завершения, который возвращается в родительскую оболочку. Внешняя команда source и команда "точка" (еще два синонима) выполняют команды, приведенные в сценарии, в той же командной оболочке, которая выполняет сценарий.

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

    В сценариях командной оболочки команда "точка" играет роль, немного похожую на роль директивы #include в языках программирования С и С++. И хотя она не подключает сценарий в буквальном смысле слова, она действительно выполняет команду в текущем контексте, поэтому вы можете применять ее для включения переменных и определений функций в ваш сценарий.

    Выполните упражнение 2.13.

    Упражнение 2.13. Команда точка

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

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

    #!/bin/sh


    version=classic

    PATH=/usr/local/old_bin:/usr/bin:/bin:

    .

    PS1="classic> "

    2. Для новых команд применяется latest_set.

    #!/bin/sh


    version=latest

    PATH=/usr/local/new_bin:/usr/bin:/bin:

    .

    PS1=" latest version> "

    Вы можете установить окружение, применяя эти сценарии в сочетании с командой "точка", как показано в следующей порции примера.

    $ . ./classic_set

    classic> echo $version

    classic

    classic> . /latest_set

    latest version> echo $version

    latest

    latest version>

    Как это работает

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

    echo

    Несмотря на призыв группы Х/Open применять в современных командных оболочках команду

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

    При этом возникает общая проблема: удаление символа перехода на новую строку. К сожалению, в разных версиях ОС UNIX реализованы разные решения. В ОС Linux общепринятый метод

    echo -n "string to output"

    Но вы часто будете сталкиваться и с вариантом

    echo -е "string to output\c"

    Второй вариант

    echo -е
    рассчитан на то, что задействована интерпретация символов escape-последовательности, начинающихся с обратного слэша, таких как
    \c
    для подавления новой строки,
    \t
    для вывода табуляции,
    \n
    для вывода символов возврата каретки. В более старых версиях bash этот режим установлен по умолчанию, а в более современных версиях интерпретация символов escape-последовательностей с обратным слэшем отключена. Подробные сведения о поведении вашего дистрибутива ищите на страницах интерактивного справочного руководства.

    Примечание

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

    tr
    , но она будет выполняться немного медленнее. Если вашим системам UNIX нужна переносимость и нужно избавиться от завершающей новой строки, как правило, лучше придерживаться команды
    printf
    . Если ваши сценарии предназначены для работы только в ОС Linux и bash, вполне подойдет
    echo -n
    , хотя, возможно, придется начинать файл со строки
    #!/bin/bash
    для того, чтобы в явной форме показать, что вы рассчитываете на поведение в стиле bash.

    eval

    Команда eval позволяет вычислять аргументы. Она встроена в командную оболочку и обычно не представлена как отдельная команда. Лучше всего ее действие демонстрирует короткий пример, позаимствованный непосредственно из стандарта X/Open.

    foo=10

    x=foo

    у='$'$х

    echo $у

    Будет выведено

    $foo
    . Однако код

    foo=10

    x=foo

    eval у='$'$х

    echo $у

    выведет на экран 10. Таким образом,

    eval
    немного похожа на дополнительный знак
    $
    : она возвращает значение значения переменной.

    Команда

    eval
    очень полезна, т.к. позволяет генерировать и выполнять код на лету. Применение этой команды усложняет отладку сценария, но разрешает делать то, что в противном случае выполнить сложно или даже невозможно.

    exec

    У команды

    exec
    два варианта применения. Обычно ее используют для замены текущей командной оболочки другой программой.

    Например, строка

    exec wall "Thanks for all the fish"

    в сценарии заменит текущую оболочку командой

    wall
    . Строки, следующие за командой
    exec
    , не обрабатываются, потому что командная оболочка, выполнявшая сценарий, больше не существует.

    Второй вариант применения

    exec
    — модификация текущих дескрипторов файлов.

    exec 3< afile

    Эта команда открывает файловый дескриптор 3 для чтения из файла afile. Этот вариант редко используется.

    exit n

    Команда

    exit
    вызывает завершение сценария с кодом завершения
    n
    .
    Если вы примените ее в строке подсказки или приглашения любой интерактивной командной оболочки, она приведет к вашему выходу из системы. Если разрешить сценарию завершиться без указания кода завершения, статус последней выполненной в сценарии команды используется как возвращаемое значение. Задание кода завершения считается хорошим стилем программирования.

    При программировании сценариев в командной оболочке код завершения 0 — успешное завершение сценария, коды от 1 до 125 включительно — коды ошибок, которые можно использовать в сценариях. Оставшиеся значения зарезервированы в соответствии с табл. 2.5.


    Таблица 2.5

    Код завершения Описание
    126 Файл не является исполняемым
    127 Команда не найдена
    128 и выше Появившийся сигнал

    Многим программистам на языках С и С++ использование нуля как признака успешного завершения может показаться несколько необычным. Большое преимущество сценариев — возможность применения 125 кодов ошибок, определенных пользователем, и отсутствие необходимости в глобальной переменной для хранения кода ошибки.

    Далее приведен простой пример, возвращающий код успешного завершения, если в текущем каталоге существует файл с именем .profile.

    #!/bin/sh


    if [ -f .profile ]; then

     exit 0

    fi

    exit 1

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

    [ -f .profile ] && exit 0 || exit 1

    export

    Команда

    export
    делает переменную, называемую ее параметром, доступной в подоболочках. По умолчанию переменные, созданные в командной оболочке, не доступны в новых дочерних подоболочках, запускаемых из данной. Команда
    export
    создает из своего параметра переменную окружения, которая видна другим сценариям и программам, запускаемым из текущей программы. Говоря профессиональным языком, экспортируемые переменные формируют переменные окружения в любых дочерних процессах, порожденных командной оболочкой. Лучше всего проиллюстрировать это примером из двух сценариев:
    export1
    и
    export2
    (упражнение 2.14).

    Упражнение 2.14. Экспорт переменных

    1. Первым представим сценарий export2.

    #!/bin/sh


    echo "$foo"

    echo "$bar"

    2. Теперь сценарий export1. В конце сценария запускается export2.

    #!/bin/sh


    foo="The first meta-syntactic variable"

    export bar="The second meta-syntactic variable"

    export2

    Если вы запустите их, то получите следующий результат.

    $ ./export1

    The second meta-syntactic variable

    $

    Как это работает

    Сценарий export2 просто выводит значения двух переменных. В сценарии export1 задаются значения обеих переменных, но только переменная bar помечается как экспортируемая, поэтому, когда впоследствии запускается сценарий export2, значение переменной

    foo
    потеряно, а значение переменной
    bar
    экспортировано во второй сценарий. На экране появляется пустая строка, поскольку
    $foo
    ничего не содержит и вывод переменной со значением
    null
    приводит к отображению новой строки.

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

    bar
    также была бы доступна.

    Примечание

    Команды

    set -а
    или
    set -allexport
    экспортируют все переменные соответственно.

    expr

    Команда

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

    х=`expr $x + 1`

    Символы

    ``
    (обратная кавычка или обратный апостроф) заставляют переменную
    х
    принять результат выполнения команды
    expr $х + 1
    . Ее можно также записать с помощью синтаксической конструкции
    $( )
    вместо обратной кавычки, например, следующим образом:

    х=$(expr $х + 1)

    Команда expr обладает большими возможностями, с ее помощью можно вычислять различные выражения. Основные виды вычислений перечислены в табл. 2.6.


    Таблица 2.6

    Вычисление выражения Описания
    Выражение1 | Выражение2
    Выражение1
    , если
    Выражение1
    не равно нулю, в противном случае
    Выражение2
    Выражение1 & Выражение2
    Нуль, если оба выражения равны нулю, в противном случае
    Выражение1
    Выражение1 = Выражение2
    Равенство
    Выражение1 > Выражение2
    Больше чем
    Выражение1 >= Выражение2
    Больше или равно
    Выражение1 < Выражение2
    Меньше чем
    Выражение1 <= Выражение2
    Меньше или равно
    Выражение1 != Выражение2
    Неравенство
    Выражение1 + Выражение2
    Сложение
    Выражение1Выражение2
    Вычитание
    Выражение1 * Выражение2
    Умножение
    Выражение1 / Выражение2
    Деление нацело
    Выражение1 % Выражение2
    Остаток от деления нацело

    В современных сценариях вместо команды

    expr
    обычно применяется более эффективная синтаксическая конструкция
    $((...))
    , которая будет описана далее в этой главе.

    printf

    Команда

    printf
    есть только в современных командных оболочках. Группа X/Open полагает, что ее следует применять вместо команды
    echo
    для генерации форматированного вывода, несмотря на то, что, кажется, лишь немногие следуют этому совету.

    У команды следующая синтаксическая запись.

    printf "строка формата" параметр1 параметр2 ...

    Строка формата очень похожа с некоторыми ограничениями на применяемую в языках программирования С и С++. Главным образом не поддерживаются числа с плавающей точкой, поскольку все арифметические операции в командной оболочке выполняются над целыми числами. Строка формата состоит из произвольной комбинации литеральных символов, escape-последовательностей и спецификаторов преобразования. Все символы строки формата, отличающиеся от

    \
    и
    %
    , отображаются на экране при выводе.

    В табл. 2.7 приведены поддерживаемые командой escape-последовательности.


    Таблица 2.7

    Escape-последовательность Описание
    \"
    Двойная кавычка
    \\
    Символ обратный слэш
    \a
    Звуковой сигнал тревоги (звонок колокольчика или прерывистый звуковой сигнал)
    \b
    Символ Backspace (стирание слева)
    \c
    Отбрасывание последующего вывода
    \f
    Символ Form feed (подача бумаги)
    \n
    Символ перехода на новую строку
    \r
    Возврат каретки
    \t
    Символ табуляции
    \v
    Символ вертикальной табуляции
    \ooo
    Один символ с восьмеричным значением
    ooo
    \xHH
    Один символ с шестнадцатеричным значением
    HH

    Спецификаторы преобразований довольно сложны, поэтому мы приведем наиболее распространенные варианты их применения. Более подробную информацию можно найти в интерактивном справочном руководстве командной оболочки bash или на страницах раздела 1 интерактивного руководства к команде

    printf
    (
    man 1 printf
    ). (Если вы не найдете нужных сведений в разделе 1, попробуйте поискать в разделе 3.) Спецификатор преобразования состоит из символа
    %
    , за которым следует символ преобразования. Основные варианты преобразований перечислены в табл. 2.8.


    Таблица 2.8

    Символ преобразования  Описание
    D
    Вывод десятичного числа
    С
    Вывод символа
    S
    Вывод строки
    %
    Вывод знака
    %

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

    $ printf "%s\n" hello

    hello

    $ printf "%s %d\t%s" "Hi There" 15 people

    Hi There 15 people

    Обратите внимание на то, что для защиты строки

    Hi There
    и превращения ее в единый параметр, строку нужно заключить в кавычки (
    ""
    ).

    return

    Команда

    return
    служит для возврата значений из функций, как уже упоминалось ранее при обсуждении функций. Команда принимает один числовой параметр, который становится доступен в сценарии, вызывающем функцию. Если параметр не задан, команда
    return
    по умолчанию возвращает код завершения последней команды.

    set

    Команда

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

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

    date
    , содержащая название месяца в виде строки, но нужно отделить его от других полей. Это можно сделать с помощью комбинации команды
    set
    и конструкции
    $(...)
    , которые обеспечат выполнение команды
    date
    и возврат результата (более подробно об этом см. далее). В выводе команды date строка с названием месяца — второй параметр.

    #!/bin/sh


    echo the date is $(date)

    set $(date)

    echo The month is $2


    exit 0

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

    date
    и затем использует позиционный параметр
    $2
    для получения названия месяца.

    Мы использовали команду

    date
    только как простой пример, демонстрирующий, как извлекать позиционные параметры. Поскольку команда
    date
    зависит от языковых параметров или локализации, в действительности мы бы извлекли название месяца командой
    date +%B
    . У команды
    date
    много других вариантов форматирования, более подробную информацию см. на страницах интерактивного справочного руководства к команде.

    Команду

    set
    можно также применять для передачи параметров командной оболочке и тем самым управления режимом ее работы. Наиболее часто используемый вариант команды
    set -х
    , который заставляет сценарий выводить на экран трассировку выполняемой в данный момент команды. Мы обсудим команду
    set
    и ее дополнительные опции позже в этой главе, когда будем рассматривать отладку программ.

    shift

    Команда

    shift
    сдвигает все переменные-параметры на одну позицию назад, так что параметр
    $2
    становится параметром
    $1
    , параметр
    $3
    $2
    и т.д. Предыдущее значение параметра
    $1
    отбрасывается, а значение параметра 
    $0
    остается неизменным. Если в вызове команды
    shift
    задан числовой параметр, параметры сдвигаются на указанное количество позиций. Остальные переменные
    $*
    ,
    $@
    и
    $#
    также изменяются в связи с новой расстановкой переменных-параметров.

    Команда

    shift
    часто полезна при поочередном просмотре параметров, переданных в сценарий, и если вашему сценарию требуется 10 и более параметров, вам понадобится команда
    shift
    для обращения к 10-му параметру и следующим за ним.

    Например, вы можете просмотреть все позиционные параметры:

    #!/bin/sh


    while [ "$1" != "" ]; do

     echo "$1"

     shift

    done


    exit 0

    trap

    Команда

    trap
    применяется для задания действий, предпринимаемых при получении сигналов, которые подробно будут обсуждаться далее в этой книге. Обычное действие — удалить сценарий, когда он прерван. Исторически командные оболочки всегда использовали числа для обозначения сигналов, но в современных сценариях следует применять имена, которые берутся из файла signal.h директивы
    #include
    с опущенным префиксом
    SIG
    . Для того чтобы посмотреть номера сигналов и соответствующие им имена, можно ввести в командной строке команду
    trap -l
    .

    Примечание

    Для тех, кто не знаком с сигналами, это события, асинхронно посылаемые программе. Стандартно они обычно вызывают прекращение выполнения программы.

    С помощью команды

    trap
    передается предпринимаемое действие, за которым следует имя (имена) сигнала для перехвата:

    trap команда сигнал

    Напоминаем, что обычно сценарии обрабатываются интерпретатором сверху вниз, поэтому вы должны задать, команду

    trap
    перед той частью сценария, которую хотите защитить.

    Для возврата к стандартной реакции на сигнал, просто задайте

    команду
    как
    -
    . Для игнорирования сигнала задайте в
    команде
    пустую строку
    ''
    . Команда trap без параметров выводит текущий список перехватов и действий.

    В табл. 2.9 перечислены самые важные, включенные в. стандарт Х/Open сигналы, которые можно отследить (со стандартными номерами в скобках). Дополнительную информацию можно найти на страницах раздела 7 интерактивного справочного руководства, посвященного сигналам (

    man 7 signal
    ).


    Таблица 2.9

    Сигнал Описание
    HUP (1)
    Неожиданный останов; обычно посылается, когда отключается терминал или пользователь выходит из системы
    INT (2)
    Прерывание; обычно посылается нажатием комбинации клавиш <Ctrl>+<C>
    QUIT (3)
    Завершение выполнения; обычно посылается нажатием комбинации клавиш <Ctrl>+<\>
    ABRT (6)
    Аварийное завершение; обычно посылается при возникновении серьезной ошибки выполнения
    ALRM (14)
    Аварийный сигнал; обычно посылается для обработки превышений лимита времени
    TERM (15)
    Завершение; обычно посылается системой, когда она завершает работу

    А теперь выполните упражнение 2.15.

    Упражнение 2.15. Сигналы прерываний

    В следующем сценарии показана простая обработка сигнала.

    #!/bin/sh


    trap 'rm -f /tmp/my_tmp_file_$$' INT

    echo creating file /tmp/my_tmp_file_$$

    date > /tmp/my_tmp_file_$$


    echo "press interrupt (CTRL-C) to interrupt..."

    while [ -f /tmp/my_tmp_file_$$ ] ; do

     echo File exists

     sleep 1

    done


    echo The file no longer exists trap INT

    echo creating file /tmp/my_tmp_file_$$

    date > /tmp/my_tmp_file_$$


    echo "press interrupt (CTRL-C) to interrupt..."

    while [ -f /tmp/my_tmp_file_$$ ]; do

     echo File exists

     sleep 1

    done


    echo we never get here

    exit 0

    Если вы выполните этот сценарий, нажимая и удерживая нажатой клавишу <Ctrl> и затем нажимая клавишу <C> (или любую другую прерывающую комбинацию клавиш) в каждом из циклов, то получите следующий вывод:

    creating file /tmp/my_tmp_file_141

    press interrupt (CTRL-C) to interrupt ...

    File exists

    File exists

    File exists

    File exists

    The file no longer exists

    creating file /tmp/my tmp_file_141

    press interrupt (CTRL-C) to interrupt ...

    File exists

    File exists

    File exists

    File exists

    Как это работает

    Сценарий использует команду

    trap
    для организации выполнения команды
    rm -f /tmp/my_tmp_file_$$
    при возникновении сигнала
    INT
    (прерывание). Затем сценарий выполняет цикл
    while
    до тех пор, пока существует файл. Когда пользователь нажимает комбинацию клавиш <Ctrl>+<C>, выполняется команда
    rm -f /tmp/my_tmp_file_$$
    , а затем возобновляется выполнение цикла
    while
    . Поскольку теперь файл удален, первый цикл
    while
    завершается стандартным образом.

    Далее сценарий снова применяет команду

    trap
    , на этот раз для того, чтобы сообщить, что при возникновении сигнала
    INT
    никакая команда не выполняется. Затем сценарий создает заново файл и выполняет второй цикл
    while
    . Когда пользователь снова нажимает комбинацию клавиш <Ctrl>+<C>, не задана команда для выполнения, поэтому реализуется стандартное поведение: немедленное прекращение выполнения сценария. Поскольку сценарий завершается немедленно, заключительные команды
    echo
    и
    exit
    никогда не выполняются.

    unset

    Команда

    unset
    удаляет переменные или функции из окружения. Она не может проделать это с переменными, предназначенными только для чтения и определенными командной оболочкой, такими как
    IFS
    . Команда применяется редко.

    В следующем сценарии сначала выводится строка

    Hello world
    , а во второй раз новая строка.

    #!/bin/sh


    foo="Hello World"

    echo $foo


    unset foo

    echo $foo

    Примечание

    Написание

    foo=
    подобно, но не идентично применению команды
    unset
    в только что приведенной программе. Оператор
    foo=
    задает для переменной
    foo
    значение
    null
    , но при этом переменная
    foo
    все еще существует. Команда
    unset foo
    удаляет из окружения переменную
    foo
    .

    Еще две полезные команды и регулярные выражения

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

    Команда find

    Первой рассмотрим команду

    find
    . Эта команда, применяющаяся для поиска файлов, чрезвычайно полезна, но новички в ОС Linux часто находят ее немного сложной в использовании в немалой степени из-за ее опций, критериев и аргументов, определяющих действия (action-type), причем результат одного из этих аргументов может влиять на обработку последующих аргументов.

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

    test
    . Выполните приведенную далее команду под именем суперпользователя root, чтобы иметь достаточно прав доступа для обследования всего компьютера.

    # find / -name test -print

    /usr/bin/test

    #

    В зависимости от варианта установки системы на вашей машине вы можете найти и другие файлы, также названные test. Как вы, вероятно, догадываетесь, команда звучит так: "искать, начиная с каталога /, файл с именем test и затем вывести на экран имя файла". Легко, не правда ли? Безусловно.

    Выполнение команды займет какое-то время, она будет искать на нашей машине и на сетевом диске машины с ОС Windows. Это происходит потому, что на компьютере с Linux смонтирована (с помощью пакета SAMBA) порция файловой системы машины с ОС Windows. Похоже, что подобный поиск будет вестись, даже если мы знаем, что искомый файл находится на машине под управлением ОС Linux.

    В этом случае на помощь приходит первая опция. Если вы укажете опцию

    -mount
    , то сможете сообщить команде find о том, что смонтированные каталоги проверять не нужно.

    # find / -mount -name test -print

    /usr/bin/test

    #

    Мы нашли все тот же файл на нашей машине, но на сей раз гораздо быстрее и без поиска в смонтированных файловых системах.

    Полная синтаксическая запись команды

    find
    выглядит следующим образом:

    find [путь] [опции] [критерии] [действия]

    Часть записи

    [путь]
    понятна и проста: вы можете указать абсолютный путь поиска, например,
    /bin
    , или относительный, например
    ..
    При необходимости можно задать несколько путей — например,
    find /var /home
    .

    В табл. 2.10 перечислены основные опции команды.


    Таблица 2.10

    Опция Описание
    -depth
    Поиск в подкаталогах перед поиском в самом каталоге
    -follow
    Следовать по символическим ссылкам
    -maxdepths N
    При поиске проверять не более
    N
    вложенных уровней каталога
    -mount
    (или
    -xdev
    )
    Не искать в каталогах других файловых систем

    Теперь о критериях. В команде

    find
    можно задать большое число критериев, и каждый из них возвращает либо
    true
    , либо
    false
    . В процессе работы команда
    find
    рассматривает по очереди каждый файл и применяет к нему все критерий в порядке их определения. Если очередной критерий возвращает значение
    false
    , команда
    find
    прекращает анализ текущего файла и переходит к следующему; если критерий возвращает значение
    true
    , команда применяет следующий критерий к текущему файлу или совершает заданное действие над ним. В табл. 2.11 перечислены самые распространенные критерии; полный список тестов, которые можно применять в команде
    find
    , вы найдете на страницах интерактивного справочного руководства.


    Таблица 2.11

    Критерий Описание
    -atime N
    К файлу обращались последний раз
    N
    дней назад
    -mtime N
    Файл последний раз изменялся
    N
    дней назад
    -name шаблон
    Имя файла без указания пути соответствует заданному шаблону. Для гарантии того, что шаблон будет передан в команду
    find
    и не будет немедленно обработан командной оболочкой, его следует всегда заключать в кавычки
    -newer другой файл
    Текущий файл, измененный позже, чем другой файл
    -type С
    Файл типа
    C
    , где
    C
    может принимать определенные значения; наиболее широко используемые "
    d
    " для каталогов и "
    f
    " для обычных файлов. Остальные обозначения типов можно посмотреть на страницах интерактивного справочного руководства
    -user имя пользователя
    Файл принадлежит пользователю с заданным именем

    Вы также можете объединять критерии с помощью операторов. Как показано в табл. 2.12, у большинства из них две формы записи: короткая и более длинная форма.


    Таблица 2.12

    Оператор, короткая форма Оператор, длинная форма Описание
    !
    -not
    Инвертирование критерия
    -and
    Оба критерия должны быть истинны
    -or
    Один из критериев должен быть истинным

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

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

    \(-newer X -о -name "_*" \)

    Мы приведем пример сразу после описания "Как это работает". А сейчас выполните упражнение 2.16.

    Упражнение 2.16 Применение команды
    find
    с критериями

    Попытаемся найти в текущем каталоге файлы, измененные после модификации файла while2.

    $ find . -newer while2 -print

    .

    ./elif3

    ./words.txt

    ./words2.txt

    ./_trap

    $

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

    -type f
    .

    $ find . -newer while2 -type f -print

    ./elif3

    ./words.txt

    ./words2.txt

    ./_trap

    $

    Как это работает

    Как это работает? Вы определили, что команда

    find
    должна искать в текущем каталоге (
    .
    ) файлы, измененные позже, чем файл while2 (
    -newer while2
    ), и, если этот критерий пройден, проверять с помощью следующего критерия (
    -type f
    ), обычные ли это файлы. В заключение вы применили действие, с которым уже сталкивались,
    -print
    , просто для того чтобы подтвердить, что файлы были найдены.

    Теперь найдем файлы с именами, начинающимися с символа подчеркивания или измененные позже, чем файл while2, но в любом случае обычные файлы. Этот пример покажет, как объединять критерии с помощью скобок.

    $ find . \( -name "_*" -or -newer while2 \) -type f -print

    ./elif3

    ./words.txt

    ./words2.txt

    ./_break

    ./_if

    ./set

    ./_shift

    ./_trap

    ./_unset

    ./ until

    $

    Это не слишком трудный пример, не так ли? Вы должны экранировать скобки, чтобы они не обрабатывались командной оболочкой, и заключить в кавычки символ

    *
    , чтобы он также был передан непосредственно в команду
    find
    .

    Теперь, когда вы можете правильно искать файлы, рассмотрим действия, которые можно совершить, когда найден файл, соответствующий вашей спецификации. И снова в табл. 2.13 перечислены только самые популярные действия; полный список можно найти на страницах интерактивного справочного руководства.


    Таблица 2.13

    Действие Описание
    -exec команда
    Выполняет
    команду
    . Наиболее широко используемое действие. После табл. 2.13 приведено объяснение способа передачи параметров в команду. Это действие следует завершать символьной парой
    \;
    -ok команда
    Подобно действию
    exec
    , за исключением того, что перед обработкой файлов
    командой
    выводится подсказка для получения подтверждения пользователя на обработку каждого файла. Это действие следует завершать символьной парой
    \;
    -print
    Вывод на экран имени файла
    -ls
    Применение команды
    ls -dils
    к текущему файлу

    Команда в аргументах

    -exec
    и
    -ok
    принимает последующие параметры в строке как собственные, пока не встретится последовательность
    \;
    В действительности команда, в аргументах
    -exec
    и
    -ok
    выполняет встроенную команду, поэтому встроенная команда должна завершиться экранированной точкой с запятой, для того чтобы команда
    find
    могла определить, когда ей следует продолжить поиск в командной строке аргументов, предназначенных для нее самой. Магическая строка
    {}
    — параметр специального типа для команд
    -exec
    и
    -ok
    , который заменяется полным путем к текущему файлу.

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

    ls
    :

    $ find . -newer while2 -type f -exec ls -l  {} \;

    -rwxr-xr-x 1 rick rick  275 Feb 8 17:07 ./elif3

    -rwxr-xr-x 1 rick rick  336 Feb 8 16:52 ./words.txt

    -rwxr-xr-x 1 rick rick 1274 Feb 8 16:52 ./words2.txt

    -rwxr-xr-x 1 rick rick  504 Feb 8 18:43 ./_trap

    $

    Как видите, команда

    find
    чрезвычайно полезна; она только требует небольшой практики для умелого ее применения. И такая практика, как и эксперименты с командой
    find
    , обязательно принесет дивиденды.

    Команда grep

    Вторая очень полезная команда, заслуживающая рассмотрения, — это команда

    grep
    . Необычное имя, означающее общий синтаксический анализатор регулярных выражений (general regular expression parser). Вы применяете команду
    find
    для поиска файлов в вашей системе, а команду
    grep
    для поиска строк в ваших файлах. Действительно, очень часто при использовании команды
    find
    команда
    grep
    передается после аргумента
    -exec
    .

    Команда

    grep
    принимает опции, шаблон соответствия и файлы для поиска:

    grep [опции] шаблон [файлы]

    Если имена файлов не заданы, команда анализирует стандартный ввод.

    Давайте начнем с изучения основных опций команды

    grep
    . И на этот раз в табл. 2.14 приведены только самые важные из них; полный список см. на страницах интерактивного справочного руководства.


    Таблица 2.14

    Опция Описание
    Вместо вывода на экран совпавших с шаблоном строк выводит их количество
    -E
    Включает расширенные регулярные выражения
    -h
    Ужимает обычное начало каждой строки вывода за счет удаления имени файла, в котором строка найдена
    -i
    Не учитывает регистр букв
    -l
    Перечисляет имена файлов со строками, совпадающими с шаблоном; не выводит сами найденные строки
    -v
    Меняет шаблон соответствия для выбора вместо строк, соответствующих шаблону, несовпадающих с ним строк

    Выполните упражнение 2.17.

    Упражнение 2.17. Основной вариант использования команды
    grep

    Посмотрим команду

    grep
    в действии на примерах простых шаблонов.

    $ grep in words.txt

    When shall we three meet again. In thunder, lightning, or in rain?

    I come, Graymalkin!

    $ grep -c in words.txt words2.txt

    words.txt:2 words2.txt:14

    $ grep -c -v in words.txt words2.txt

    words.txt:9

    words2.txt:16$

    Как это работает

    В первом примере нет опций; в нем просто ищется строка

    in
    в файле words.txt и выводятся на экран любые строки, соответствующие условию поиска. Имя файла не отображается, поскольку поиск велся в единственном файле.

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

    В заключение применяется опция

    -v
    для инвертирования критерия поиска и подсчета строк, не совпадающих с шаблоном.

    Регулярные выражения

    Как вы убедились, основной вариант применения команды grep легко усвоить. Теперь пришло время рассмотреть основы построения регулярных выражений, которые позволят вам выполнять более сложный поиск. Как упоминалось ранее в этой главе, регулярные выражения применяются в системе Linux и многих языках программирования с открытым исходным кодом. Вы можете использовать их в редакторе vi и в скриптах на языке Perl, применяя одни и те же принципы, общие для регулярных выражений, где бы они ни появлялись.

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


    Таблица 2.15

    Символ Описание
    ^
    Привязка к началу строки
    $
    Привязка к концу строки
    .
    Любой одиночный символ
    []
    В квадратных скобках содержится диапазон символов, с любым из них возможно совпадение, например, диапазон символов
    a-e
    или инвертированный диапазон, перед которым стоит символ
    ^

    Если вы хотите использовать любые из перечисленных символов как "обычные", поставьте перед ними символ

    \
    . Например, если нужно найти символ
    $
    , просто введите
    \$
    .

    Есть также несколько полезных специальных проверочных шаблонов, которые могут указываться в квадратных скобках (табл. 2.16).


    Таблица 2.16

    Проверочный шаблон Описание
    [:alnum:]
    Буквенно-цифровые символы
    [:alpha:]
    Буквы
    [:ascii:]
    Символы таблицы ASCII
    [:blank:]
    Пробел или табуляция
    [:cntrl:]
    Управляющие символы ASCII
    [:digit:]
    Цифры
    [:graph:]
    Неуправляющие и непробельные символы
    [:lower:]
    Строчные буквы
    [:print:]
    Печатные символы
    [:punct:]
    Знаки пунктуации
    [:space:]
    Пробельные символы, включая вертикальную табуляцию
    [:upper:]
    Прописные буквы
    [:xdigit:]
    Шестнадцатиричные цифры

    Кроме того, если задана опция

    =E
    для расширенного соответствия, за регулярным выражением могут следовать и другие символы, управляющие выполнением проверки на соответствие шаблону (табл. 2.17). В команде
    grep
    перед этими символами необходимо вводить символ
    \
    .


    Таблица 2.17

    Опция  Описание
    ?
    Совпадение не обязательно, но возможно не более одного раза
    *
    Совпадения может не быть, оно может быть однократным или многократным
    +
    Совпадение должно быть однократным или многократным
    {n}
    Совпадение должно быть 
    n
    раз
    {n, }
    Совпадение должно быть
    n
    раз и больше
    {n, m}
    Совпадение должно быть от
    n
    до
    m
    раз включительно

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

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

    $
    :

    $ grep e$ words2.txt

    Art thou not, fatal vision, sensible

    I see thee yet, in form as palpable

    Nature seems dead, and wicked dreams abuse

    $

    Как видите, найдены строки, заканчивающиеся буквой "е".

    2. Теперь найдите трехбуквенные слова, начинающиеся с символов "Th". В данном случае вам понадобится шаблон

    [[:space:]]
    для ограничения длины слова и
    .
    для единственного дополнительного символа.

    $ grep Th.[[:space:]] words 2.txt

    The handle toward my hand? Come, let me clutch thee.

    The curtain'd sleep; witchcraft celebrates

    Thy very stones prate of my whereabout,

    $

    3. В заключение примените расширенный режим поиска в команде

    grep
    для обнаружения слов из строчных букв длиной ровно 10 символов. Для этого задайте диапазон совпадающих символов от а до z и 10 повторяющихся совпадений.

    $ grep -Е [a-z]\{10\} words2.txt

    Proceeding from the heat-oppressed brain?

    And such an instrument I was to use.

    The curtain'd sleep; witchcraft celebrates

    hy very stones prate of my whereabout,

    $

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

    Выполнение команд

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

    Сделать это можно с помощью синтаксической конструкции

    $(команда)
    , показанной ранее в примере с командой
    set
    . Существует устаревший вариант подстановки команды
    `команда`
    , который все еще широко распространен.

    Примечание

    В более раннем варианте конструкции применяется обратный апостроф или обратная кавычка (

    `
    ), а не обычный апостроф (
    '
    ), который мы использовали раньше в командной оболочке для экранирования (защиты от подстановки переменных). В сценариях оболочки применяйте этот вариант, только если вы хотите добиться высокой степени переносимости сценариев.

    Во всех современных сценариях следует применять конструкцию выполнения или подстановки команды

    $(команда)
    , которая введена для того, чтобы избавиться от довольно сложных правил использования символов
    $
    '
    и
    \
    внутри команды, заключенной в обратные апострофы. Если применяется обратный апостроф внутри конструкции
    `...`
     , его необходимо экранировать символом
    \
    . Эти непонятные знаки часто заставляют программистов путаться, и иногда даже опытные специалисты в программировании средствами командной оболочки вынуждены ставить опыты для того, чтобы добиться правильного использования кавычек и апострофов в командах, заключенных в обратные апострофы.

    Результат выполнения конструкции

    $(команда)
    — просто вывод команды. Имейте в виду, что это не статус возврата команды, а просто строковый вывод, показанный далее.

    #!/bin/sh


    echo The current directory is $PWD

    echo The current users are $(who)


    exit 0

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

    who
    , напротив, нуждается в ней, если он должен стать переменной в сценарии.

    Если вы хотите поместить результат в переменную, то можете просто присвоить его обычным образом:

    whoisthere=$(who)

    echo Swhoisthere

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

    xargs
    сможет это сделать за вас. Дополнительные подробности ищите на страницах интерактивного справочного руководства.

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

    set
    , как было показано ранее.

    Подстановки в арифметических выражениях

    Мы уже использовали команду

    expr
    , которая позволяет выполнять простые арифметические операции, но она делает это очень медленно, потому что для выполнения команды
    expr
    запускается новая командная оболочка.

    Современная и лучшая альтернатива — синтаксическая конструкция

    $((...))
    . Поместив в эту конструкцию выражение, которое вы хотите вычислить, вы можете выполнить простые арифметические операции гораздо эффективнее.

    #!/bin/sh


    х=0

    while [ "$х" -ne 10 ]; do

     echo $х

     х=$(($x+1))

    done


    exit 0

    Примечание

    Обратите внимание на тонкое отличие приведенной подстановки от команды

    х=$(...)
    . Двойные скобки применяются для подстановки значений в арифметические выражения. Вариант с одиночными скобками, показанный ранее, используется для выполнения команд и перехвата их вывода.

    Подстановка значений параметров

    Вы уже видели простейший вариант присваивания параметра и подстановки значения параметра:

    foo=fredecho $foo

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

    #!/bin/sh


    for i in 1 2 do

     my_secret_process $i_tmp

    done

    Но в каждом проходе цикла вы получите следующее сообщение:

    my_secret_process: too few arguments

    В чем ошибка?

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

    $i_tmp
    , которая не существует. Оболочка не считает это ошибкой; она просто не делает никакой подстановки, поэтому в сценарий my_secret_process не передаются никакие параметры. Для обеспечения подстановки в переменную части ее значения
    $i
    необходимо
    i
    заключить в фигурные скобки следующим образом:

    #!/bin/sh


    for i in 1 2 do

     my_secret_process ${i}_tmp

    done

    В каждом проходе цикла вместо

    ${i}
    подставляется значение
    i
    и получаются реальные имена файлов. Вы подставляете значение параметра в строку.

    В командной оболочке можно выполнять разнообразные виды подстановок. Часто они помогают найти красивое решение задач, требующих обработки многих параметров. Самые распространенные виды подстановок значений параметров приведены в табл. 2.18.


    Таблица 2.18

    Шаблон подстановки параметра Описание
    ${парам:-значение по умолчанию}
    Если у
    парам
    нет значения, ему присваивается значение по умолчанию
    ${#парам}
    Задается длина
    парам
    ${парам%строка}
    От конца значения
    парам
    отбрасывается наименьшая порция, совпадающая со
    строкой
    , и возвращается остальная часть значения
    ${парам%%строка}
    От конца значения
    парам
    отбрасывается наибольшая порция, совпадающая со
    строкой
    , и возвращается остальная часть значения
    ${парам#строка}
    От начала значения
    парам
    отбрасывается наименьшая порция, совпадающая со
    строкой
    , и возвращается остальная часть значения
    ${парам##строка}
    От начала значения
    парам
    отбрасывается наибольшая порция, совпадающая со
    строкой
    , и возвращается остальная часть значения

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

    В приведенном далее сценарии показано применение шаблонов при подстановках значений параметров.

    #!/bin/sh


    unset foo

    echo ${foo:-bar}

    foo=fud

    echo ${foo:-bar}


    foo=/usr/bin/X11/startx

    echo ${foo#*/}

    echo ${foo##*/}


    bar=/usr/local/etc/local/networks

    echo ${bar%local*}

    echo ${bar%%local*}


    exit 0

    У этого сценария следующий вывод:

    bar

    fud

    usr/bin/X11/startx

    startx

    /usr/local/etc/usr

    Как это работает

    Первая подстановка

    ${foo:-bar}
    дает значение
    bar
    , поскольку у
    foo
    нет значения в момент выполнения команды. Переменная
    foo
    остается неизменной, т.е. она остается незаданной.

    Примечание

    Подстановка

    ${foo:=bar}
    установила бы значение переменной
    $foo
    . Этот строковый шаблон устанавливает, что переменная
    foo
    существует и не равна
    null
    . Если значение переменной не равно
    null
    , оператор возвращает ее значение, в противном случае вместо этого переменной
    foo
    присваивается значение
    bar
    .

    Подстановка

    ${foo:?bar}
    выведет на экран
    foo: bar
    и аварийно завершит команду, если переменной
    foo
    не существует или ее значение не определено. И наконец,
    ${foo:+bar}
    вернет
    bar
    , если
    foo
    существует и не равна
    null
    . Какое разнообразие вариантов!

    Шаблон

    {foo#*/}
    задает поиск и удаление только левого символа
    /
    (символ
    *
    соответствует любой строке, в том числе и пустой). Шаблон
    {foo##*/}
    задает поиск максимальной подстроки, совпадающей с ним, и, таким образом, удаляет самый правый символ / и все предшествующие ему символы.

    Шаблон

    ${bar%local*}
    определяет просмотр символов в значении параметра, начиная от крайнего правого, до первого появления подстроки
    local
    , за которой следует любое количество символов, а в случае шаблона
    ${bar%%local*}
    ищется максимально возможное количество символов, начиная от крайнего правого символа значения и заканчивая крайним левым появлением подстроки
    local
    .

    Поскольку в системах UNIX и Linux многое основано на идеи фильтров, результат какой-либо операции часто должен перенаправляться вручную. Допустим, вы хотите преобразовать файлы GIF в файлы JPEG с помощью программы cjpeg:

    $ cjpeg image.gif > image.jpg

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

    #!/bin/sh

    for image in *.gif

    do

     cjpeg $image > {image%%gif}jpg

    done

    Этот сценарий, giftojpeg, создает в текущем каталоге для каждого файла формата GIF файл формата JPEG.

    Встроенные документы

    Особый способ передачи из сценария командной оболочки входных данных команде — использование встроенного документа (here document). Такой документ позволяет команде выполняться так, как будто она читает данные из файла или с клавиатуры, в то время как на самом деле она получает их из сценария.

    Встроенный документ начинается со служебных символов

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

    Рассмотрим упражнение 2.19.

    Упражнение 2.19. Применение встроенных документов

    Простейший пример просто передает входные данные команде

    cat
    .

    #!/bin/sh

    cat <<!FUNKY!

    hello

    this is a here

    document

    !FUNKY!

    Этот пример выводит на экран следующие строки:

    hello

    this is a here

    document

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

    echo
    для каждой выводимой строки. Вы можете использовать восклицательные знаки (
    !
    ) с двух сторон от идентификатора документа во избежание путаницы.

    Если вы хотите обработать несколько строк заранее определенным способом, можно применить в сценарии строчный редактор ed и передать ему команды из встроенного документа (упражнение 2.20).

    Упражнение 2.20. Ещё одно применение встроенного документа

    1. Начнем с файла, названного a_text_file и содержащего следующие строки:

    That is line 1

    That is line 2

    That is line 3That is line 4

    2. Вы можете отредактировать этот файл, совместно используя встроенный документ и редактор ed:

    #!/bin/sh


    ed a_text_file <<!FunkyStuff!

    3

    d

    ., \$s/is/was/ w

    q

    !FunkyStuff!


    exit 0

    Если вы выполните этот сценарий, то увидите, что теперь файл содержит следующие строки:

    That is line 1

    That is line 2

    That was line 4

    Как это работает

    Сценарий командной оболочки запускает редактор ed и передает ему команды, необходимые для перехода к третьей строке, удаления строки и затем замены ее содержимым текущей строки (поскольку строка 3 (line 3) была удалена, теперь текущая строка — последняя строка файла). Эти команды редактора ed берутся из строк сценария, формирующих встроенный документ, строк между маркерами

    !Funky Stuff!
    .

    Примечание

    Обратите внимание на знак

    \
    внутри встроенного документа, применяемый для защиты от подстановки, выполняемой командной оболочкой. Символ
    \
    экранирует знак
    $
    , поэтому оболочка знает, что не следует пытаться подставить вместо строки
    \$s/is/was/
    ее значение, которого у нее конечно же нет. Оболочка просто передает текст
    \$
    как $, который затем сможет интерпретировать редактор e

    Отладка сценариев

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

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

    echo
    для отображения содержимого переменных и протестировать фрагменты программного кода, просто вводя их в командной оболочке в интерактивном режиме.

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

    set
    . В табл. 2.19 перечислены эти опции.


    Таблица 2.19

    Опция командной строки Опция команды
    set
    Описание
    sh -n <сценарий>
    set -о noexec  set -n
    Только проверяет синтаксические ошибки; не выполняет команды
    sh -v <сценарий>
    set -о verbose  set -v
    Выводит на экран команды перед их выполнением
    sh -х <сценарий>
    set -о xtrace  set -x
    Выводит на экран команды после обработки командной строки
    sh -u <сценарий>
    set -o nounset  set -u
    Выдает сообщение об ошибке при использовании неопределенной переменной

    Вы можете установить опции с помощью флагов

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

    Для установки опции

    xtrace
    используйте следующую команду:

    set -о xtrace

    Для того чтобы снова отключить эту опцию, применяйте следующую команду:

    set +о xtrace

    Уровень выполняемых подстановок задается (по умолчанию) количеством знаков

    +
    в начале каждой строки. Вы можете заменить знак
    +
    на что-то более осмысленное, определив переменную командной оболочки
    PS4
    в вашем файле конфигурации оболочки.

    В командной оболочке также можно выяснить состояние программы после ее завершения, перехватив сигнал

    EXIT
    с помощью строки, подобной приведенной далее и помещенной в начале вашего сценария:

    trap 'echo Exiting: critical variable = $critical_variable' EXIT

    По направлению к графическому режиму — утилита dialog

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

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

    dialog
    . Она использует средства псевдографики текстового режима и цвет, но при этом выглядит привлекательно.

    Примечание

    В некоторых дистрибутивах команда

    dialog
    по умолчанию не устанавливается; например, в Ubuntu вам, возможно, придется добавить совместно поддерживаемые репозитарии для поиска готовой версии. В других дистрибутивах вы можете найти уже установленный альтернативный вариант,
    gdialog
    . Он очень похож, но рассчитан на пользовательский интерфейс GNOME, применяемый для отображения диалоговых окон команды. В этом случае вы получите настоящий графический интерфейс. Как правило, в любой программе, использующей команду
    dialog
    , можно заменить все вызовы этой команды на
    gdialog
    , и вы получите графическую версию вашей программы. В конце этого раздела мы покажем пример программы, использующей команду
    gdialog
    .

    Общая концепция утилиты

    dialog
    проста — одна программа с множеством параметров и опций, позволяющих отображать различные типы графических окон, начиная с простых окон с кнопками типа Yes/No (Да/Нет) и заканчивая окнами ввода и даже выбором пункта меню. Утилита обычно возвращает результат, когда пользователь выполнил какой-либо ввод, и результат может быть получен или из статуса завершения, или, если вводился текст, извлечением стандартного потока ошибок.

    Прежде чем переходить к подробностям, давайте рассмотрим очень простой пример применения утилиты

    dialog
    . Вы можете вызывать ее непосредственно из командной строки, что прекрасно с точки зрения создания прототипов. Поэтому создадим простое окно сообщений для отображения традиционной первой программы:

    dialog --msgbox "Hello World" 9 18

    На экране появится графическое информационное окно, дополненное кнопкой OK (рис. 2.3).

    Рис. 2.3


    Теперь, когда вы убедились в простоте утилиты

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


    Таблица 2.20

    Тип диалогового окна Опция, применяемая для создания окна этого типа Назначение окна
    Окна с флажками (Check boxes)
    --checklist
    Позволяет отображать список флажков, каждый из которых можно установить или сбросить
    Информационные окна (Info boxes)
    --infobox
    Простое немедленное отображение в окне, без очистки экрана, возвращаемых данных
    Окна ввода (Input boxes)
    --inputbox
    Позволяет пользователю вводить в окно текст
    Окна меню (Menu boxes)
    --menu
    Позволяет пользователю выбрать один пункт из списка
    Окна сообщений (Message boxes)
    --msgbox
    Отображает сообщения для пользователей и снабжено кнопкой OK, которую они должны нажать для продолжения
    Окна с переключателями (Radio selection boxes)
    --radiolist
    Позволяет пользователю выбрать один переключатель из списка
    Текстовые окна (Text boxes)
    --textbox
    Позволяют отображать содержимое файла в окне с прокруткой
    Диалоговые окна Да/Нет (Yes/No boxes)
    --yesno
    Позволяют задать вопрос, на который пользователь может ответить "Да" или "Нет"

    Доступны также и некоторые дополнительные типы диалоговых окон (например, индикатор процесса или окно ввода пароля). Если вы хотите узнать побольше о необычных типах диалоговых окон, как всегда подробную информацию можно найти на страницах интерактивного справочного руководства.

    Для получения вывода из диалогового окна любого типа, допускающего текстовый ввод или выбор, вы должны перехватить стандартный поток ошибок, как правило, направляя его во временный файл, который вы сможете обработать позже. Для получения ответа на вопросы типа "Да"/"Нет", просто проверьте код завершения, который, как и во всех соблюдающих приличия программах, в случае успеха возвращает 0 (т. е. выбор ответа "Да" (Yes)) и 1 в остальных случаях.

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


    Таблица 2.21

    Тип диалогового окна Параметры
    --checklist
    text height width list-height [tag text status] ...
    --infobox
    text height width
    --inputbox
    text height width [initial string]
    --menu
    text height width menu-height [tag item ] ...
    --msgbox
    text height width
    --radiolist
    text height width list-height [tag text status] ...
    --textbox
    filename height width
    --yesno
    text height width

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

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

    Выполните упражнения 2.21 и 2.22.

    Упражнение 2.21. Применение утилиты
    dialog

    Давайте сразу перейдем к красивому сложному примеру. Если вы поймете его, все остальные покажутся легкими! В этом примере вы создадите диалоговое окно со списком флажков, с заголовком Check me (Поставь галочку) и пояснительной надписью Pick Numbers (Выбери номера). Окно с флажками будет высотой 15 строк и шириной 25 символов, и каждый флажок будет занимать 3 символа по высоте. И последнее, но не по степени важности, вы перечислите отображаемые элементы вместе с принятой по умолчанию установкой или сбросом (on/off) флажка.

    dialog --title "Check me" --checklist "Pick Numbers" 15 25 3 1 "one" "off" 2 "two" "on" 3 "three" "off"

    Полученный результат показан на рис. 2.4.

    Как это работает

    В этом примере параметр

    --checklist
    указывает на то, что вы собираетесь создать диалоговое окно с флажками. Вы используете опцию
    --title
    для задания заголовка "
    Check me
    ", следующий параметр — пояснительная надпись "
    Pick Numbers
    ".

    Далее вы переходите к указанию размеров диалогового окна. Оно будет высотой 15 строк и шириной 25 символов и 3 строки отводятся для меню. Это не самый удачный выбор размеров, но он позволит вам увидеть, как размещаются элементы.

    Опции выглядят несколько мудрено, но вам следует запомнить только то, что у каждого элемента списка есть три значения:

    □ номер в списке;

    □ текст;

    □ состояние.

    Рис. 2.4


    У первого элемента номер 1, отображается текст "one" (один) и выбрано состояние "off" (сброшен). Далее вы переходите ко второму элементу с номером 2, текстом "two" и состоянием "on" (установлен). Так продолжается до тех пор, пока вы не опишите все элементы списка.

    Легко, не правда ли? Теперь попробуйте ввести несколько вариантов в командной строке и убедитесь, насколько эту утилиту легко применять. Для того чтобы включить этот пример в программу, вы должны иметь доступ к результатам пользовательского ввода. Это совсем просто: перенаправьте стандартный поток ошибок в текстовый ввод или проверьте переменную окружения

    $?
    , которая, как вы помните, не что иное, как код завершения предыдущей команды.

    Упражнение 2.22. Более сложная программа, использующая утилиту
    dialog

    Давайте рассмотрим простую программу questions, которая принимает к сведению пользовательские ответы.

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

    #!/bin/sh

    # Задайте несколько вопросов и получите ответ

    dialog --title "Questionnaire" --msgbox "Welcome to my simple survey" 9 18

    2. Спросите пользователя с помощью простого диалогового окна с кнопками типа Yes/No, хочет ли он продолжать. Воспользуйтесь переменной окружения

    $?
    для того, чтобы выяснить, выбрал пользователь ответ Yes (код завершения 0) или No. Если он не хочет двигаться дальше, используйте простое информационное окно, не требующее никакого пользовательского ввода для своего завершения.

    dialog --title "Confirm" --yesno "Are you willing to take part?" 9 18

    if [ $? != 0 ]; then

     dialog --infobox "Thank you anyway" 5 20 sleep 2

     dialog --clear exit 0

    fi

    3. Спросите у пользователя его имя с помощью диалогового окна ввода. Перенаправьте стандартный поток ошибок во временный файл _1.txt, который затем вы сможете обработать в переменной

    QNAME
    .

    dialog --title "Questionnaire" --inputbox "Please enter your name" 9 30 2>_1.txt

    Q_NAME=$(cat _1.txt)

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

    dialog --menu "$Q_NAME, what music do you like best?" 15 30 4 1 "Classical" 2 "Jazz" 3 "Country" 4 "Other" 2>_1.txt

    Q_MUSIC=$(cat _1.txt)

    5. Номер, выбранный пользователем, будет запоминаться во временном файле _1.txt, который перехватывается переменной

    Q_MUSIC
    , поэтому вы сможете проверить результат.

    if [ "$Q_MUSIC" = "1" ]; then

     dialog --title "Likes Classical" --msgbox "Good choice!" 12 25

    else

     dialog --title "Doesn't like Classical" --msgbox "Shame" 12 25

    fi

    В заключение очистите последнее диалоговое окно и завершите программу.

    sleep 2

    dialog --clear

    exit 0

    На рис. 2.5 показан результат.

    Как это работает

    В данном примере вы соединяете команду

    dialog
    и простой программный код на языке командной оболочки для того, чтобы показать, как можно создавать простые программы с графическим пользовательским интерфейсом, используя только сценарий командной оболочки. Вы начинаете с обычного экрана-приветствия, а затем с помощью простого диалогового окна с кнопками типа Yes/No спрашиваете пользователя о его желании участвовать в опросе. Вы используете переменную
    $?
    для проверки ответа пользователя. Если он согласен, вы запрашиваете его имя, сохраняете его в переменной
    Q_NAME
    и выясняете с помощью диалогового окна-меню, какой музыкальный стиль он любит. Сохранив числовой вывод в переменной
    Q_MUSIC
    , вы сможете увидеть, что ответил пользователь, и отреагировать соответственно.

    Рис. 2.5


    Рис. 2.6


    Если вы применяете графический пользовательский интерфейс (GUI) на базе графической среды GNOME и в данный момент запустили в нем сеанс работы с терминалом, на месте команды

    dialog
    можно использовать команду
    gdialog
    . У обеих команд одинаковые параметры, поэтому вы сможете воспользоваться тем же программным кодом, не считая замены запускаемой вами команды
    dialog
    командой
    gdialog
    . На рис. 2.6 показано, как выглядит этот сценарий в дистрибутиве Ubuntu, когда применяется команда
    gdialog
    .

    Это очень лёгкий способ формирования из сценария удачного графического пользовательского интерфейса.

    Соединяем все вместе

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

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

    Требования

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

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

    Проектирование

    Поиск и отображение данных — заставляют предполагать, что адекватным решением будет простое меню. Все данные, которые следует хранить, — текстовые, и, полагая, что ваша коллекция компакт-дисков не слишком велика, для управления ею не потребуется сложная база данных, подойдут простые текстовые файлы. Сохранение информации в текстовых файлах позволит сделать приложение простым, и если ваши требования изменятся, всегда легче манипулировать текстовыми файлами, чем какими бы то ни было еще. И, в крайнем случае, вы сможете применить редактор для ввода и удаления данных вручную, а не писать для этого специальную программу.

    Необходимо принять важное проектное решение, касающееся способа хранения данных. Достаточно одного файла? Если да, то какой у него должен быть формат? Большая часть информации, которую вы собираетесь хранить, за исключением данных о дорожках, вводится однократно для каждого компакт-диска (мы пока оставим в стороне вариант наличия на одном CD произведений разных композиторов и исполнителей). И практически на всех компакт-дисках много дорожек.

    Нужно ли ограничить количество дорожек на одном компакт-диске, которые можно хранить? Это ограничение кажется лишним, поэтому сразу отбросим его.

    Если вы допускаете, что на компакт-диске может быть разное количество дорожек, у вас есть три варианта:

    □ использовать один файл с одной строкой для "заголовочной" типовой информации и n строк для сведений о дорожках на каждом компакт-диске;

    □ поместить всю информацию о каждом компакт-диске в одну строку, разрешая ей продолжаться то тех пор, пока вся информация о дорожках диска не будет сохранена;

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

    Только третий вариант позволит нам легко изменять формат файлов, что потребуется, если вы когда-либо захотите превратить вашу базу данных в реляционную (более подробную информацию об этом см. в главе 7), поэтому выберем этот вариант.

    Далее нужно решить, какие данные помещать в файлы.

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

    □ номер компакт-диска в каталоге;

    □ название;

    □ музыкальный стиль (классика, рок, поп, джаз и т.д.);

    □ композитор или исполнитель.

    О дорожках вы будете хранить две характеристики:

    □ номер дорожки;

    □ ее название.

    Для объединения двух файлов вы должны сопоставить данные о дорожках с остальной информацией о компакт-диске. Для этого будет использоваться номер компакт-диска в каталоге. Поскольку он уникален для каждого диска, он будет появляться однократно в файле с заголовками и один раз для каждой дорожки в файле с данными о дорожках.

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


    Таблица 2.22

    Catalog Title Type Composer
    CD123 Cool sax Jazz Bix
    CD234 Classic violin Classical Bach
    CD345 Hits99 Pop Various

    Таблица 2.23

    Catalog Track No. Title
    CD123 1 Some jazz
    CD123 2 More jazz
    CD234 1 Sonata in D minor
    CD345 1 Dizzy

    Два файла объединены общим полем

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

    Последнее, что мы должны решить, — способ разделения элементов данных. Поля фиксированной ширины, обычные в реляционных базах, — не всегда самый удобный вариант. Другой распространенный способ, применяемый в данном примере, — запятая (т. е. файл со значениями, разделенными запятыми, или CSV-файл).

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

    get_return()
    ;

    get_confirm()
    ;

    set_menu_choice()
    ;

    insert_title()
    ;

    insert_track()
    ;

    add_record_tracks()
    ;

    add_records()
    ;

    find_cd()
    ;

    update_cd()
    ;

    count_cds()
    ;

    remove_records()
    ;

    list_tracks()
    .

    Упражнение 2.23. Приложение для работы с коллекцией компакт-дисков

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

    #!/bin/bash


    # Очень простой пример сценария командной оболочки для управления

    # коллекцией компакт-дисков.

    # Copyright (С) 1996-2007 Wiley Publishing Inc.

    # Это свободно распространяемое программное обеспечение;

    # вы можете распространять эту программу и/или изменять ее

    # в соответствии с положениями GNU General Public License,

    # документа, опубликованного фондом Free Software Foundation;

    # либо версии 2 этой лицензии или (по вашему выбору)

    # любой более свежей версии.

    # Эта программа распространяется в надежде на ее полезность,

    # но WITHOUT ANY WARRANTY, (без каких-либо гарантий);

    # даже без предполагаемой гарантии MERCHANTABILITY

    # or FITNESS FOR A PARTICULAR PURPOSE (годности

    # ее для продажи или применения для определенной цели).

    # Более подробную информацию см. в GNU General Public License.

    # Вы должны были получить копию GNU General Public License

    # вместе с этой программой;

    # если нет, пишите в организацию Free Software Foundation,

    # Inc. no адресу: 675 Mass Ave, Cambridge, MA 02139, USA.

    2. Теперь убедитесь, что установлены некоторые глобальные переменные, которые будут использоваться во всем сценарии. Задайте заголовочный файл, файл с данными о дорожках и временный файл и перехватите нажатие комбинации клавиш <Ctrl>+<C> для того, чтобы удалить временный файл, если пользователь прервет выполнение сценария.

    menu_choice=""

    current cd=""

    title_file="title.cdb"

    tracks_file="tracks.cdb"

    temp_file=/tmp/cdb.$$

    trap 'rm -f $temp_file' EXIT

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

    get_return() (

     echo -е "Press return \с"

     read x

     return 0

    }

    get_confirm() (

     echo -e "Are you sure? \c"

     while true do

      read x

      case "$x" in

       y | yes | Y | Yes | YES )

        return 0;;

       n | no | N | No | NO )

        echo

        echo "Cancelled"

        return 1;;

       *)

        echo "Please enter yes or no" ;;

      esac

     done

    }

    4. Теперь вы дошли до основной, формирующей меню функции

    set_menu_choice
    . Содержимое меню изменяется динамически, добавляя дополнительные пункты при выборе компакт-диска.

    set_menu_choice() {

     clear

     echo "Options :-"

     echo

     echo " a) Add new CD"

     echo " f) Find CD"

     echo " c) Count the CDs and tracks in the catalog"

     if [ "$cdcatnum" != "" ]; then

      echo " 1) List tracks on $cdtitle"

      echo " r) Remove $cdtitle"

      echo " u) Update track information for $cdtitle"

     fi

     echo " q) Quit" echo

     echo -e "Please enter choice then press return \c"

     read menu_choice

     return

    }

    Примечание

    Имейте в виду, что команда

    echo -е
    не переносится в некоторые командные оболочки.

    5. Далее идут две очень короткие функции,

    insert_title
    и
    insert_track
    , для пополнения файлов базы данных. Несмотря на то, что некоторые программисты ненавидят однострочные функции вроде этих, они помогают сделать понятнее другие функции.

    За ними следует более длинная функция

    add_record_track
    , использующая эти функции. В данной функции применяется проверка на соответствие шаблону, чтобы исключить ввод запятых (поскольку мы. используем запятые для разделения полей), и арифметические операции для увеличения номера текущей дорожки на 1, когда вводятся данные о новой дорожке.

    insert_title() {

     echo $* >> $title_file

     return

    }

    insert_track() {

     echo $* >> $tracks_file

     return

    }

    add_record_tracks() {

     echo "Enter track information for this CD"

     echo "When no more tracks enter q"

     cdtrack=1

     cdttitle=""

     while [ "$cdttitle" != "q" ]

     do

      echo -e "Track $cdtrack, track title? \c"

      read tmp

      cdttitle=${tmp%%, *}

      if [ "$tmp" != "$cdttitle" ]; then

       echo "Sorry, no commas allowed"

       continue

      fi

      if [ -n "$cdttitle" ] ; then

       if [ "$cdttitle" ! = "q" ]; then

        insert_track $cdcatnum, $cdtrack, $cdttitle

       fi

      else

       cdtrack=$((cdtrack-1))

      fi

      cdtrack=$((cdtrack+1))

     done

    }

    6. Функция

    add_records
    позволяет вводить основную информацию о новом компакт-диске.

    add_records() {

     # Подсказка для начала ввода информации

     echo -е "Enter catalog name \с"

     read tmp

     cdcatnum=${tmp%%, *}

     echo -e "Enter title \c"

     read tmp

     cdtitle=${tmp%%, *}

     echo -e "Enter type \c"

     read tmp

     cdtype=${tmp%%, *}

     echo -e "Enter artist/composer \c"

     read tmp

     cdac=${tmp%%, *}

     # Проверяет, хочет ли пользователь ввести информацию

     echo About to add new entry

     echo "$cdcatnum $cdtitle $cdtype $cdac"

     # Если получено подтверждение, добавляет данные в конец файла.

     # с заголовками

     if get_confirm ; then

      insert_title $cdcatnum, $cdtitle, $cdtype, $cdac

      add_record_tracks

     else

      remove_records

     fi

     return

    }

    7. Функция

    find_cd
    с помощью команды
    grep
    ищет текст с названием компакт-диска в файле с заголовочной информацией. Вам нужно знать, сколько раз была найдена строка, а команда grep только вернет значение, указывающее на то, что строка не была найдена или была найдена многократно. Для решения этой проблемы сохраните вывод в файл, отводящий по одной строке на каждое найденное совпадение, а затем сосчитайте количество строк в файле.

    У команды счетчика слов,

    wc
    , в выводе есть пробельный символ, разделяющий количества строк, слов и символов в файле. Используйте синтаксическую запись
    $(wc -l $temp_file)
    для извлечения первого параметра в выводе и переноса его в переменную
    linesfound
    . Если бы вам был нужен другой следующий далее параметр, нужно было бы воспользоваться командой
    set
    для установки значений переменных-параметров оболочки из вывода команды.

    Изменив значение переменной

    IFS
    (Internal Field Separator, внутренний разделитель полей) на запятую, вы сможете разделить поля, разграниченные запятыми. Альтернативный вариант — применить команду
    cut
    .

    find_сd() {

     if [ "$1" = "n" ]; then

      asklist=n

     else

      asklist=y

     fi

     cdcatnum=""

     echo -e "Enter a string to search for in the CD titles \c"

     read searchstr

     if [ "$searchstr" = "" ]; then

      return 0

     fi

     grep "$searchstr" $title_file > $temp_file

     set $(wc -l $temp_file)

     linesfound=$1

     case "$linesfound" in

      0)

       echo "Sorry, nothing found"

       get_return

       return 0 ;;

      1) ;;

      2)

       echo "Sorry, not unique."

       echo "Found the following"

       cat $temp_file

       get_return

       return 0

     esac

     IFS=", "

     read cdcatnum cdtitle cdtype cdac < $temp_file

     IFS=" "

     if [ -z "$cdcatnum" ]; then

      echo "Sorry, could not extract catalog field from $temp_file"

      get_return

      return 0

     fi

     echo

     echo Catalog number: $cdcatnum echo Title: $cdtitle

     echo Type: $cdtype

     echo Artist/Composer: $cdac

     echo

     get_return

     if [ "$asklist" = "y" ]; then

      echo -e "View tracks for this CD? \c"

      read x

      if [ "$x" = "y" ]; then

       echo

       list_tracks

       echo

      fi

     fi

     return 1

    }

    8. Функция

    update_cd
    позволит вам повторно ввести сведения о компакт-диске. Учтите, что вы ищите (с помощью команды
    grep
    ) строки, начинающиеся (
    ^
    ) с подстроки
    $cdcatnum
    , за которой следует
    ", "
    и должны заключить подстановку значения
    $cdcatnum
    в
    {}
    . Таким образом, вы сможете найти запятую без специального пробельного символа между ней и номером в каталоге. Эта функция также использует
    {}
    для образования блока из нескольких операторов, которые должны выполняться, если функция
    get_confirm
    вернет значение true.

    update_cd() {

     if [ -z "$cdcatnum" ]; then

      echo "You must select a CD first"

      find_cd n

     fi

     if [ -n "$cdcatnum" ]; then

      echo "Current tracks are :-"

      list_tracks

      echo

      echo "This will re-enter the tracks for $cdtitle"

      get_confirm && {

       grep -v "^${cdcatnum}, " $tracks_file > $temp_file

       mv $temp_file $tracks_file

       echo

       add_record_tracks

      }

     fi

     return

    }

    9. Функция

    count_cds
    дает возможность быстро пересчитать содержимое базы данных.

    count_cds() {

     set $(wc -l $title_file)

     num_titles=$1

     set $(wc -l $tracks_file)

     num_tracks=$1

     echo found $num_titles CDs, with a total of $num_tracks tracks

     get_return

     return

    }

    10. Функция

    remove_records
    удаляет элементы из файлов базы данных с помощью команды
    grep -v
    , удаляющей все совпадающие строки. Учтите, что нужно применять временный файл.

    Если вы попытаетесь применить команду:

    grep -v "^$cdcatnum" > $title_file

    файл

    $title_file
    станет пустым благодаря перенаправлению вывода
    >
    до того, как команда
    grep
    выполнится, поэтому она будет читать уже пустой файл.

    remove_records() {

     if [ -z "$cdcatnum" ]; then

      echo You must select a CD first find_cd n

     fi

     if [ -n "$cdcatnum" ]; then

      echo "You are about to delete $cdtitle"

      get_confirm && {

       grep -v "^${cdcatnum}, " $title_file > $temp_file

       mv $temp_file $title_file

       grep -v "^${cdcatnum}, " $tracks_file > $temp_file

       mv $temp_file $tracks_file

       cdcatnum=""

       echo Entry removed

      }

      get_return

     fi

     return

    }

    11. Функция

    list_tracks
    снова использует команду
    grep
    для извлечения нужных вам строк, команду
    cut
    для доступа к отдельным полям и затем команду
    more
    для постраничного вывода. Если вы посмотрите, сколько строк на языке С займет повторная реализация этих 20 необычных строк кода, то поймете, каким мощным средством может быть командная оболочка.

    list_tracks() {

     if [ "$cdcatnum" = "" ]; then

      echo no CD selected yet

      return

     else

      grep "^${cdcatnum}, " $tracks_file > $temp_file

      num_tracks=${wc -l $temp_file}

      if [ "$num_tracks" = "0" ]; then

       echo no tracks found for $cdtitle

      else

       {

        echo

        echo "$cdtitle :-"

        echo

        cut -f 2- -d , $temp_file

        echo

       } | ${PAGER:-more}

      fi

     fi

     get_return

     return

    }

    12. Теперь, когда все функции определены, можно вводить основную процедуру. Первые несколько строк просто приводят файлы в известное состояние; затем вы вызываете функцию формирования меню

    set_menu_choice
    и действуете в соответствии с ее выводом.

    Если выбран вариант

    quit
    (завершение), вы удаляете временный файл, выводите сообщение и завершаете сценарий с успешным кодом завершения.

    rm -f $temp_file

    if [ ! -f $title_file ]; then

     touch $title_file

    fi

    if [ ! -f $tracks_file ]; then

     touch $tracks_file

    fi


    # Теперь непосредственно приложение


    clear

    echo

    echo

    echo "Mini CD manager" sleep 1

    quit=n

    while [ "$quit" != "y" ]; do

     set_menu_choice

     case "$menu_choice" in

      a) add_records;;

      r) remove records;;

      f) find_cd y;;

      u) update_cd;;

      c) count_cds;;

      l) list_tracks;;

      b)

       echo

       more $title_file

       echo

       get return;;

      q | Q ) quit=y;;

      *) echo "Sorry, choice not recognized";;

     esac

    done


    # Убираем и покидаем


    rm -f $temp_file echo "Finished"

    exit 0

    Замечания, касающиеся приложения

    Команда trap в начале сценария предназначена для перехвата нажатия пользователем комбинации клавиш <Ctrt>+<C>. Им может быть сигнал

    EXIT
    или
    INT
    , в зависимости от настроек терминала.

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

    select
    в оболочках bash и ksh (которая, тем не менее, не определена в стандарте X/Open). Она представляет собой специализированный селектор пунктов меню. Проверьте ее на практике, если ваш сценарий может позволить себе быть немного менее переносимым. Для передачи пользователям многострочной информации можно также воспользоваться встроенными документами.

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

    1 First CD Track 1

    2 First CD Track 2

    1 Another CD

    2 With the same CD key

    Мы оставляем это и другие усовершенствования в расчете на ваше воображение и творческие способности, которые проявятся при корректировке вами программного кода в соответствии с требованиями GPL.

    Резюме 

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

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








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх