Это старая версия (1.56) ОболочкаShell.

Содержание

Оболочка shell

Кратко о программируемой оболочке

Программируемая оболочка интерпретирует команду и выполняет её. Оболочка shell предназначена для манипуляции с:

  1. данными — строчно-ориентированная обработка;
  2. файлами — посредством многочисленных утилиты Unix;
  3. программами — как инструментом «склеивания» разных программ друг с другом (конвеер, сопроцессы, асинхронный запуск и пр.)

В Unix оболочка shell — обычная программа. Она не является частью ядра. Перечислим значимые особенности и возможности оболочки:

  • Оболочка раскрывает шаблоны имён файлов. Shell находит совпадающие с шаблоном имена файлов, формирует из них список и выполняет подстановку в аргументы команды. Команды Unix, используемые в оболочке, не обязаны уметь раскрывать шаблоны, так как этим занимается сама оболочка.
  • Перенаправление ввода-вывода. Любая программа, запускаемая в оболочке, может вводить данные из файлов и выводить их в файл, а не через терминал. Программы могут быть соединены посредством программных каналов (pipes) в конвеер;
  • Оболочка предоставляет настраиваемые переменные окружения (environment variables), псевдонимы команд (alias) и возможность организовывать часто выполняемые последовательности команд в функции.

Архитектура расширений языка в оболочке shell беспрецедентно проста и гибка по причине отсутствия специальной архитектуры или API. Утилиты связываются друг с другом текстовым, как правило, строково-ориентированным интерфейсом. Производительность программ, написанных на оболочке, существенно ниже, чем у специализированных программ на Си. Это обсуловлено накладными расходами на создание новых процессов. Не смотря на то, что в Unix запуск новых процессов происходит довольно быстро, ядро ОС несёт затраты на организацию системных таблиц, выделение ресурсов и т. д. Программы на оболочке эффективны тогда, когда расходы на сами операции (ввод-вывод, вычисления) существенно превышают расходы на создание новых процессов.

Интерактивный и неинтерактивный режимы

В интерактивном режиме оболочка сигнализирует о готовности принимать команды приглашением — символом доллара ($). Если оболочка ожидает продолжения ввода, то об этом она сигнализирует приглашением продолжения ввода — символом «больше-чем» (>).

 $ echo ’hello
 > world’
 hello
 world
 $

В приведённом выше примере оболочка ожидает продолжения ввода после `’hello`, т. к. символ апострофа (’) обозначает начало экранируемой строки, и строка считается незавершённой, пока оболочка не встретит парный апостроф. Оболочка знает, что ввод должен продолжиться, поэтому выводит вторичное приглашение (>) и ждёт дальнейшего ввода. Символ перевода строки внутри экранированной строки становится частью аргумента для echo. В неинтерактивном режиме оболочка не показывает приглашений, только вывод самих команд. Запускать команды можно двумя способами. Первый способ — это сохранить все команды в файл, а потом запустить их:

 $ cat hello
 echo ’hello
 world’
 $ sh hello
 hello
 world
 $

Второй способ — добавить путь к интерпретатору в сам файл. Для этого нужно первой строкой в файле поместить `#!/bin/sh` и добавить разрешение на исполнение файла:

 $ chmod +x hello

Теперь файл можно запускать как программу из текущего каталога:

 $ ./hello
 hello
 world
 $

Простые команды

Оболочка выполняет преобразования с командами, разбивая каждую входную строку на последовательность символов, называемых словами (words). Процесс разбиения входного текста на слова называется разбиением слов (word splitting). Количество пробелов или символов табуляции между словами не имеет значения. Обычно первое слово является командой (встроенной или внешней программой), последующие слова передаются как аргументы команде. Для трассировки изменений, вносимых оболочкой в команды, включают режим трассировки командой `set -x`, а выключают трассировку командой `set +x`. Несколько команд можно ввести в одну строку через разделитель (;).

 $ echo hello; echo world
 hello
 world
 $

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

Переменные

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

 $ fruit=apple
 $ echo $fruit
 apple
 $

Имя переменной в shell должно начинаться с буквы (строчной или прописной) или символа подчёркивания (_). После первого символа в имени переменой могут следовать любые комбинации букв, цифр и символов подчёркивания. Если переменная не была определена, то при её раскрытии вставляется пустая строка. Такое поведение оболочки может быть источником некоторых неприятностей, поэтому следует быть предусмотрительным. В оболочке все переменные — строки. Целые, единичные символы, строки не различаются. В этом смысле оболочка shell считается нетипизированным языком. Значение переменной может быть введено пользователем.

 $ echo Enter your favourite fruit: ; read fruit
 Enter your favourite fruit:
 orange
 $ echo $fruit
 orange
 $

Экранирование через "..." и '...'

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

 $ city=Saint Petersburg
 Petersburg: command not found
 $ city=’Saint Petersburg’
 $ echo $city
 Saint Petersburg
 $

В Unix имя файла может содержать любые символы, кроме слэша (/) и нулевого байта (NUL в ASCII). Если пользователь создал файл с именем my file, то для его удаления тоже нужно предотвратить разбиение на слова:

 $ rm my file
 rm: cannot remove ‘my’: No such file or directory
 rm: cannot remove ‘file’: No such file or directory
 $ rm ’my file’
 $

Переменная может содержать команду:

 $ command=’echo Nice day!’
 $ $command
 Nice day!
 $

После раскрытия переменной оболочка разбивает её на слова, но повторно уже не раскрывает слова как переменные. Иногда нужно предотвратить разбиение на слова, но при этом выполнить раскрытия переменных. Для этого переменную заключают в двойные кавычки ("):

 $ fruit=’red
 apple’
 $ echo $fruit
 red apple
 $ echo ’$fruit’
 $fruit
 $ echo "$fruit"
 red
 apple
 $

Весьма распространено заблуждение, что кавычки отделяют слова в оболочке. Это не так, потому что кавычки «склеивают» в одно слово строки, заключённые в них, если между ними нет пробелов.

 $ echo ’It’"’"’s not my fault!’
 It’s not my fault!
 $

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

Большинство команд печатают вывод на терминал и читают ввод с его клавиатуры. В качестве устройства ввода и вывода в Unix может выступать любой файл. Оболочка может перенаправить вывод оператором перенаправления (>) в любой файл по желанию пользователя.

 $ echo "hello, world" >hello
 $ cat hello
 hello, world
 $

При перенаправлении shell удаляет старое содержимое из файла. Если нужно добавить вывод в конец к существующему содержимому, тогда используют оператор «>>», как в следующем примере:

 $ echo "goodby, world" >> hello
 $ cat hello
 hello, world
 goodby, world
 $

Программные каналы

Одно из главных достижений Unix — программные каналы. Они позволяют подать вывод одной программы на вход другой без временных файлов. Несколько программ, соединённых таким способом, называют конвеером (pipeline).

 $ who | sort -k 1d | cut -d’ ’ -f1 | uniq | wc -l

Ввод в программу тоже может быть получен из файла, а не из терминала:

 $ ls -1 > files
 $ wc -l < files
 42
 $

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

 $ ls -1 | wc -l
 42
 $

Шаблоны имён файлов

Символы «?», «*» и «[» имеют специальное назначение. Когда оболочка встречает такие символы в аргументе команды (вне кавычек), то аргумент интерпретируются как шаблон имён файлов. Оболочка пытается найти подходящие под шаблон имена файлов (выполняет раскрытие шаблона), а затем подставляет список имён (через пробел) вместо него. Для отмены действия символа перед ним ставят обратный слэш (\).

 Вывести имена файлов из /etc, заканчивающиеся на «.conf».
 $ ls /etc/*.conf
 Вывести имена файлов, заканчивающиеся на «.c» и «.h».
 $ ls *.[ch]
 Вывести имена файлов с любым символом вместо ?.
 $ ls j?nk
 Вывести на терминал файл с именем j?nk.
 $ cat j\?nk
 Вывести имена файлов из /var/run, начинающиеся на любой символ, исключая символы abc, и заканчивающиеся на «.pid».
 $ ls /var/run/[!abc]*.pid

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

Замечания по раскрытию шаблонов

Символ слэш (/) не может быть частью шаблона при раскрытии путей, потому что является разделителем пути (path separator). Если слэш присутствует в шаблоне, то он разделяет шаблон на части, где каждая часть задаёт шаблон для совпадения в дереве каталогов. Поиск файлов производится оболочкой только на явно указанной глубине дерева файловой системы. Если ни одного слэша в шаблоне нет, то оболочка ищет имена файлов только в текущем каталоге.

Метасимвол «*» при раскрытии путей никогда не даёт совпадений с именами файлов, начинающихся с точки. Это связано с тем, что в Unix традиционно файлы, имя которых начиналось с точки, считались скрытыми. По этой причине они и не попадают в шаблон «*». Если такие файлы должны давать совпадение с шаблоном, тогда точку нужно указывать явно. В шаблон «.*» при раскрытии путей всегда попадут текущий и родительский каталоги.


На этой странице будут собраны и разбиты на разделы практические приёмы использования оболочки (shell). Так как сама по себе оболочка является всего лишь тонким «клеем» для объединения программ и файлов в новые инструменты (через конвеер и перенаправление ввода-вывода), в этот раздел будут помещены ссылки на другие вики-страницы, где описаны приёмы работы с прочими классическими утилитами Unix.

Конвеер для работы с файлами

 $ find /etc -maxdepth 1 -type f | sort | ( echo cat ; cat ) | paste -s -d ' ' | sh >/tmp/all

Я бы отдал предпочтение tr вместо paste, вот так:

 $ find /etc -maxdepth 1 -type f | sort | { echo cat; cat; } | tr '\n' ' ' | sh >/tmp/all

Здесь запуск подоболочки (subshell) заменён на блок (в фигурных скобках), так как преследовалась цель объединить последовательно вывод утилит echo и cat на стандартный вывод. Для этой цели подоболочку запускать расточительно.

Здесь есть ещё одна потенциальная проблема, она связана с тем, что в именаx файлов могут встречаться любые символы, кроме нулевого байта (NUL) и слеша (/). В имени файла может присутствовать символ новой строки (NL), в то же время find печатает список файлов, отделяя этим символом имена друг от друга. Если в имени файла содержится символ новой строки, тогда вывод find не отличим от двух файлов, следующих подряд.

Если используются утилиты GNU, то следует воспользоваться расширением, которое заменяет символ разделителя в списке файлов, выводимом find:

 $ find /etc -maxdepth 1 -type f -print0 | sort -z | xargs -0 cat >/tmp/all

Здесь find выводит имена файлов разделяя их не символом новой строки, а нулевым байтом (NUL). В этом случае все утилиты конвеера должны понимать это соглашение, например, GNU sort и GNU xargs умеют обрабатывать списки файлов с таким разделителем. -- СиткаревГригорий


Фильтр sed

 $ echo "abcabc" | sed 's/\(.*\)\(\1\)/\1#\2/'
 abc#abc

В данном примере в регулярном выражении используется backreference. Регулярное выражение состоит из двух групп выделенных круглыми скобками. Первой группе соотвествует любой набор символов .*, во второй группе используется backreference \1 (отсыл на первое выражение), при чем эта группа выделит выражение идентичное первому. В выводе мы адресуем группы как \1 и \2 и разделяем их символом #.

sed как язык программирования

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

Далее текст можно рассматривать как небольшое введение по программированию на sed.

sed имеет два буфера, первый -- pattern буфер -- это входной буфер, он заполняется автоматически при обработке входных данных (или принудительно смотри команды n, N), второй -- hold буфер -- буфер хранения, данные в этот буфер могут попасть только по вызову специальной команды (смотри команды h, H). Пользователь может копировать, добавлять данные из одного буфера в другой, обменивать их содержимым друг с другом.

Команды могут объединяться в группы фигурными скобками:

 { action1; action2... }
а так же выполняться с условием, подобно ЯзыкAWK:
 condition { action }

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

Напечатать на экран строки с 5-ой по 8-ую:

 $ sed -n '5,8p' < /etc/passwd

p -- печатает содержимое pattern буфера.

Напечатать на экран лог ошибки сервера Apache за определенный период:

 $ sed -n '/^\[.*Feb 08/,/^\[.*Feb 10/p' < /var/log/apache2/error.log

/regexp1/,/regexp2/ -- условие задается двумя регулярными выражениями -- начальной и конечной датой.

Удалить все закоментированные и пустые строки:

 $ sed -e '/^ *#/d' -e '/^$/d' </etc/ssh/sshd_config

d -- удаляет pattern буфер, если условие верно, /^ *#/ строка с коментариями, /^$/ пустая строка, переходит к следующему циклу чтения, все последующие команды игнорируются.

Следующий пример будет сложнее:

 $ sed -n 'H;${x;s/\n/ /g;p}' < /etc/passwd

H -- добавляет все входные строки из pattern буфер к данным в hold буфер, таким образом, мы накапливаем все входные строки в буфере хранения;

${ ... } -- $ означает последнюю строку, т.е все действия в фигурных скобках выполняться только при обработке последней строки;

x -- обменивает содержимое hold буфера с содержимым pattern буфера, т.е все накопленные данные перемещаются из hold в ptatern буфер, а из pattern в hold буфер;

s/\n/ /g -- команда замены в pattern буфере, где теперь находятся все наши строки, меняем символ переноса -- пробелом (g делаем это глобально, для всех \n);

p -- печатает pattern буфер. Все строки теперь объеденены в одну.

Слегка расширим предыдущий пример.

 $ sed -n '1h;1!H;${x;s/\n/ /g;p}' < /etc/passwd

1h -- первую строку копируем в hold буфер;

1!H -- все входные строки кроме первой добавляем в hold буфер, т.е дописываем к уже имеющимся в нем данным, ! знак инвертирования условия, в нашем случаи оно выполняется для всех строк кроме первой.

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

Стоит отметить, что накопление значительных объемов данных может привести к переполнению внутренних буферов sed. Этого не должно произойти при использовании GNU sed.

Литература

  1. Peek, J. Unix Power Tools / Jerry Peek, Shelley Powers, Tim O'Reilly, Mike Loukides. — 3rd ed. — O'Reilly Media, October 2002.
  2. The Open Group Base Specifications Issue 7 [Электронный ресурс] : Shell Command Language / The IEEE and The Open Group. — IEEE Std 1003.1, 2013 Edition. — 2013. — Режим доступа: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html, свободный.


КатегорияОболочки | КатегорияЯзыкиПрограммирования