Интерпретатор командной строки, или shell (shell -- оболочка) -- эта та программа, которая принимает команды от пользователя и исполняет их. К функциям оболочки относятся:
Кроме того, shell -- это специализированный язык программирования, в котором есть переменные, конструкции while, if, for и т.д., функции и много чего еще. Он позволяет писать как несложные сценарии для автоматизации повседневных задач, так и довольно сложные программы (например, запуск и останов большинства Unix'ов производятся сценариями на языке shell). Хотя работа непосредственно в командной строке (а не в оболочке типа NortonCommander или какой-нибудь оконной) на первый взгляд не столь удобна, она обеспечивает более удобный доступ к таким функциям, как перенаправление ввода/вывода и управление заданиями -- оболочки типа Midnight Commander в этом случае будут только мешать. Shell -- это не одна конкретная программа. Исторически существует несколько подвидов оболочек; "генеалогическое древо" представлено на Рис.1.
Не вдаваясь в подробности истории (краткое описание можно найти в разделе 3.10 книги "Unix: универсальная среда программирования" Кернигана и Пайка), стоит лишь заметить, что csh и tcsh не в полной мере реализуют командный язык sh, а zsh, являясь самой последней разработкой, умеет все, что и любой другой подвид, и плюс много чего еще. Кроме того, поскольку командный интерпретатор имеет десятки параметров для настройки, не следует удивляться, увидев, что некая знакомая оболочка на другом компьютере выглядит и ведет себя по другому. Изложение в данном разделе основано на zsh (которая и используется на практических занятиях), но большая часть описанного верно и для других оболочек.
| ||||||||||
Представим себе ситуацию: хочется посмотреть листинг директории /bin (в которой лежат многие программы). Выполняем команду "ls -l /bin" и... Результат в экран не помещается. А ведь бывают директории еще большего объема. Что делать? Большинство команд выводят информацию на терминал, а некоторые (например, текстовый редактор) вводят данные с терминала. В Unix есть почти универсальное правило: терминал может быть заменен для ввода, вывода или и того, и другого, на файл. Так, чтобы сохранить вывод нашей команды в файл, надо написать:
При этом весь вывод команды ls будет вместо терминала отправлен в файл. Символ ">" означает, что выходной поток должен быть помещен в указанный далее файл, а не выведен на терминал. Если в файле что-то было, то старое содержимое будет стерто. Получив таким образом список файлов, его можно просмотреть командой less. Еще один пример: можно слить содержимое нескольких файлов в один файл, "перехватив" выходной поток команды cat и отправив его в файл:
Сделать то же самое при помощи команды cp нельзя:
Символ ">>" действует подобно ">", но указывает на необходимость добавить выходной поток к концу файла, вместо того, чтобы стереть старое содержимое. Например:
Первая команда cat скопирует содержимое file1 в all, а вторая -- допишет к нему содержимое file2 и file3. Использование ">>" очень удобно, если есть некий "долгоживущий" файл (например, протокол каких-то действий или результатов), к которому иногда надо дописывать данные в конец. Просто указать ">>" с какой-нибудь командой обычно намного быстрее, чем загружать файл в текстовый редактор и что-то дописывать. | ||||||
Теперь вернемся к нашему первому примеру: как посмотреть листинг большой директории. Мы отправили вывод команды ls в файл, а потом запустили less для его просмотра. Сам же временный файл не имеет никакого смысла -- потом он больше не нужен. Можно обойтись без временного файла, воспользовавшись конвейером:
Символ "|" означает, что надо взять выходной поток от первой команды, и отправить его на входной поток второй. Большинство команд, выдающих информацию из файла на экран (cat, more, less) устроены так, что они будут читать входной поток, если не указан никакой файл -- вот почему less показывает то, что ему "пришло" от ls.
Можно заставить команду читать вместо терминала не только выходной поток другой команды, но и обычный файл. Это делается при помощи оператора "<", который указывает, что вместо терминала надо брать входной поток из указанного далее файла. Пример (команда "sort" построчно сортирует входные данные):
| ||||||||||
Что будет, если мы попросим ls показать файлы, которых нет?
Файлы которые есть, ls покажет, а про остальные скажет, что их нет. А теперь перенаправим вывод ls в файл:
В чем же дело?! Казалось бы, на экране ничего не должно появиться... А дело в том, что кроме выходного потока есть еще поток сообщений об ошибках, и "культурно" написанные программы все "ругательства" отправляют туда. Чтобы перенаправить в файл поток сообщений об ошибках (обычно его сокращенно называют stderr), надо перед символом ">" указать номер потока -- 2. Пример:
С потоком stderr обычно требуется сделать одну из трех вещей: перенаправить его туда же, куда и выходной поток, направить в отдельный файл, или отправить "в никуда". В первом случае можно воспользоваться специальной формой оператора перенаправления вывода -- "2>&1" ("отправь второй поток (stderr) туда же, куда и первый (stdout)):
Этот способ работает и тогда, когда стандартный вывод отправляется по конвейеру другой программе. Во третьем же случае надо отправить stderr в "черную дыру" -- файл /dev/null:
| |||||||||||||||||||
Подробную информацию на эту тему можно найти в info-документации на zsh, набрав команду "info zsh redirection". Здесь же следует лишь заметить, что zsh обладает способностью перенаправлять вывод в несколько файлов сразу. Это бывает полезно, если хочется запустить какую-нибудь "долгоиграющую" команду так, чтобы ее вывод сохранялся в файле, но при этом отображался бы и на экране -- для этого надо перенаправить вывод одновременно в файл "протокола" и в файл /dev/tty. | ||
Часто возникает задача: надо найти, в каком файле встречается некое слово или фраза. Для этого служит команда "grep". Она ищет "образец" в указанных файлах (или в стандартном вводе, если файлы не указаны) и печатает все строки, на которых он встречается. Пример (найти строку "no"):
Каждая строка предваряется именем файла, в котором она найдена, и двоеточием; если указан только один файл (или стандартный ввод), то этого не делается -- просто печатается найденная строка. С ключом "-i" grep ищет, не различая маленькие/заглавные буквы:
Вообще-то, образец поиска для grep -- это не просто строка, а так называемое "регулярное выражение", некоторые символы в котором приобретают специальное значение:
Поскольку большинство этих символов имеют специальное значение и для оболочки, то образец следует указывать в одинарных кавычках (прямых апострофах). Пример (найти все символьные линки в директории /etc):
Здесь используется то, что для символьных линков ls первым символом строки (тип файла) выводит букву "l". Если результат grep слишком большой и не помещается на экран, то его можно "переправить" команде просмотра, например, less. Пример (показать список всех поддиректорий из директории /usr/lib):
Вообще говоря, в конвейере может участвовать сколько угодно команд. Так, команда
делает почти то же, что и в предыдущем примере, но отбирает только те директории, в имени которых есть "lib".
| |||||||||||||
Часто бывает нужно запустить "долгоиграющую" программу, которая все равно пишет данные только в файл (например, какие-либо вычисления), или графическое приложение, которое не пользуется окном терминала. Но ведь пока программа запущена, терминал "принадлежит" ей, и им больше ни для чего нельзя пользоваться! Unix позволяет запускать задачи в "фоновом режиме": если в конце командной строки указать символ "&", то после запуска команды терминал можно продолжать использовать для ввода других команд. Пример (запустить графический калькулятор):
В квадратных скобках shell печатает номер задания, а за ним -- номер процесса (об этом подробнее в следующем разделе). Таким образом можно запустить в фоновом режиме несколько задач -- например, калькулятор, текстовый редактор и "снежную зиму":
Посмотреть список запущенных задач можно командой "jobs":
(Символы "+" и "-" означают "последняя запущенная задача" и "предпоследняя"). Если у программы не предусмотрено способа завершить исполнение, то ее можно "убить" командой "kill":
Символ процента и следующий за ним номер являются ссылкой на конкретное задание. Если задача случайно запущена без символа "&", то ее можно или завершить комбинацией клавиш Ctrl+C и потом запустить правильно, или "заморозить", нажав Ctrl+Z, а потом перевести в фоновый режим командой "bg" (сокращение от BackGround):
Бывает и обратное: случайно интерактивная программа (например, текстовый редактор) запущена в фоновом режиме. Интерактивные программы при этом автоматически "замораживаются" (потому, что они пытаются читать с терминала, который ей "не принадлежит"). Перевести их в "основной режим" можно командой "fg" (сокращение от ForeGround):
Если попробовать набрать "exit" для выхода из системы (или из окна терминала) при исполняющихся в фоновом режиме задачах, то zsh не позволит выйти:
Чтобы zsh не считал своей обязанностью "убитие" фоновых задач при выходе, можно заставить его забыть про них:
Чтобы запустить фоновую задачу и заставить zsh сразу же забыть про нее, надо набрать всю команду (включая "&") в круглых скобках:
| ||||||||||||||||||||||||
Допустим, запустив задачу в фоновом режиме, пользователь выходит из системы, оставляя задачу работать дальше. Хорошо, если это была графическая программа -- у нее почти наверняка будет хотя бы команда "Выход". А если нет, если это какая-либо счетная задача? В этом случае придется воспользоваться средствами более низкого (системного) уровня -- управлением процессами. Здесь следует сразу понять разницу. Задача (job) -- это одна или несколько программ (например, конвейер), запущенных из оболочки, которыми можно управлять при помощи команд оболочки jobs, bg, fg и kill. Процесс (process) -- это любая программа, запущенная любым способом (из оболочки; другой программой -- например, из графического меню; самой операционной системой при запуске и т.д.). | ||
Для просмотра списка процессов служит команда "ps". У нее есть довольно много ключей, которые к тому же различаются в BSD и SystemV. Поэтому здесь приведем лишь несколько наиболее часто встречающихся вариантов ее использования. В основном изложение относится к BSD-версии команды ps (в том числе и Linux), эквиваленты для SysV-версии (Solaris, IRIX) приведены отдельно. Команда ps без параметров выводит список почти всех процессов, принадлежащих запустившему ее пользователю.
Из всей информации, что выдает ps, для нас пока интересны поля PID и COMMAND. PID (Process IDentifier) -- это число, уникальное для каждого процесса, которое используется, например, для того, чтобы приостановить или завершить его исполнение. COMMAND -- название программы и ее параметры. "Почти" -- потому, что в список не включаются процессы "без терминала", т.е. те, которые запущены в фоновом режиме, а окно, из которого это было сделано, потом было закрыто. Чтобы эти процессы также отображались, надо указать ключ "-x"; в поле TTY у них стоит "?". Ключ "-a" показывает процессы всех пользователей, а не только запустившего ps. Ключ "-u" выдает более полную информацию о каждом процессе -- потребление процессорного времени, памяти, время запуска. Ключ "-f" выдает список процессов "со связями", позволяя понять, какой из процессов является чьим "родителем" и чьим "потомком". Если у пользователя запущено много процессов (например, когда он находится в графической оболочке), то список обычно не помещается на экран. В этом случае надо отправить вывод команды ps команде less. Другой часто используемый прием -- если интересует информация о какой-то конкретной команде, то вывод ps фильтруется через grep. Пример:
Приблизительные эквиваленты для SystemV (Solaris, IRIX):
| ||||||||||
Команде kill можно указывать не только номер задания, но и PID. Так, команда "kill 1206" пошлет команду завершения процессу с номером 1206. Стоит напомнить, что при запуске задач оболочка кроме номера задания печатает также PID соответствующего процесса (или процессов, в случае конвейера):
Вообще говоря, "kill" не завершает процесс, а посылает ему сигнал. Процесс же, получив сигнал, может завершиться, а может и предпринять какое-либо другое действие, например, проигнорировать сигнал. Есть несколько десятков сигналов, каждый из которых имеет свой смысл. Каждый сигнал имеет как имя, так и номер, но, поскольку номера разные в разных Unix'ах, то лучше использовать имена. Чтобы указать kill, какой сигнал послать, надо указать его имя (или номер), предваренное дефисом:
Вот список самых часто употребляемых сигналов:
Сигналы TERM, INT, HUP и KILL обычно приводят к завершению работы программы. Разница между ними в том, в каких случаях они посылаются и в том, что одни сигналы программа перехватывает, а другие -- нет. Сигнал KILL программа перехватить не может, поэтому если она не реагирует больше ни на что, то остается использовать его. Номер KILL во всех Unix'ах -- 9, поэтому его часто указывают по номеру, а не по имени -- "kill -9". Сигнал KILL следует использовать только в крайних случаях, когда ничто другое не помогает. Дело в том, что перехватив, к примеру, сигнал TERM, программа может корректно завершить работу, при надобности сохранив данные в файлы и восстановив правильное состояние терминала, а KILL не дает ей такой возможности. Вообще говоря, большая часть функций по управлению заданиями реализуется при помощи сигналов. Так, послав программе сигнал STOP, получим тот же эффект, что и при нажатии Ctrl+Z. В большинстве современных Unix'ов есть возможность послать сигнал процессу, указав его не по номеру, а по имени программы. Для этого служит команда "killall" -- она посылает сигнал всем процессам с указанным именем. Пример:
Не стоит ей злоупотреблять -- эта команда мало того, что "убивает" все процессы с таким именем, она пытается "убить" даже процессы с таким именем у других пользователей (чего ей Unix, естественно, не позволяет сделать). | |||||||||||||||||||
В большинстве современных клонов Unix есть программа, позволяющая оперативно отслеживать, какие процессы запущены в системе и какие из них потребляют больше всего процессорного времени. Эта программа называется "top".
top показывает процессы по убыванию "потребления" процессора. ("top" -- верхушка, вверху показываются те процессы, которые потребляют больше). К сожалению, как видно из приведенного примера, сам top также потребляет немало -- на старых компьютерах типа 486 он иногда пожирал больше 10%. | |||
Поскольку команды jobs, bg и fg -- это внутренние команды оболочки, то их описание следует искать в описании оболочки. Информация про перенаправление ввода/вывода имеется там же. Для оболочек bash и tcsh лучше всего смотреть man-страницы. Для zsh -- info-документацию: и соответственно. | ||
| ||