Сайт о телевидении

Сайт о телевидении

» » Языки командной оболочки Shell (sh, bash, ksh и другие). О разных командных оболочках Linux и Unix

Языки командной оболочки Shell (sh, bash, ksh и другие). О разных командных оболочках Linux и Unix

Как уже говорилось выше, для построения произвольных алгоритмов необходимо иметь операторы проверки условий. Оболочка bash поддерживает операторы выбора if then else и case , а также операторы организации циклов for , while , until , благодаря чему она превращается в мощный язык программирования.

5.8.1 Операторы if и test (или )

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

if list1 then list2 else list3 fi

где list1 , list2 и list3 — это последовательности команд, разделенные запятыми и оканчивающиеся точкой с запятой или символом новой строки. Кроме того, эти последовательности могут быть заключены в фигурные скобки: {list} .

Оператор if проверяет значение, возвращаемое командами из list1 . Если в этом списке несколько команд, то проверяется значение, возвращаемое последней командой списка. Если это значение равно 0, то будут выполняться команды из list2 ; если это значение не нулевое, будут выполнены команды из list3 . Значение, возвращаемой таким составным оператором if , совпадает со значением, выдаваемым последней командой выполняемой последовательности.

Полный формат команды if имеет вид:

if list then list [ elif list then list ] ... [ else list ] fi

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

В качестве выражения, которое стоит сразу после if или elif , часто используется команда test , которая может обозначаться также квадратными скобками . Команда test выполняет вычисление некоторого выражения и возвращает значение 0, если выражение истинно, и 1 в противном случае. Выражение передается программе test как аргумент. Вместо того, чтобы писать

test expression,

можно заключить выражение в квадратные скобки:

[ expression ].

Заметьте, что test и [ — это два имени одной и той же программы, а не какое-то магическое преобразование, выполняемое оболочкой bash (только синтаксис [ требует, чтобы была поставлена закрывающая скобка). Заметьте также, что вместо test в конструкции if может быть использована любая программа.

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

if [ -e textmode2.htm ] ; then

ls textmode*

else

pwd

Об операторе test (или […]) надо бы поговорить особо.

5.8.2 Оператор test и условные выражения

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

    A file

Верно, если файл с именем file существует.

    B file

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

    C file

Верно, если file существует и является специальным файлом символьного устройства.

    D file

Верно, если file существует и является каталогом.

    E file

Верно, если файл с именем file существует.

    F file

Верно, если файл с именем file существуети является обычным файлом.

    G file

Верно, если файл с именем file существуети для него установлен бит смены группы.

    H file или -L file

Верно, если файл с именем file существуети является символической ссылкой.

    K file

Верно, если файл с именем file существуети для него установлен "sticky"" bit.

    P file

Верно, если файл с именем file существуети является именованным каналом (FIFO).

    R file

Верно, если файл с именем file существуети для него установлено право на чтение

    S file

Верно, если файл с именем file существуети его размер больше нуля .

    T fd

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

    U file

Верно, если файл с именем file существуети для него установлен бит смены пользователя.

    W file

Верно, если файл с именем file существуети для него установлено право на запись.

    X file

Верно, если файл с именем file существуети является исполняемым .

    O file

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

    G file

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

    S file

Верно, если файл с именем file существуети является сокетом.

    N file

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

    file1 -nt file2

Верно, если файлfile1 имеет более позднее время модификации, чем file2 .

    file1 -ot file2

Верно, если файлfile1 старше , чем file2 .

    file1 -ef file2

Верно, если файлыfile1 и file2 имеют одинаковые номера устройств и индексных дескрипторов (inode).

    O optname

Верно, если задействована опция оболочки optname . Пояснения см. на странице man bash.

    Z string

Верно, если длина строки равна нулю.

    N string

Верно, если длина строки не равна нулю.

    string1 == string2

Верно, если строки совпадают. Вместо == может использоваться = .

    string1 !== string2

Верно, если строки не совпадают.

    string1 < string2

Верно, если строка string1 лексикографически предшествует строке string2 (для текущей локали).

    string1 > string2

Верно, если строка string1 лексикографически стоит после строки string2 (для текущей локали).

    arg1 OP arg2

Здесь OP — это одна из операций арифметического сравнения: -eq (равно), -ne (не равно), -lt (меньше чем), -le (меньше или равно), -gt (больше), -ge (больше или равно). В качестве аргументов могут использоваться положительные или отрицательные целые.

Из этих элементарных условных выражений можно строить сколь угодно сложные с помощью обычных логических операций ОТРИЦАНИЯ, И и ИЛИ:

    !(expression)

Булевский оператор отрицания.

    expression1 -a expression2

Булевский оператор AND (И). Верен, если верны оба выражения.

    expression1 -o expression2

Булевский оператор OR (ИЛИ). Верен, если верно любое из двух выражений.

Такие же условные выражения используются и в операторах while и until , которые мы рассмотрим чуть ниже.

5.8.3 Оператор case

Формат оператора case таков:

case word in [ [(] pattern [ | pattern ] ...) list ;; ] ... esac

Команда case вначале производит раскрытие слова word , и пытается сопоставить результат с каждым из образцов pattern поочередно. После нахождения первого совпадения дальнейшие проверки не производятся, выполняется список команд, стоящий после того образца, с которым обнаружено совпадение. Значение, возвращаемое оператором, равно 0, если совпадений с образцами не обнаружено. В противном случае возвращается значение, выдаваемое последней командой из соответствующего списка.

Следующий пример использования оператора case заимствован из системного скрипта /etc/rc.d/rc.sysinit.

case "$UTC" in

yes|true)

CLOCKFLAGS="$CLOCKFLAGS -u";

CLOCKDEF="$CLOCKDEF (utc)";

no|false)

CLOCKFLAGS="$CLOCKFLAGS --localtime";

CLOCKDEF="$CLOCKDEF (localtime)";

esac

Если переменная принимает значение yes или true, то будет выполнена первая пара команд, а если ее значение равно no или false - вторая пара.

5.8.4 Оператор select

Оператор select позволяет организовать интерактивное взаимодействие с пользователем. Он имеет следующий формат:

select name [ in word; ] do list ; done

Вначале из шаблона word формируется список слов, соответствующих шаблону. Этот набор слов выводится в стандартный поток ошибок, причем каждое слово сопровождается порядковым номером. Если шаблон word пропущен, таким же образом выводятся позиционные параметры. После этого выдается стандартное приглашение PS3, и оболочка ожидает ввода строки на стандартном вводе. Если введенная строка содержит число, соответствующее одному из отображенных слов, то переменной name присваивается значение, равное этому слову. Если введена пустая строка, то номера и соответствующие слова выводятся заново. Если введено любое другое значение, переменной name присваивается нулевое значение. Введенная пользователем строка запоминается в переменой REPLY . Список команд list выполняется с выбранным значением переменной name .

Вот небольшой скрипт:

#!/bin/sh

echo "Какую ОС Вы предпочитаете?"

select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do

break

done

echo "Вы бы выбрали $var"

Какую ОС Вы предпочитаете?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#?

Нажмите любую из 4 предложенных цифр (1,2,3,4). Если вы, например, введете 1, то увидите собщение:

“Вы бы выбрали Linux”

5.8.5 Оператор for

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

for name in words do list done.

Правила построения списков команд (list ) такие же, как и в операторе if .

Пример. Следующий скрипт создает файлы foo_1, foo_2 и foo_3:

for a in 1 2 3 ; do

touch foo_$a

done

В общем случае оператор for имеет формат:

for name [ in word; ] do list ; done

Вначале производится раскрытие слова word в соответствии с правилами раскрытия выражений, приведенными выше. Затем переменной name поочередно присваиваются полученные значения, и каждый раз выполняется список команд list . Если "in word " пропущено, то список команд list выполняется один раз для каждого позиционного параметра, который задан.

В Linux имеется программа seq , которая воспринимает в качестве аргументов два числа и выдает последовательность всех чисел, расположенных между заданными. С помощью этой команды можно заставить for в bash работать точно так же, как аналогичный оператор работает в обычных языках программирования. Для этого достаточно записать цикл for следующим образом:

for a in $(seq 1 10) ; do

cat file_$a

done

Эта команда выводит на экран содержимое 10-ти файлов: " file_1", ..., "file_10".

5.8.6 Операторы while и until

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

while list1 do list2 done.

while [ -d mydirectory ] ; do

ls -l mydirectory >> logfile

echo -- SEPARATOR -- >> logfile

sleep 60

done

Такая программа будет протоколировать содержание каталога "mydirectory" ежеминутно до тех пор, пока директория существует.

Оператор until аналогичен оператору while :

until list1 do list2 done.

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

5.8.7 Функции

Синтаксис

Оболочка bash позволяет пользователю создавать собственные функции. Функции ведут себя и используются точно так же, как обычные команды оболочки, т. е. мы можем сами создавать новые команды. Функции конструируются следующим образом:

function name () { list }

Причем слово function не обязательно, name определяет имя функции, по которому к ней можно обращаться, а тело функции состоит из списка команд list , находящегося между { и }. Этот список команд выполняется каждый раз, когда имя name задано как имя вызываемой команды. Отметим, что функции могут задаваться рекурсивно, так что разрешено вызывать функцию, которую мы задаем, внутри нее самой.

Функции выполняются в контексте текущей оболочки: для интерпретации функции новый процесс не запускается (в отличие от выполнения скриптов оболочки).

Аргументы

Когда функция вызывается на выполнение, аргументы функции становятся позиционными параметрами (positional parameters) на время выполнения функции. Они именуются как $n , где n — номер аргумента, к которому мы хотим получить доступ. Нумерация аргументов начинается с 1, так что $1 — это первый аргумент. Мы можем также получить все аргументы сразу с помощью $* , и число аргументов с помощью $# . Позиционный параметр 0 не изменяется.

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

Локальные переменные (local)

Если мы хотим создать локальный параметр, можно использовать ключевое слово local . Синтаксис ее задания точно такой же, как и для обычных параметров, только определению предшествует ключевое слово local: local name=value .

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

seq()

local I=$1;

while [ $2 != $I ]; do

echo -n "$I ";

I=$(($I + 1))

done;

echo $2

Обратите внимание на опцию -n оператора echo , она отменяет переход на новую строку. Хотя это и несущественно для тех целей, которые мы здесь имеем в виду, это может оказаться полезным для использования функции в других целях.

Функция вычисления факториала fact

Еще один пример:

fact()

if [ $1 = 0 ]; then

echo 1;

else

echo $(($1 * $(fact $(($1 — 1)))))

Это функция факториала, пример рекурсивной функции. Обратите внимание на арифметическое расширение и подстановку команд.

В. Костромин (kos at rus-linux dot net) - 5.8. Shell как язык программирования
  • Tutorial

Зачем и для кого статья?

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

Здесь не будет пересказа манов (документации), и статья никак не отменяет и не заменяет их чтение. Вместо этого я расскажу о главных вещах (командах, приемах и принципах), которые надо осознать с самого начала работы в unix shell-е, чтобы работа происходила эффективно и приятно.

Статья касается полноценных unix-подобных окружений, с полнофункциональным шеллом (предпочтительно zsh или bash)и достаточно широким набором стандартных программ.

Что такое шелл

Shell (шелл, он же «командная строка», он же CLI, он же «консоль», он же «терминал», он же «черное окошко с белыми буковками») -- это текстовый интерфейс общения с операционной системой (ну, строго говря, это программа , которая таковой интерфейс обеспечивает, но сейчас это различие несущественно).

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

Типичный вид шелла:

Шелл - это основной способ для взаимодействия со всеми Unix-подобными серверными системами.

Где встречаются системы с командной строкой?

Где вас может поджидать unix-овый шелл, популярные варианты:
  • MacOS (bash);
  • удаленный доступ на сервер по работе или для личного веб-проекта;
  • домашний файл-сервер с удаленным доступом;
  • Ubuntu, PC-BSD на ноутбуке/десктопе - unix-подобные системы сегодня просты в установке и использовании.

Какие задачи разумно решать шеллом?

Естественные задачи, для которых шелл пригоден, полезен и незаменим:
  • интерактивная работа в терминале:
    • выполнение компиляции, запуск заданий через make;
    • сравнение текстовых файлов;
    • быстрый ad-hoc анализ данных (количество уникальных ip в логе, распределение записей по часам/минутам и т.п.);
    • разовые массовые действия (прибить много процессов; если работаете с системой контроля версий - ревертнуть или зарезолвить кучу файлов);
    • диагностика происходящего в системе (семафоры, локи, процессы, дескрипторы, место на диске и т.п.);
  • скриптование:
    • установочные скрипты, для выполнения которых нельзя рассчитывать на наличие других интерпретаторов - это не для новичков;
    • функции для кастомизации интерактивного шелла (влияющие на приглашение, меняющие каталог, устанавливающие переменные окружения) - тоже не совсем для новичков;
    • одноразовые скрипты типа массового перекодирования файлов;
    • makefile-ы.

Абсолютно первые шаги

Начинаем работу: войти и выйти

Убедитесь, что точно знаете, как запустить шелл и как из него выйти.

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

На MacOS - тоже запустить Terminal.

Для доступа к удаленному серверу - воспользоваться ssh (если локально у вас MacOS, Ubuntu или другая unix-like система) или putty (если у вас Windows).

Кто я, где я?

Выполните следующие команды:
  • hostname - выводит имя машины (сервера), на которой вы сейчас находитесь;
  • whoami - выводит ваш логин (ваше имя в системе);
  • tree -d / |less - псевдографическое изображение дерева каталогов на машине; выход из пролистывания - q ;
  • pwd - выводит каталог, в котором вы сейчас находитесь; в командной строке вы не можете быть «просто так», вы обязательно находитесь в каком-то каталоге (=текущий каталог, рабочий каталог). Вероятно, текущий рабочий каталог выводится у вас в приглашении (prompt).
  • ls - список файлов в текущем каталоге; ls /home - список файлов в указанном каталоге;

История команд (history)

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

Выполните несколько команд: hostname , ls , pwd , whoami . Теперь нажмите клавишу «вверх». В строке ввода появилась предыдущая команда. Клавишами «вверх» и «вниз» можно перемещаться вперед и назад по истории. Когда долистаете до hostname , нажмите Enter - команда выполнится еще раз.

Команды из истории можно не просто выполнять повторно, а еще и редактировать. Долистайте историю до команды ls , добавьте к ней ключ -l (получилось ls -l , перед минусом пробел есть, а после - нет). Нажмите Enter - выполнится модифицированная команда.

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

Copy-paste

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

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

Попробуйте выполнить команду date +"%y-%m-%d, %A"
Вводили ли вы ее целиком руками или скопировали из статьи? Убедитесь, что вы можете ее скопировать, вставить в терминал и выполнить.

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

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

Ключи и опции

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

Akira@latitude-e7240: ~/shell-survival-quide> ls Makefile shell-first-steps.md shell-first-steps.pdf shell-survival-quide.md shell-survival-quide.pdf
Если же добавить ключ -l , к каждому файлу выводится подробная информация:

Akira@latitude-e7240: ~/shell-survival-quide> ls -l total 332 -rw-rw-r-- 1 akira akira 198 Feb 13 11:48 Makefile -rw-rw-r-- 1 akira akira 15107 Feb 14 22:26 shell-first-steps.md -rw-rw-r-- 1 akira akira 146226 Feb 13 11:49 shell-first-steps.pdf -rw-rw-r-- 1 akira akira 16626 Feb 13 11:45 shell-survival-quide.md -rw-rw-r-- 1 akira akira 146203 Feb 13 11:35 shell-survival-quide.pdf
Это очень типичная ситуация: если к вызову команды добавлять специальные модификаторы (ключи, опции, параметры), поведение команды меняется. Сравните: tree / и tree -d / , hostname и hostname -f .

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

Ls -ld /home ls -l /home grep root /etc/passwd

man

man - справка по командам и программам, доступным на вашей машине, а также по системным вызовам и стандартной библиотеке C.

Попробуйте: man grep , man atoi , man chdir , man man .

Пролистывание вперед и назад делается кнопками «вверх», «вниз», «PageUp», «PageDown», выход из просмотра справки - кнопкой q . Поиск определенного текста в справочной статье: нажимите / (прямой слеш), введите текст для поиска, нажимите Enter. Перемещение к следующим вхождениям - клавиша n .

Все справочные статьи делятся на категории. Самые важные:

  • 1 - исполняемые программы и шелльные команды (wc , ls , pwd и т.п.);
  • 2 - системные вызовы (fork , dup2 и т.п.)
  • 3 - библиотечные функции (printf , scanf , cos , exec).
Указывать, из какой именно категории надо показать справку, нужно в случаях совпадений имен. Например, man 3 printf описывает функцию из стандартной библиотеки C, а man 1 printf - консольную программу с таким же именем.

Посмотреть список всех доступных на машине справочных статей можно с помощью команды man -k . (точка - тоже часть комады).

less

Когда в небольшом окне терминала надо просмотреть очень длинный текст (содержимое какого-то файла, длинный man и т.п.), используют специальные программы-«пейджеры» (от слова page/страница, то есть постраничные листатели). Самый популярный листатель - less , и именно он обеспечивает вам пролистывание, когда вы читаете man-ы.

Попробуйте и сравните поведение:

Cat /etc/bash.bashrc cat /etc/bash.bashrc |less

Можно передать файл в пролистыватель сразу в параметрах:

Less /etc/bash.bashrc

Пролистывание вверхи и вниз - кнопки «вверх», «вниз», «PageUp», «PageDown», выход - кнопка q . Поиск определенного текста: нажимите / (прямой слеш), введите текст для поиска, нажимите Enter. Перемещение к следующим вхождениям - клавиша n . (Узнаете инструкцию про man ? Ничего удивительного, для вывода справки тоже используется less .)

Права

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

Посмотреть права на файл можно с помощью ls -l . Например:

> ls -l Makefile -rw-r--r-- 1 akira students 198 Feb 13 11:48 Makefile
Этот вывод означает, что владельцу (akira) можно читать и писать файл, группе (students) - только читать, всем прочим пользователя - тоже только читать.

Если при работе вы получаете сообщение permission denied , это значит, что у вас недостаточно правна объект, с которым вы хотели работать.

Подробнее читайте в man chmod .

STDIN, STDOUT, конвейеры (пайпы)

С каждой исполняющейся программой связаны 3 стандартных потока данных: поток входных данных STDIN , поток выходных данных STDOUT , поток для вывода ошибок STDERR .

Запустите программу wc , введите текст Good day today , нажмите Enter, введтие текст good day , нажмите Enter, нажмите Ctrl+d. Программа wc покажет статистику по количеству букв, слов и строк в вашем тексте и завершится:

> wc good day today good day 2 5 24
В данном случае вы подали в STDIN программы двухстрочный текст, а в STDOUT получили три числа.

Теперь запустите команду head -n3 /etc/passwd , должно получиться примерно так:

> head -n3 /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin
В этом случае программа head ничего не читала из STDIN , а в STDOUT написала три строки.

Можно представить себе так: программа - это труба, в которую втекает STDIN , а вытекает STDOUT .

Важнейшее свойство юниксовой командной строки состоит в том, что программы-«трубы» можно соединять между собой: выход (STDOUT) одной программы передавать в качестве входных данных (STDIN) другой программе.

Такая конструкция из соединенных программ называется по-английски pipe (труба), по-русски - конвейер или пайп.

Объединение программ в конвейер делается символом | (вертикальная черта)

Выполните команду head -n3 /etc/passwd |wc , получится примерно следующее:

> head -n3 /etc/passwd |wc 3 3 117
Произошло вот что: программа head выдала в STDOUT три строки текста, которые сразу же попали на вход программе wc , которая в свою очередь подсчитала количество символов, слов и строк в полученном тексте.

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

> head -n3 /etc/passwd |wc |wc 1 3 24

Составление конвейеров (пайпов) - очень частое дело при работе в командной строке. Пример того, как это делается на практике, читайте в разделе «Составление конвейера-однострочника».

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

Вывод (STDOUT) програмы можно не только передать другой программе по конвейеру, но и просто записать в файл. Такое перенаправление делается с помощью > (знак «больше»):

Date > /tmp/today.txt
В результате выполнения этой команды на диске появится файл /tmp/today.txt . Посмотрите его содержимое с помощью cat /tmp/today.txt

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

Если надо не перезаписать файл, а добавить вывод в его конец, используйте >> :

Date >> /tmp/today.txt
Проверьте, что теперь записано в файле.

Кроме того, программе можно вместо STDIN передать любой файл. Попробуйте:

Wc

Что делать, когда что-то непонятно

Если вы сталкиваетесь с поведением системы, которое не понимаете, или хотите добиться определенного результата, но не знаете, как именно, советую действовать в следующем порядке (кстати, это относится не только к шеллам):
  • насколько возможно четко сформулируйте вопрос или задачу - нет ничего сложнее, чем решать «то, не знаю что»;
  • вспомните, сталкивались ли вы уже с такой же или подобной проблемой - в этом случае стоит попробовать решение, которое сработало в прошлый раз;
  • почитайте подходящие man-ы (если понимаете, какие man-ы подходят в вашем случае) - возможно, вы найдете подходящие примеры использования команд, нужные опции или ссылки на другие команды;
  • подумайте: нельзя ли немного поменять задачу? - возможно, чуть-чуть изменив условия, вы получите задачу, которую уже умеете решать;
  • задайте свой четко сформулированный вопрос в поисковой системе - возможно, ответ найдется на Stack Overflow или других сайтах;
Если ничего из перечисленного не помогло - обратитесь за советом к преподавателю, опытному коллеге или товарищу. И не бойтесь задавать «глупые» вопросы - не стыдно не знать, стыдно не спрашивать.

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

Методы работы

Скопировать-и-вставить - из man-ов, из статей на StackOverflow и т.п.Командная строка состоит из текста, пользуйтесь этим: копируйте и используйте примеры команд,записывайте удачные находки на память, публикуйте их в твиттерах и блогах.

Вытащить из истории предыдущую команду, добавить в конвейер еще одну команду, запустить, повторить .См. также раздел «Составление конвейера-однострочника».

Базовые команды

  • переход в другой каталог: cd ;
  • просмотр содержимого файлов: саt , less , head , tail ;
  • манипуляции с файлами: cp , mv , rm ;
  • просмотр содержимого каталогов: ls , ls -l , ls -lS ;
  • структура каталогов: tree , tree -d (можно передать в качестве параметра каталог);
  • поиск файлов: find . -name ... ;

Аналитика

  • wc , wc -l ;
  • sort -k - сортировка по указанному полю;
  • sort -n - числовая соритровка;
  • diff - сравнение файлов;
  • grep , grep -v , grep -w , grep "\" , grep -E - поиск текста;
  • uniq , uniq -c - уникализация строк;
  • awk - в варианте awk "{print $1}" , чтобы оставить только первое поле из каждой строки, $1 можно менять на $2 , $3 и т.д.;

Диагностика системы

  • ps axuww - информация о процессах (запущенных программах), работающих на машине;
  • top - интерактивный просмотр самых ресурсоемких процессов;
  • df - занятое и свободное место на диске;
  • du - суммарный размер файлов в каталоге (рекурсивно с подкаталогами);
  • strace , ktrace - какие системные вызовы выполняет процесс;
  • lsof - какие файлы использует процесс;
  • netstat -na , netstat -nap - какие порты и сокеты открыты в системе.

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

Массовое и полуавтоматическое выполнение

На первых порах пропускайте этот раздел, эти команды и конструкции понадобятся вам тогда, когда доберетесь до несложного шелльного скриптинга.
  • test - проврека условий;
  • while read - цикл по строчкам STDIN ;
  • xargs - подстановка строк из STDIN в параметры указанной программе;
  • seq - генерация последовательностей натуральных чисел;
  • () - объединить вывод нескольких команд;
  • ; - выполнить одно за другим;
  • && - выполнить при условии успешного завершения первой команды;
  • || - выполнить при условии неудачного завершения первой команды;
  • tee - продублировать вывод программы в STDOUT и в файл на диске.

Разное

  • date - текущая дата;
  • curl - скачивает документ по указаному url и пишет результат на STDOUT ;
  • touch - обновить дату модификации файла;
  • kill - послать процессу сигнал;
  • true - ничего не делает, возвращает истину, полезна для организации вечных циклов;
  • sudo - выполнить команду от имени root "а.

Составление конвейера-однострочника

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

Шаг 1.
Понять, какая программа выдает примерно нужные данные, хотя бы и не в чистом виде. Для нашей задачи стоит получить список всех процессов в системе: ps axuww . Запустить.

Шаг 2.
Посмотреть на полученные данные глазами, придумать фильтр, который выкинет часть ненужных данных. Часто это grep или grep -v . Клавишей «Вверх» вытащить из истории предыдущую команду, приписать к ней придуманный фильтр, запустить.

Ps axuww |grep `whoami`
- только процессы текущего пользователя.

Шаг 3.
Повторять пункт 2, пока не получатся чистые нужные данные.

"
- все процессы с нужным именем (плюс, может быть, лишние вроде vim task-6-server.c и т.п.),

Ps axuww |grep `whoami` | grep "\" | grep -v vim ps axuww |grep `whoami` | grep "\" | grep -v vim |grep -v less
- только процессы с нужным именем

Ps axuww |grep `whoami` | grep "\" | grep -v vim |grep -v less |awk "{print $2}"

Pid-ы нужных процессов, п. 3 выполнен

Шаг 4.
Применить подходящий финальный обработчик. Клавишей «Вверх» вытаскиваем из истории предыдущую команду и добавляем обработку, которая завершит решение задачи:

  • |wc -l чтобы посчитать количество процессов;
  • >pids чтобы записать pid-ы в файл;
  • |xargs kill -9 убить процессы.

Задания для тренировки

Хотите попрактиковаться в новых умениях? Попробуйте выполнить следующие задания:
  • получите список всех файлов и каталогов в вашем домашнем каталоге;
  • получите список всех man -статей из категории 2 (системные вызовы);
  • посчитайте, сколько раз в man-е по программе grep встречается слово grep;
  • посчитайте, сколько процессов запущено в данный момент от имени пользователя root ;
  • найдите, какая команда встречается в максимальном количестве категорий справки (man);
  • подсчитайте, сколько раз встречается слово var на странице ya.ru .
Подсказка: вам понадобится find , grep -o , awk "{print $1}" , регулярные выражения в grep , curl -s .

Что изучать дальше?

Если командная строка начинает вам нравиться, не останавливайтесь, продолжайте совершенствовать свои навыки.

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

  • find со сложными опциями
  • apropos
  • locate
  • telnet
  • netcat
  • tcpdump
  • rsync
  • screen
  • zgrep , zless
  • visudo
  • crontab -e
  • sendmail
Кроме того, со временем стоит освоить какой-нибудь скриптовый язык,например, perl или python , или даже их оба.

Кому это надо?

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

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

Совет Не стоит сдавать или использовать (даже в изменённом виде) приведённый ниже код в качестве домашнего проекта в школе или вузе. Многие преподаватели знают об оригинальной статье и уличат вас в обмане.

Жизненный цикл командной оболочки

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

  1. Инициализация: на этом этапе она читает и исполняет свои файлы конфигурации. Они изменяют её поведение.
  2. Интерпретация: далее оболочка считывает команды из stdin и исполняет их.
  3. Завершение: после исполнения основных команд она исполняет команды выключения, освобождает память и завершает работу.

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

Int main(int argc, char **argv) { // Загрузка файлов конфигурации при их наличии. // Запуск цикла команд. lsh_loop(); // Выключение / очистка памяти. return EXIT_SUCCESS; }

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

Базовый цикл командной оболочки

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

  1. Чтение: считывание команды со стандартных потоков.
  2. Парсинг: распознавание программы и аргументов во входной строке.
  3. Исполнение: запуск распознанной команды.

Эта идея реализована в функции lsh_loop() :

Void lsh_loop(void) { char *line; char **args; int status; do { printf("> "); line = lsh_read_line(); args = lsh_split_line(line); status = lsh_execute(args); free(line); free(args); } while (status); }

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

Чтение строки

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

#define LSH_RL_BUFSIZE 1024 char *lsh_read_line(void) { int bufsize = LSH_RL_BUFSIZE; int position = 0; char *buffer = malloc(sizeof(char) * bufsize); int c; if (!buffer) { fprintf(stderr, "lsh: ошибка выделения памяти\n"); exit(EXIT_FAILURE); } while (1) { // Читаем символ c = getchar(); // При встрече с EOF заменяем его нуль-терминатором и возвращаем буфер if (c == EOF || c == "\n") { buffer = "\0"; return buffer; } else { buffer = c; } position++; // Если мы превысили буфер, перераспределяем блок памяти if (position >= bufsize) { bufsize += LSH_RL_BUFSIZE; buffer = realloc(buffer, bufsize); if (!buffer) { fprintf(stderr, "lsh: ошибка выделения памяти\n"); exit(EXIT_FAILURE); } } } }

В первой части много объявлений. Стоит отметить, что в коде используется старый стиль C, а именно объявление переменных до основной части кода. Основная часть функции находится внутри, на первый взгляд, бесконечного цикла while(1) . В цикле символ считывается и сохраняется как int , а не char (EOF - это целое число, а не символ, поэтому для проверки используйте int). Если это символ перевода строки или EOF, мы завершаем текущую строку и возвращаем ее. В обратном случае символ добавляется в существующую строку.

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

Те, кто знаком с новыми версиями стандартной библиотеки C, могут заметить, что в stdio.h есть функция getline() , которая выполняет большую часть работы, реализованной в коде выше. Эта функция была расширением GNU для библиотеки C до 2008 года, а затем была добавлена в спецификацию, поэтому большинство современных Unix-систем уже идут с ней в комплекте. С getline функция становится тривиальной:

Char *lsh_read_line(void) { char *line = NULL; ssize_t bufsize = 0; // getline сама выделит память getline(&line, &bufsize, stdin); return line; }

Парсинг строки

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

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

#define LSH_TOK_BUFSIZE 64 #define LSH_TOK_DELIM " \t\r\n\a" char **lsh_split_line(char *line) { int bufsize = LSH_TOK_BUFSIZE, position = 0; char **tokens = malloc(bufsize * sizeof(char*)); char *token; if (!tokens) { fprintf(stderr, "lsh: ошибка выделения памяти\n"); exit(EXIT_FAILURE); } token = strtok(line, LSH_TOK_DELIM); while (token != NULL) { tokens = token; position++; if (position >= bufsize) { bufsize += LSH_TOK_BUFSIZE; tokens = realloc(tokens, bufsize * sizeof(char*)); if (!tokens) { fprintf(stderr, "lsh: ошибка выделения памяти\n"); exit(EXIT_FAILURE); } } token = strtok(NULL, LSH_TOK_DELIM); } tokens = NULL; return tokens; }

Реализация этой функции подозрительно похожа на lsh_read_line() , и это неспроста! Здесь используется та же стратегия, только вместо нуль-терминированного массива символов мы используем нуль-терминированный массив указателей.

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

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

Теперь у нас есть массив токенов, готовых к исполнению.

Как командные оболочки запускают процессы

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

В Unix есть только два способа запуска процессов. Первый (который не будем брать в счет) - это Init . Видите ли, когда загружается Unix-система, загружается её ядро. После загрузки и инициализации ядро запускает только один процесс, который называется Init . Этот процесс выполняется в течение всего времени работы компьютера, и управляет загрузкой остальных процессов, которые необходимы для его работы.

Поскольку все остальные процессы не Init , остаётся только один практический способ запуска процессов: системный вызов fork() . Когда эта функция вызывается, операционная система делает дубликат процесса и запускает их параллельно. Первоначальный процесс называется «родительским», а новый - «дочерним». Дочернему процессу fork() возвращает 0 , а родителю - идентификатор процесса (PID) его дочернего элемента. Таким образом, любой новый процесс можно создать только из копии уже существующего.

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

Благодаря этим двум системным вызовам и возможен запуск большинства программ в Unix. Сперва существующий процесс раздваивается на родительский и дочерний, а затем дочерний процесс использует exec() для замены себя новой программой. Родительский процесс может продолжать делать другие вещи, а также следить за своими дочерними элементами, используя системный вызов wait() .

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

Int lsh_launch(char **args) { pid_t pid, wpid; int status; pid = fork(); if (pid == 0) { // Дочерний процесс if (execvp(args, args) == -1) { perror("lsh"); } exit(EXIT_FAILURE); } else if (pid < 0) { // Ошибка при форкинге perror("lsh"); } else { // Родительский процесс do { wpid = waitpid(pid, &status, WUNTRACED); } while (!WIFEXITED(status) && !WIFSIGNALED(status)); } return 1; }

Эта функция принимает список аргументов, которые мы создали ранее. Затем она разворачивает процесс и сохраняет возвращаемое значение. Как только fork() возвращает значение, мы получаем два параллельных процесса. Дочернему процессу соответствует первое условие if (где pid == 0).

В дочернем процессе мы хотим запустить команду, заданную пользователем. Поэтому мы используем один из вариантов системного вызова exec , execvp . Разные варианты exec делают разные вещи. Одни принимают переменное количество строковых аргументов, другие берут список строк, а третьи позволяют указать окружение, в котором выполняется процесс. Этот конкретный вариант принимает имя программы и массив (также называемый вектором, отсюда "v") строковых аргументов (первым должно быть имя программы). "p" означает, что вместо предоставления полного пути к файлу программы для запуска мы укажем только её имя, а также кажем операционной системе искать её самостоятельно.

Если команда exec возвращает -1 (или любое другое значение), значит, произошла ошибка. Таким образом, мы используем perror для вывода сообщения об ошибке вместе с именем программы, чтобы было понятно, где произошла ошибка. Затем мы завершаем процесс, но так, чтобы программная оболочка продолжала работать.

Второе условие (pid < 0) проверяет, произошла ли в процессе выполнения fork() ошибка. Если ошибка есть, мы выводим сообщение об этом на экран, но программа продолжает работать.

Третье условие означает, что вызов fork() выполнен успешно. Там находится родительский процесс. Мы знаем, что потомок собирается исполнить процесс, поэтому родитель должен дождаться завершения команды. Мы используем waitpid() для ожидания изменения состояния процесса. К сожалению, у waitpid() есть много опций (например, exec()). Процессы могут изменять свое состояние множеством способов, и не все состояния означают, что процесс завершился. Процесс может либо завершиться обычным путём (успешно либо с кодом ошибки), либо быть остановлен сигналом. Таким образом, мы используем макросы, предоставляемые waitpid() , чтобы убедиться, что процесс завершен. Затем функция возвращает 1 как сигнал вызывающей функции, что она снова может вывести приглашение ввода.

Встроенные функции оболочки

Возможно, вы заметили, что функция lsh_loop() вызывает lsh_execute() , но выше мы назвали нашу функцию lsh_launch() . Это было намеренно! Дело в том, что большинство команд, которые исполняет оболочка, являются программами - но не все. Некоторые из команд встроены прямо в оболочку.

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

Аналогично программа с именем exit не сможет выйти из командной оболочки, которая ее вызвала. Эта команда также должна быть встроена в оболочку. Кроме того, большинство оболочек настраиваются с помощью сценариев конфигурации, таких как ~/.bashrc . Эти сценарии используют команды, которые изменяют работу оболочки. Сами же команды могут изменить работу оболочки, если только они были реализованы внутри самой оболочки.

Соответственно, имеет смысл добавить некоторые команды в оболочку. В эту оболочку мы добавим cd , exit и help . А вот и реализация этих функций:

/* Объявление функций для встроенных команд оболочки: */ int lsh_cd(char **args); int lsh_help(char **args); int lsh_exit(char **args); /* Список встроенных команд, за которыми следуют соответствующие функции */ char *builtin_str = { "cd", "help", "exit" }; int (*builtin_func) (char **) = { &lsh_cd, &lsh_help, &lsh_exit }; int lsh_num_builtins() { return sizeof(builtin_str) / sizeof(char *); } /* Реализации встроенных функций */ int lsh_cd(char **args) { if (args == NULL) { fprintf(stderr, "lsh: ожидается аргумент для \"cd\"\n"); } else { if (chdir(args) != 0) { perror("lsh"); } } return 1; } int lsh_help(char **args) { int i; printf("LSH Стивена Бреннана\n"); printf("Наберите название программы и её аргументы и нажмите enter.\n"); printf("Вот список втсроенных команд:\n"); for (i = 0; i < lsh_num_builtins(); i++) { printf(" %s\n", builtin_str[i]); } printf("Используйте команду man для получения информации по другим программам.\n"); return 1; } int lsh_exit(char **args) { return 0; }

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

Следующая часть представляет собой массив имён встроенных команд, за которыми следует массив соответствующих функций. Это значит, что в будущем встроенные команды могут быть добавлены путем изменения этих массивов, а не большого оператора switch где-то в коде. Если вы смущены объявлением builtin_func , все в порядке. Это массив указателей на функции (которые принимают массив строк и возвращают int). Любое объявление, включающее указатели на функции в C, может стать действительно сложным.

Наконец, идет реализация каждой функции. Функция lsh_cd() сначала проверяет наличие своего второго аргумента и выводит сообщение об ошибке, если его нет. Затем она вызывает chdir() , проверяет наличие ошибок и завершает работу. Функция справки выводит информативное сообщение и имена всех встроенных функций. А функция выхода возвращает 0 , как сигнал для окончания цикла команд.

Объединение встроенных функций и процессов

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

Int lsh_execute(char **args) { int i; if (args == NULL) { // Была введена пустая команда. return 1; } for (i = 0; i < lsh_num_builtins(); i++) { if (strcmp(args, builtin_str[i]) == 0) { return (*builtin_func[i])(args); } } return lsh_launch(args); }

Код проверяет, является ли команда встроенной. Если это так, то запускает её, а в противном случае вызывает lsh_launch() , чтобы запустить процесс.

Собираем все вместе

Вот и весь код, который входит в командную оболочку. Если вы внимательно читали статью, то должны были понять, как работает оболочка. Чтобы испробовать оболочку (на Linux), вам нужно скопировать эти сегменты кода в файл main.c и скомпилировать его. Обязательно включите только одну реализацию lsh_read_line() . Вам нужно будет включить следующие заголовочные файлы:

  • #include
    • waitpid() и связанные макросы
  • #include
    • chdir()
    • fork()
    • exec()
    • pid_t
  • #include
    • malloc()
    • realloc()
    • free()
    • exit()
    • execvp()
    • EXIT_SUCCESS , EXIT_FAILURE
  • #include
    • fprintf()
    • printf()
    • stderr
    • getchar()
    • perror()
  • #include
    • strcmp()
    • strtok()

Чтобы скомпилировать файл, введите в терминале gcc -o main main.c , а затем./main , чтобы запустить.

Кроме того, все исходники доступны на GitHub .

Подводя итоги

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

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

Чтобы разобраться в системных вызовах, рекомендуем обратиться к мануалу: man 3p . Если вы не знаете, какой интерфейс вам предлагают стандартная библиотека C и Unix, советуем посмотреть спецификацию POSIX , в частности раздел 13.

  • Tutorial

Зачем и для кого статья?

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

Здесь не будет пересказа манов (документации), и статья никак не отменяет и не заменяет их чтение. Вместо этого я расскажу о главных вещах (командах, приемах и принципах), которые надо осознать с самого начала работы в unix shell-е, чтобы работа происходила эффективно и приятно.

Статья касается полноценных unix-подобных окружений, с полнофункциональным шеллом (предпочтительно zsh или bash)и достаточно широким набором стандартных программ.

Что такое шелл

Shell (шелл, он же «командная строка», он же CLI, он же «консоль», он же «терминал», он же «черное окошко с белыми буковками») -- это текстовый интерфейс общения с операционной системой (ну, строго говря, это программа , которая таковой интерфейс обеспечивает, но сейчас это различие несущественно).

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

Типичный вид шелла:

Шелл - это основной способ для взаимодействия со всеми Unix-подобными серверными системами.

Где встречаются системы с командной строкой?

Где вас может поджидать unix-овый шелл, популярные варианты:
  • MacOS (bash);
  • удаленный доступ на сервер по работе или для личного веб-проекта;
  • домашний файл-сервер с удаленным доступом;
  • Ubuntu, PC-BSD на ноутбуке/десктопе - unix-подобные системы сегодня просты в установке и использовании.

Какие задачи разумно решать шеллом?

Естественные задачи, для которых шелл пригоден, полезен и незаменим:
  • интерактивная работа в терминале:
    • выполнение компиляции, запуск заданий через make;
    • сравнение текстовых файлов;
    • быстрый ad-hoc анализ данных (количество уникальных ip в логе, распределение записей по часам/минутам и т.п.);
    • разовые массовые действия (прибить много процессов; если работаете с системой контроля версий - ревертнуть или зарезолвить кучу файлов);
    • диагностика происходящего в системе (семафоры, локи, процессы, дескрипторы, место на диске и т.п.);
  • скриптование:
    • установочные скрипты, для выполнения которых нельзя рассчитывать на наличие других интерпретаторов - это не для новичков;
    • функции для кастомизации интерактивного шелла (влияющие на приглашение, меняющие каталог, устанавливающие переменные окружения) - тоже не совсем для новичков;
    • одноразовые скрипты типа массового перекодирования файлов;
    • makefile-ы.

Абсолютно первые шаги

Начинаем работу: войти и выйти

Убедитесь, что точно знаете, как запустить шелл и как из него выйти.

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

На MacOS - тоже запустить Terminal.

Для доступа к удаленному серверу - воспользоваться ssh (если локально у вас MacOS, Ubuntu или другая unix-like система) или putty (если у вас Windows).

Кто я, где я?

Выполните следующие команды:
  • hostname - выводит имя машины (сервера), на которой вы сейчас находитесь;
  • whoami - выводит ваш логин (ваше имя в системе);
  • tree -d / |less - псевдографическое изображение дерева каталогов на машине; выход из пролистывания - q ;
  • pwd - выводит каталог, в котором вы сейчас находитесь; в командной строке вы не можете быть «просто так», вы обязательно находитесь в каком-то каталоге (=текущий каталог, рабочий каталог). Вероятно, текущий рабочий каталог выводится у вас в приглашении (prompt).
  • ls - список файлов в текущем каталоге; ls /home - список файлов в указанном каталоге;

История команд (history)

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

Выполните несколько команд: hostname , ls , pwd , whoami . Теперь нажмите клавишу «вверх». В строке ввода появилась предыдущая команда. Клавишами «вверх» и «вниз» можно перемещаться вперед и назад по истории. Когда долистаете до hostname , нажмите Enter - команда выполнится еще раз.

Команды из истории можно не просто выполнять повторно, а еще и редактировать. Долистайте историю до команды ls , добавьте к ней ключ -l (получилось ls -l , перед минусом пробел есть, а после - нет). Нажмите Enter - выполнится модифицированная команда.

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

Copy-paste

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

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

Попробуйте выполнить команду date +"%y-%m-%d, %A"
Вводили ли вы ее целиком руками или скопировали из статьи? Убедитесь, что вы можете ее скопировать, вставить в терминал и выполнить.

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

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

Ключи и опции

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

Akira@latitude-e7240: ~/shell-survival-quide> ls Makefile shell-first-steps.md shell-first-steps.pdf shell-survival-quide.md shell-survival-quide.pdf
Если же добавить ключ -l , к каждому файлу выводится подробная информация:

Akira@latitude-e7240: ~/shell-survival-quide> ls -l total 332 -rw-rw-r-- 1 akira akira 198 Feb 13 11:48 Makefile -rw-rw-r-- 1 akira akira 15107 Feb 14 22:26 shell-first-steps.md -rw-rw-r-- 1 akira akira 146226 Feb 13 11:49 shell-first-steps.pdf -rw-rw-r-- 1 akira akira 16626 Feb 13 11:45 shell-survival-quide.md -rw-rw-r-- 1 akira akira 146203 Feb 13 11:35 shell-survival-quide.pdf
Это очень типичная ситуация: если к вызову команды добавлять специальные модификаторы (ключи, опции, параметры), поведение команды меняется. Сравните: tree / и tree -d / , hostname и hostname -f .

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

Ls -ld /home ls -l /home grep root /etc/passwd

man

man - справка по командам и программам, доступным на вашей машине, а также по системным вызовам и стандартной библиотеке C.

Попробуйте: man grep , man atoi , man chdir , man man .

Пролистывание вперед и назад делается кнопками «вверх», «вниз», «PageUp», «PageDown», выход из просмотра справки - кнопкой q . Поиск определенного текста в справочной статье: нажимите / (прямой слеш), введите текст для поиска, нажимите Enter. Перемещение к следующим вхождениям - клавиша n .

Все справочные статьи делятся на категории. Самые важные:

  • 1 - исполняемые программы и шелльные команды (wc , ls , pwd и т.п.);
  • 2 - системные вызовы (fork , dup2 и т.п.)
  • 3 - библиотечные функции (printf , scanf , cos , exec).
Указывать, из какой именно категории надо показать справку, нужно в случаях совпадений имен. Например, man 3 printf описывает функцию из стандартной библиотеки C, а man 1 printf - консольную программу с таким же именем.

Посмотреть список всех доступных на машине справочных статей можно с помощью команды man -k . (точка - тоже часть комады).

less

Когда в небольшом окне терминала надо просмотреть очень длинный текст (содержимое какого-то файла, длинный man и т.п.), используют специальные программы-«пейджеры» (от слова page/страница, то есть постраничные листатели). Самый популярный листатель - less , и именно он обеспечивает вам пролистывание, когда вы читаете man-ы.

Попробуйте и сравните поведение:

Cat /etc/bash.bashrc cat /etc/bash.bashrc |less

Можно передать файл в пролистыватель сразу в параметрах:

Less /etc/bash.bashrc

Пролистывание вверхи и вниз - кнопки «вверх», «вниз», «PageUp», «PageDown», выход - кнопка q . Поиск определенного текста: нажимите / (прямой слеш), введите текст для поиска, нажимите Enter. Перемещение к следующим вхождениям - клавиша n . (Узнаете инструкцию про man ? Ничего удивительного, для вывода справки тоже используется less .)

Права

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

Посмотреть права на файл можно с помощью ls -l . Например:

> ls -l Makefile -rw-r--r-- 1 akira students 198 Feb 13 11:48 Makefile
Этот вывод означает, что владельцу (akira) можно читать и писать файл, группе (students) - только читать, всем прочим пользователя - тоже только читать.

Если при работе вы получаете сообщение permission denied , это значит, что у вас недостаточно правна объект, с которым вы хотели работать.

Подробнее читайте в man chmod .

STDIN, STDOUT, конвейеры (пайпы)

С каждой исполняющейся программой связаны 3 стандартных потока данных: поток входных данных STDIN , поток выходных данных STDOUT , поток для вывода ошибок STDERR .

Запустите программу wc , введите текст Good day today , нажмите Enter, введтие текст good day , нажмите Enter, нажмите Ctrl+d. Программа wc покажет статистику по количеству букв, слов и строк в вашем тексте и завершится:

> wc good day today good day 2 5 24
В данном случае вы подали в STDIN программы двухстрочный текст, а в STDOUT получили три числа.

Теперь запустите команду head -n3 /etc/passwd , должно получиться примерно так:

> head -n3 /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin
В этом случае программа head ничего не читала из STDIN , а в STDOUT написала три строки.

Можно представить себе так: программа - это труба, в которую втекает STDIN , а вытекает STDOUT .

Важнейшее свойство юниксовой командной строки состоит в том, что программы-«трубы» можно соединять между собой: выход (STDOUT) одной программы передавать в качестве входных данных (STDIN) другой программе.

Такая конструкция из соединенных программ называется по-английски pipe (труба), по-русски - конвейер или пайп.

Объединение программ в конвейер делается символом | (вертикальная черта)

Выполните команду head -n3 /etc/passwd |wc , получится примерно следующее:

> head -n3 /etc/passwd |wc 3 3 117
Произошло вот что: программа head выдала в STDOUT три строки текста, которые сразу же попали на вход программе wc , которая в свою очередь подсчитала количество символов, слов и строк в полученном тексте.

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

> head -n3 /etc/passwd |wc |wc 1 3 24

Составление конвейеров (пайпов) - очень частое дело при работе в командной строке. Пример того, как это делается на практике, читайте в разделе «Составление конвейера-однострочника».

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

Вывод (STDOUT) програмы можно не только передать другой программе по конвейеру, но и просто записать в файл. Такое перенаправление делается с помощью > (знак «больше»):

Date > /tmp/today.txt
В результате выполнения этой команды на диске появится файл /tmp/today.txt . Посмотрите его содержимое с помощью cat /tmp/today.txt

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

Если надо не перезаписать файл, а добавить вывод в его конец, используйте >> :

Date >> /tmp/today.txt
Проверьте, что теперь записано в файле.

Кроме того, программе можно вместо STDIN передать любой файл. Попробуйте:

Wc

Что делать, когда что-то непонятно

Если вы сталкиваетесь с поведением системы, которое не понимаете, или хотите добиться определенного результата, но не знаете, как именно, советую действовать в следующем порядке (кстати, это относится не только к шеллам):
  • насколько возможно четко сформулируйте вопрос или задачу - нет ничего сложнее, чем решать «то, не знаю что»;
  • вспомните, сталкивались ли вы уже с такой же или подобной проблемой - в этом случае стоит попробовать решение, которое сработало в прошлый раз;
  • почитайте подходящие man-ы (если понимаете, какие man-ы подходят в вашем случае) - возможно, вы найдете подходящие примеры использования команд, нужные опции или ссылки на другие команды;
  • подумайте: нельзя ли немного поменять задачу? - возможно, чуть-чуть изменив условия, вы получите задачу, которую уже умеете решать;
  • задайте свой четко сформулированный вопрос в поисковой системе - возможно, ответ найдется на Stack Overflow или других сайтах;
Если ничего из перечисленного не помогло - обратитесь за советом к преподавателю, опытному коллеге или товарищу. И не бойтесь задавать «глупые» вопросы - не стыдно не знать, стыдно не спрашивать.

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

Методы работы

Скопировать-и-вставить - из man-ов, из статей на StackOverflow и т.п.Командная строка состоит из текста, пользуйтесь этим: копируйте и используйте примеры команд,записывайте удачные находки на память, публикуйте их в твиттерах и блогах.

Вытащить из истории предыдущую команду, добавить в конвейер еще одну команду, запустить, повторить .См. также раздел «Составление конвейера-однострочника».

Базовые команды

  • переход в другой каталог: cd ;
  • просмотр содержимого файлов: саt , less , head , tail ;
  • манипуляции с файлами: cp , mv , rm ;
  • просмотр содержимого каталогов: ls , ls -l , ls -lS ;
  • структура каталогов: tree , tree -d (можно передать в качестве параметра каталог);
  • поиск файлов: find . -name ... ;

Аналитика

  • wc , wc -l ;
  • sort -k - сортировка по указанному полю;
  • sort -n - числовая соритровка;
  • diff - сравнение файлов;
  • grep , grep -v , grep -w , grep "\" , grep -E - поиск текста;
  • uniq , uniq -c - уникализация строк;
  • awk - в варианте awk "{print $1}" , чтобы оставить только первое поле из каждой строки, $1 можно менять на $2 , $3 и т.д.;

Диагностика системы

  • ps axuww - информация о процессах (запущенных программах), работающих на машине;
  • top - интерактивный просмотр самых ресурсоемких процессов;
  • df - занятое и свободное место на диске;
  • du - суммарный размер файлов в каталоге (рекурсивно с подкаталогами);
  • strace , ktrace - какие системные вызовы выполняет процесс;
  • lsof - какие файлы использует процесс;
  • netstat -na , netstat -nap - какие порты и сокеты открыты в системе.

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

Массовое и полуавтоматическое выполнение

На первых порах пропускайте этот раздел, эти команды и конструкции понадобятся вам тогда, когда доберетесь до несложного шелльного скриптинга.
  • test - проврека условий;
  • while read - цикл по строчкам STDIN ;
  • xargs - подстановка строк из STDIN в параметры указанной программе;
  • seq - генерация последовательностей натуральных чисел;
  • () - объединить вывод нескольких команд;
  • ; - выполнить одно за другим;
  • && - выполнить при условии успешного завершения первой команды;
  • || - выполнить при условии неудачного завершения первой команды;
  • tee - продублировать вывод программы в STDOUT и в файл на диске.

Разное

  • date - текущая дата;
  • curl - скачивает документ по указаному url и пишет результат на STDOUT ;
  • touch - обновить дату модификации файла;
  • kill - послать процессу сигнал;
  • true - ничего не делает, возвращает истину, полезна для организации вечных циклов;
  • sudo - выполнить команду от имени root "а.

Составление конвейера-однострочника

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

Шаг 1.
Понять, какая программа выдает примерно нужные данные, хотя бы и не в чистом виде. Для нашей задачи стоит получить список всех процессов в системе: ps axuww . Запустить.

Шаг 2.
Посмотреть на полученные данные глазами, придумать фильтр, который выкинет часть ненужных данных. Часто это grep или grep -v . Клавишей «Вверх» вытащить из истории предыдущую команду, приписать к ней придуманный фильтр, запустить.

Ps axuww |grep `whoami`
- только процессы текущего пользователя.

Шаг 3.
Повторять пункт 2, пока не получатся чистые нужные данные.

"
- все процессы с нужным именем (плюс, может быть, лишние вроде vim task-6-server.c и т.п.),

Ps axuww |grep `whoami` | grep "\" | grep -v vim ps axuww |grep `whoami` | grep "\" | grep -v vim |grep -v less
- только процессы с нужным именем

Ps axuww |grep `whoami` | grep "\" | grep -v vim |grep -v less |awk "{print $2}"

Pid-ы нужных процессов, п. 3 выполнен

Шаг 4.
Применить подходящий финальный обработчик. Клавишей «Вверх» вытаскиваем из истории предыдущую команду и добавляем обработку, которая завершит решение задачи:

  • |wc -l чтобы посчитать количество процессов;
  • >pids чтобы записать pid-ы в файл;
  • |xargs kill -9 убить процессы.

Задания для тренировки

Хотите попрактиковаться в новых умениях? Попробуйте выполнить следующие задания:
  • получите список всех файлов и каталогов в вашем домашнем каталоге;
  • получите список всех man -статей из категории 2 (системные вызовы);
  • посчитайте, сколько раз в man-е по программе grep встречается слово grep;
  • посчитайте, сколько процессов запущено в данный момент от имени пользователя root ;
  • найдите, какая команда встречается в максимальном количестве категорий справки (man);
  • подсчитайте, сколько раз встречается слово var на странице ya.ru .
Подсказка: вам понадобится find , grep -o , awk "{print $1}" , регулярные выражения в grep , curl -s .

Что изучать дальше?

Если командная строка начинает вам нравиться, не останавливайтесь, продолжайте совершенствовать свои навыки.

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

  • find со сложными опциями
  • apropos
  • locate
  • telnet
  • netcat
  • tcpdump
  • rsync
  • screen
  • zgrep , zless
  • visudo
  • crontab -e
  • sendmail
Кроме того, со временем стоит освоить какой-нибудь скриптовый язык,например, perl или python , или даже их оба.

Кому это надо?

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

В данном разделе предоставлена документация в помощь программисту на языках командных оболочек shell (sh, bash, ksh и другие)

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

0. Введение

Командный интерпретатор в среде UNIX выполняет две основные функции:

представляет интерактивный интерфейс с пользователем, т.е. выдает приглашение, и обрабатывает вводимые пользователем команды;
обрабатывает и исполняет текстовые файлы, содержащие команды интерпретатора (командные файлы);

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

В среде UNIX (в отличие, скажем, от DOS) имеются несколько различных командных интерпретаторов. Перечислим наиболее популярные:

/bin/sh - Bourne shell. Исторически это первая командная оболочка, разработанная для первой версии ОС UNIX. В настоящее время эта оболочка является основной в версиях UNIX System V.
/bin/csh - С-shell. Оболочка, синтаксис командного языка которой приближен к языку C. Является основной оболочкой для Берклеевской разновидности ОС UNIX.
/bin/ksh - k-shell.
/bin/rsh - Restricted shell. Представляет собой sh с ограниченными возможностями (прежде всего для защиты ОС от несанкционированных действий пользователя).

Операционная система ConvexOS является разновидностью 4.3 BSD UNIX()BSD - Berkeley Series Distribution и, следовательно, базовой командной оболочкой является csh.

1. Основные возможности

Работа с командной строкой

Набираемую пользователем строку интерпретатор воспринимает как команду (или несколько команд). Синтаксис командного интерпретатора позволяет набирать

несколько команд в одной строке, разделяя их точкой с запятой. Например

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

Наоборот, при желании пользователь может

продолжить набор длинной команды на следующей строке, закончив текущую строку знаком \\. До завершения ввода команды вы будете получать ``вторичное приглашение"" > вместо основного (%). Например,

% tar tv Makefile star.o star.c star.dat main.o main.c

эквивалентно

% tar tv Makefile star.o \

> star.c star.dat \

Управление потоками ввода-вывода осуществляется, подобно DOS(Точнее, синтаксис перенаправления потоков ОС DOS восприняла от UNIX) с помощью символов > , > > ,

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

создаст в текущей директории пустой файл myfile.

Дополнительно C-shell позволяет группировать команды с помощью круглых скобок. В этом случае вся конструкция внутри скобок рассматривается интерпретатором как одна команда. Сие полезно, например, в таких конструкциях:

% (command1 | command2)

Если же скобки опустить, shell не сможет определить какой из команд вы хотите подать на вход файл myfile.

Следующие ``удобства"" существуют в данной реализации C-shell:

Вы можете не набирать длинную команду до конца, а попробовать после частичного набора команды (или имени файла) нажать клавишу табуляции. C-shell попытается сама дополнить недостающие символы, либо ответит писком, если выбор неоднозначен.
Если вы набрали команду, но забыли ее опции, наберите последовательность H. C-shell выдаст краткую помощь. Например,

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

Разбор командной строки

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

Раскрывает псевдонимы (alias)
Раскрывает метасимволы (*, ?, [, ], ~, {, })
Подставляет переменные shell
Выполняет команду, если она - встроенная команда интерпретатора, или запускает процесс, если команда внешняя.

Разберем эти действия по этапам.

Псевдонимы (alias). Встроенная команда alias позволяет определять псевдонимы команд. Пример:

% alias mycat "cat | more"

определяет mycat как псевдоним строки cat | more. Поэтому далее вы вправе пользоваться командой mycat, которая будет раскрыта интерпретатором везде, где вы ее используйте. Это - способ определения коротких имен для длинных составных команд.

Встроенная команда unalias mycat уничтожает ранее введенный псевдоним mycat.

Метасимволы. Метасимволы позволяют кратко записывать целые списки слов (главным образом - имен файлов). Shell рассматривает слово, в котором встречаются метасимволы, как шаблон для составления списка имен файлов:

* в шаблоне заменяет любую последовательность символов. Например m* раскроется в список всех файлов, начинающихся с буквы m. Существует небольшое исключение из этого правила: просто * опускает в списке те файлы, имена которых начинаются с точки.
? заменяет один символов. Например m? раскроется в список всех имен файлов, начинающихся с буквы m и состоящих точно из двух букв.
[.-.] позволяет указать интервал для подставляемого символа. Например m будет раскрыто в ma mb mc me.
{...,...} позволяет перечислить слова для подстановки. Так, например m{red,blue,green} будет раскрыто в mred mblue mgreen.

Наконец, тильда позволяет указать домашний каталог пользователя:

~name/ эквивалентно указанию полного пути в домашний каталог пользователя name (Скажем, /usr1/name/)
~/ эквивалентно указанию полного пути в ваш собственный домашний каталог.

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

Встроенная команда set name=value позволяет определить простую переменную с именем name и дать ей значение value. Встретив в командной строке выражение $name интерпретатор заменит его на value. Например,

% set color=blue

выдаст на терминал строчку blue. А

% set color=blue

% echo new$color

даст newblue. Наконец, введя

% set color=blue

% echo ${color}new

получим colornew. Последний пример демонстрирует как надо использовать фигурные скобки для выделения имени переменной из слова (на echo $colornew интерпретатор бы ответил, что переменная colornew не определена.

Команда unset уничтожает ранее определенные переменные.

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

% set color="blue or red or green"

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

% set colors=(blue red green)

Теперь команда echo $colors выдаст строку из трех цветов (попробуйте!). Однако вы можете также работать в отдельными элементами массива (элементы нумеруются с нулевого значения), например так:

(получим green). Количество элементов в массиве содержится в переменной $#colors.

даст на терминал цифру 3.

Возможны довольно сложные комбинации с использованием шаблонов, например:

% set files=(m*)

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

Переменные окружения вызываются точно также как и простые переменные. Разница заключается в способе их определения:

Команда % setenv name value устанавливает переменную окружения с именем name. Обратите внимание на раздражающую разницу в синтаксисе: определяя переменную окружения не надо ставить знак =.

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

Отменить определения переменной окружения можно с помощью unsetenv.

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

% setenv MANPATH /usr/man/:/usr/local/man:/usr/man/X11:~/man

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

Список важнейших встроенных команд C-shell с краткими пояснениями:

alias определяет псевдоним

bg переводит задачу в фоновый режим исполнения

chdir path команда перехода в каталог path.

echo выводит на стандартный вывод все свои аргументы

exec filename запускает процесс из файла filename вместо текущей shell (т.е. поверх нее). Возврат в shell невозможен.

exit заканчивает работу shell.

fg переводит фоновый процесс в синхронный.

file filename выдает информацию о том, что операционная система думает об этом файле.

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

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

source filename считывает и исполняет команды из файла filename.

set, setenv установка внутренних переменных и переменных окружения.

shift var сдвигает элементы массива var влево. При этом размер массива уменьшается на единицу, а нулевой элемент массива теряется. Переменная var должна быть массивом.

time command выполняет команду command и выводит на терминал затраченное на ее выполнение время.

unset уничтожает переменную shell.

unalias уничтожает ранее определенный псевдоним команды.

@ name=expr заносит результат арифметического выражения expr в переменную name.

Список важнейших встроенных переменных C-shell с краткими пояснениями:

argv массив параметров командной строки (используется в командном режиме)

cdpath каталог, куда shell переходит, получив команду chdir без аргумента.

history размер буфера для запоминания команд.

home домашний каталог пользователя

mail местоположение в файловой системе почтового ящика пользователя.

path путь поиска внешних команд.

prompt основное приглашение shell.

prompt1 вторичное приглашение.

shell полный путь исполняемого файла текущей оболочки (/bin/csh)

Управляющие операторы и операторы цикла

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

Условное выполнение

Синтаксис условного оператора if в C-shell таков

if (expr) command

В качестве expr может стоять либо арифметическое выражение, либо проверка атрибутов файла. Пример:

if (-f /etc/hosts) cat /etc/hosts

Рассмотрим последний случай подробнее. Возможны следующие проверки атрибутов файла:

R доступен на чтение

W доступен на запись

X доступен на исполнение

E проверка существования файла

O проверка что вы являетесь хозяином данного файла

Z файл имеет нулевой размер

F файл является обычным файлом

P файл является именованным программным каналом

D файл является директорией

Цикл while

Цикл выполняется до тех пор, пока условие истинно. Пример:

while ($#files > 0)

Цикл foreach

Это чрезвычайно полезный оператор, позволяющий организовать цикл по элементам массива слов

foreach varname (list)

Тело цикла выполняется столько раз, сколько элементов в массиве list. При этом переменная varname содержит очередное значение элемента массива. Пример

foreach color (blue red green)

echo The color is $color

foreach file (*.for)

echo Renaming $file

mv $file `basename $file .for`.f

Здесь использована стандартная команда basename, которая ``отрезает"" у слова, заданного в первом аргументе суффикс, заданный вторым аргументом и выводит получившееся слово на стандартный вывод. Об использовании обратных кавычек в языке C-shell будет рассказано несколько позже.

Многовариантный условный оператор

Синтаксис условного оператора switch в C-shell таков

case pattern1: ... breaksw case {\it pattern2} :

Оператор позволяет передавать управление в зависимости от того, удовлетворяет ли строка string какому-либо шаблону из набора pattern1, pattern2, ...(в этом случае управление передается в блок, ограниченный case ... breaksw) или нет (в этом случае управление передается на ветвь default:... endsw. В целом, оператор switch очень похож на аналогичный опреатор языка C. Такие конструкции часто используются в командных файлах для анализа ответа пользователя на заданный вопрос ().

2. Работа оболочки в командном режиме

Уже отмечалось, что csh может быть запущена в командном режиме. Более того, условные операторы и операторы цикла чаще используются именно в командных файлах. Здесь мы рассмотрим особенности такой ``командной"" работы.

Идентификация интерпретатора

Проще всего осуществить запуск оболочки в режиме исполнения некоторого файла mycommand придав этому файлу атрибут исполняемости командой chmod:

% chmod +x mycommand

Теперь достаточно ввести с клавиатуры команду mycommand и ОС автоматически запустит shell в командном режиме исполнения данного файла. В таком пути есть один подводный камень: командных интерпретаторов в системе много и синтаксис команд у них разный. Как ОС определит нужный вам? Ответ - никак. Вы должны явно указать ОС какой интерпретатор вы хотите запускать для исполнения данного командного файла. Для этого первая строчка вашего файла должна иметь следующий стандартный вид:

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

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

Следующий факт позволяет вам работать с командной строкой средствами csh: при запуске командного файла mycommand автоматически становится определенной внутренняя переменная с именем argv, представляющая массив параметров командной строки. Например, следующий командный файл просто выводит все свои аргументы и их количество на терминал:

# This file simply outputs its arguments

# and the total number of arguments

echo Arguments: $argv

echo Number of arguments: $#argv

Явный запуск

Вы можете применить более прямой, но менее удобный способ запуска командного файла - вызвав shell с ключом -c filename. Пример:

% /bin/csh -c mycommand arg1 arg2 arg3...

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

Кавычки

В синтаксисе shell кавычки играют важную роль. Есть три типа кавычек: простые ("), двойные (") и обратные (`).

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

echo "Dollar is $good"

получим букально Dollar is $good несмотря на то, что знак доллара является метасимволом оболочки.

Двойные кавычки выделяют строку символов, которую оболочка будет считать одним словом. Пример:

set colors="green blue red"; echo $#colors

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

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

занесет в переменную mytty ту строку, которую выдает команда tty (а именно имя и номер текущего терминала).