Интерпретатор командной строки, или shell (shell -- оболочка) -- эта та программа, которая принимает команды от пользователя и исполняет их.
К функциям оболочки относятся:
Кроме того, shell -- это специализированный язык программирования, в котором есть переменные, конструкции while, if, for и т.д., функции и много чего еще. Он позволяет писать как несложные сценарии для автоматизации повседневных задач, так и довольно сложные программы (например, запуск и останов большинства Unix'ов производятся сценариями на языке shell).
Хотя работа непосредственно в командной строке (а не в оболочке типа NortonCommander или какой-нибудь оконной) на первый взгляд не столь удобна, она обеспечивает более удобный доступ к таким функциям, как перенаправление ввода/вывода и управление заданиями -- оболочки типа Midnight Commander в этом случае будут только мешать.
Shell -- это не одна конкретная программа. Исторически существует несколько подвидов оболочек; "генеалогическое древо" представлено на Рис.1.
Рис.1: семейство интерпретаторов командной строки |
Не вдаваясь в подробности истории (краткое описание можно найти в разделе 3.10 книги "Unix: универсальная среда программирования" Кернигана и Пайка), стоит лишь заметить, что csh и tcsh не в полной мере реализуют командный язык sh, а zsh, являясь самой последней разработкой, умеет все, что и любой другой подвид, и плюс много чего еще.
Кроме того, поскольку командный интерпретатор имеет десятки параметров для настройки, не следует удивляться, увидев, что некая знакомая оболочка на другом компьютере выглядит и ведет себя по другому.
Изложение в данном разделе основано на zsh (которая и используется на практических занятиях), но большая часть описанного верно и для других оболочек.
Совет. Совет: чтобы узнать, какой используется shell, надо выполнить командуecho $SHELL
Представим себе ситуацию: хочется посмотреть листинг директории /bin (в которой лежат многие программы). Выполняем команду "ls -l /bin" и... Результат в экран не помещается. А ведь бывают директории еще большего объема. Что делать?
Большинство команд выводят информацию на терминал, а некоторые (например, текстовый редактор) вводят данные с терминала. В Unix есть почти универсальное правило: терминал может быть заменен для ввода, вывода или и того, и другого, на файл.
Так, чтобы сохранить вывод нашей команды в файл, надо написать:
bobby:~% ls -l /bin >filelist bobby:~% |
При этом весь вывод команды ls будет вместо терминала отправлен в файл. Символ ">" означает, что выходной поток должен быть помещен в указанный далее файл, а не выведен на терминал. Если в файле что-то было, то старое содержимое будет стерто.
Получив таким образом список файлов, его можно просмотреть командой less.
Еще один пример: можно слить содержимое нескольких файлов в один файл, "перехватив" выходной поток команды cat и отправив его в файл:
bobby:~% cat file1 file2 file3 >all bobby:~% _ |
Сделать то же самое при помощи команды cp нельзя:
bobby:~% cp file1 file2 file3 all cp: copying multiple files, but last argument (all) is not a directory Try `cp --help' for more information. bobby:~% _ |
Символ ">>" действует подобно ">", но указывает на необходимость добавить выходной поток к концу файла, вместо того, чтобы стереть старое содержимое. Например:
bobby:~% cat file1 >all bobby:~% cat file2 file3 >>all bobby:~% _ |
Первая команда cat скопирует содержимое file1 в all, а вторая -- допишет к нему содержимое file2 и file3.
Использование ">>" очень удобно, если есть некий "долгоживущий" файл (например, протокол каких-то действий или результатов), к которому иногда надо дописывать данные в конец. Просто указать ">>" с какой-нибудь командой обычно намного быстрее, чем загружать файл в текстовый редактор и что-то дописывать.
Теперь вернемся к нашему первому примеру: как посмотреть листинг большой директории. Мы отправили вывод команды ls в файл, а потом запустили less для его просмотра. Сам же временный файл не имеет никакого смысла -- потом он больше не нужен.
Можно обойтись без временного файла, воспользовавшись конвейером:
bobby:~% ls -l /bin | less |
Символ "|" означает, что надо взять выходной поток от первой команды, и отправить его на входной поток второй.
Большинство команд, выдающих информацию из файла на экран (cat, more, less) устроены так, что они будут читать входной поток, если не указан никакой файл -- вот почему less показывает то, что ему "пришло" от ls.
Замечание. В отличие от Dos, где тоже поддерживаются операторы перенаправления ввода/вывода, в Unix команды в конвейере запускаются одновременно, и данные передаются через программный канал (это специальное средство, предоставляемое ядром Unix) сразу от одной программы к другой, а не через скрытый временный файл.
Можно заставить команду читать вместо терминала не только выходной поток другой команды, но и обычный файл. Это делается при помощи оператора "<", который указывает, что вместо терминала надо брать входной поток из указанного далее файла. Пример (команда "sort" построчно сортирует входные данные):
bobby:~% cat proverbs What is worth doing, is worth doing well. Art is long, life is short. History repeats itself. Dead men tell no tales. _______________________ bobby:~% sort <proverbs Art is long, life is short. Dead men tell no tales. History repeats itself. What is worth doing, is worth doing well. _______________________ bobby:~% _ |
Что будет, если мы попросим ls показать файлы, которых нет?
bobby:~% ls -l file1 file2 file10 file11 /bin/ls: file10: No such file or directory /bin/ls: file11: No such file or directory -rw-r--r-- 1 ivanov lab5 76 Feb 21 13:23 file1 -rw-r--r-- 1 ivanov lab5 32 Feb 21 13:23 file2 bobby:~% _ |
Файлы которые есть, ls покажет, а про остальные скажет, что их нет. А теперь перенаправим вывод ls в файл:
bobby:~% ls -l file1 file2 file10 file11 >filelist /bin/ls: file10: No such file or directory /bin/ls: file11: No such file or directory bobby:~% _ |
В чем же дело?! Казалось бы, на экране ничего не должно появиться...
А дело в том, что кроме выходного потока есть еще поток сообщений об ошибках, и "культурно" написанные программы все "ругательства" отправляют туда. Чтобы перенаправить в файл поток сообщений об ошибках (обычно его сокращенно называют stderr), надо перед символом ">" указать номер потока -- 2. Пример:
bobby:~% ls -l file1 file2 file10 file11 >filelist 2>errs bobby:~% cat filelist -rw-r--r-- 1 ivanov lab5 76 Feb 21 13:23 file1 -rw-r--r-- 1 ivanov lab5 32 Feb 21 13:23 file2 bobby:~% cat errs /bin/ls: file10: No such file or directory /bin/ls: file11: No such file or directory bobby:~% _ |
Замечание. Такое перенаправление потока stderr невозможно в оболочках csh и tcsh -- в них для этого используется другой, более запутанный синтаксис.
Замечание. Те, кто программировал на языке C, знают, что там есть три стандартных файловых потока для ввода/вывода: stdin (входной поток, дескриптор 0), stdout (выходной поток, дескриптор 1) и stderr (поток сообщений об ошибках, дескриптор 2). Операторы ">", "<" и "2>" перенаправляют именно эти потоки.
С потоком stderr обычно требуется сделать одну из трех вещей: перенаправить его туда же, куда и выходной поток, направить в отдельный файл, или отправить "в никуда". В первом случае можно воспользоваться специальной формой оператора перенаправления вывода -- "2>&1" ("отправь второй поток (stderr) туда же, куда и первый (stdout)):
bobby:~% ls -l file1 file2 file10 file11 >filelist 2>&1 bobby:~% cat filelist /bin/ls: file10: No such file or directory /bin/ls: file11: No such file or directory -rw-r--r-- 1 ivanov lab5 76 Feb 21 13:23 file1 -rw-r--r-- 1 ivanov lab5 32 Feb 21 13:23 file2 bobby:~% _ |
Этот способ работает и тогда, когда стандартный вывод отправляется по конвейеру другой программе.
Во третьем же случае надо отправить stderr в "черную дыру" -- файл /dev/null:
bobby:~% ls -l file1 file2 file10 file11 >filelist 2>/dev/null bobby:~% cat filelist -rw-r--r-- 1 ivanov lab5 76 Feb 21 13:23 file1 -rw-r--r-- 1 ivanov lab5 32 Feb 21 13:23 file2 bobby:~% _ |
Подробную информацию на эту тему можно найти в 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-документацию:
Grep
Команда-фильтр grep: поиск строки по файлам
bobby:~% grep no *
proverbs:Dead men tell no tales.
bobby:~% _
bobby:~% grep no *
errs:/bin/ls: file10: No such file or directory
errs:/bin/ls: file11: No such file or directory
proverbs:Dead men tell no tales.
bobby:~% _
bobby:~% ls -l /etc | grep '^l'
lrwxrwxrwx 1 root root 21 Dec 9 20:55 initrunlvl -> ../
var/run/initrunlvl
lrwxrwxrwx 1 root root 38 Dec 9 21:04 localtime -> ../u
sr/share/zoneinfo/Asia/Novosibirsk
lrwxrwxrwx 1 root root 11 Dec 9 20:44 rmt -> ../sbin/rm
t*
bobby:~% _
bobby:~% ls -l /usr/lib | grep '^d' | less
bobby:~% ls -l /usr/doc | grep '^d' | grep 'lib' | less
Совет.
Команда grep -- одна из самых полезных и
частоиспользуемых в Unix. Она столь же важна для нахождения файлов с
нужным содержимым, как ls -- для нахождения файлов с нужным
именем. Поэтому стоит хорошо освоить grep -- умелое владение
ей позволяет сэкономить массу времени и сил. Кроме того, регулярные
выражения широко используются во многих других программах.
Фоновое исполнение задач
Фоновое исполнение задач
bobby:~% xcalc &
[1] 2616
bobby:~% _
bobby:~% xedit &
[2] 2628
bobby:~% xsnow &
[3] 2629
bobby:~% _
bobby:~% jobs
[1] running xcalc
[2] - running xedit
[3] + running xsnow
bobby:~% _
bobby:~% kill %3
[3] + done xsnow
bobby:~% _
bobby:~% xcalc
zsh: suspended xcalc
bobby:~% bg %1
[1] + continued xcalc
bobby:~% _
bobby:~% emacs -nw &
[1] 2637
bobby:~%
[1] + suspended (tty output) emacs -nw
bobby:~% fg %1
[1] + continued emacs -nw
Замечание.
Если командам bg и fg не указывать задачу, то они
работают с последней запущенной -- той, что помечена символом "+".
bobby:~% xcalc &
[1] 2691
bobby:~% exit
zsh: you have running jobs.
bobby:~% _
bobby:~% xcalc &
[1] 2700
bobby:~% exit
zsh: you have running jobs.
bobby:~% exit
zsh: warning: 1 jobs SIGHUPed
bobby:~% xcalc &
[1] 2701
bobby:~% jobs
[1] + running xcalc
bobby:~% disown %1
bobby:~% jobs
bobby:~% _
Замечание.
Другие оболочки (bash и tcsh) менее "заботливы", и завершают работу по
первой же команде exit, оставляя фоновые задачи
"беспризорными".
bobby:~% (xcalc &)
bobby:~% jobs
bobby:~% _
Процессы
Процессы
Как посмотреть список процессов: ps
bobby:~% ps
PID TTY STAT TIME COMMAND
2950 1 S 0:00 -zsh
2971 1 R 0:00 ps
bobby:~% _
bobby:~% ps -x | grep xcalc
3103 ? S N 0:00 xcalc
3107 p9 S 0:00 grep xcalc
bobby:~% _
Замечание.
Команда ps в последних версиях Linux при
указании любых ключей выдает предупреждение типа
На это надо просто не обращать внимания -- просто команда ps
сейчас находится в процессе переделки со стандарта BSD на стандарт
Unix98, и это предупреждение -- следствие попытки обеспечить
совместимость.
Завершение и изменение статуса процессов: kill
bobby:~% xcalc &
[1] 3206
bobby:~% kill 3206
bobby:~%
[1] + terminated xcalc
bobby:~% _
bobby:~% xcalc &
[1] 3388
bobby:~% kill -INT 3388
bobby:~%
[1] + interrupt xcalc
bobby:~% _
Сигнал Назначение TERM TERMinate -- завершить работу.
Используется командой kill по умолчаниюINT INTerrupt.
Посылается при нажатии пользователемCtrl+CSTOP "замерзнуть" CONT CONTinue -- "размерзнуть" HUP HangUP -- "повесить трубку".
Посылается при выходе пользователя из системы программам, запущенным в этой же сессииKILL "умереть"
bobby:~% xcalc&
[1] 3478
bobby:~% xcalc&
[2] 3479
bobby:~% killall xcalc
[1] - terminated xcalc
[2] + terminated xcalc
bobby:~% _
Интерактивный список самых активных процессов: top
11:33pm up 1 day, 6:20, 12 users, load average: 0.00, 0.00, 0.09
81 processes: 78 sleeping, 3 running, 0 zombie, 0 stopped
CPU states: 12.8% user, 2.5% system, 0.3% nice, 85.4% idle
Mem: 63136K av, 59612K used, 3524K free, 39772K shrd, 1000K buff
Swap: 128516K av, 12552K used, 115964K free 14852K cached
PID USER PRI NI SIZE RSS SHARE STAT LIB %CPU %MEM TIME COMMAND
400 root 17 0 26444 19M 1600 R 0 6.9 32.1 137:51 X
1887 ivanov 10 0 4312 4240 3232 R 0 4.0 6.7 117:04 xdos
3505 ivanov 10 0 740 740 564 R 0 2.3 1.1 0:01 top
402 ivanov 1 0 1028 968 560 S 0 0.7 1.5 0:11 fvwm2
2148 ivanov 6 5 2548 2436 644 S N 0 0.3 3.8 0:14 xfte
415 ivanov 0 0 1024 1004 780 S 0 0.1 1.5 0:07 xrus
508 ivanov 1 0 18144 13M 3764 S 0 0.1 21.3 1:06 netscape
3135 root 1 0 1696 1696 1204 S 0 0.1 2.6 0:05 nxterm
3460 root 1 0 832 832 600 S 0 0.1 1.3 0:00 ssh
1 root 0 0 284 244 224 S 0 0.0 0.3 0:04 init
2 root 0 0 0 0 0 SW 0 0.0 0.0 0:00 kflushd
3 root -12 -12 0 0 0 SW< 0 0.0 0.0 0:00 kswapd
4 root 0 0 0 0 0 SW 0 0.0 0.0 0:00 md_thread
5 root 0 0 0 0 0 SW 0 0.0 0.0 0:00 md_thread
398 ivanov 0 0 520 468 412 S 0 0.0 0.7 0:00 startx
380 ivanov 0 0 740 644 524 S 0 0.0 1.0 0:00 zsh
362 root 0 0 228 196 180 S 0 0.0 0.3 0:00 gpm
Где брать информацию про shell
Где брать информацию про shell
и
соответственно.
Практические задания
дать команду
и почему?
Какой файл после этого появится в директории? Удалить этот файл
командой rm.
найдет все файлы, в имени которых есть "lib", в директории
/usr и ее поддиректориях. Заставить find найти в
директории /var все файлы, в имени которых встречается
"log", результат поместить в файл logfiles в
home-директории, а все сообщения об ошибках -- в файл errs
там же.
Затем запустить top и при его помощи "убить" xlock.