Интерпретаторы
командной строки. Shell
Отступление
на тему языка С. Прямой ввод/вывод. Дескрипторы файлов
Перенаправление
потоков в BASH. Понятие
конвейра
Подстановка
текста из стандартного потока вывода в командную строку
Различные
стандартные обозначения и расширения
Командные
файлы. Параметры командных файлов
Б.В.Кениган, Р.Пайк. UNIX --- универсальная среда программирования. Финансы и статистика. Москва
92. Джеймс Армстронг (мл.). Секреты UNIX. Диалектика. Киев 96. Сергей Дунаев. UNIX. System V. Release 4.2. Диалог МИФИ, Москва 95г. UNIX. X Window. Motif. ОСновы программирования (части I, II) АО Аналитик. Москва 94. |
Для поиска файлов существует целый ряд программ:
find
ключи:
-name
-iname --- ignore case
-maxdepth n --- n=макс.вложенность
поддиректорий (1=только тек.)
-maxdepth 1 --- n=мин.вложенность поддиректорий (1=exceptтолько тек.)
-mount (иногда -xdev) --- не исп.др.файловые системы
-atime n --- последний доступ не
более n*24 часов
(access)
-ctime n --- последнее изменение
статуса не более n*24
часов (change)
-mtime n --- последнее изменение не
более n*24 часов
(change)
-atime +n --- последний доступ не
менее n*24 часов
(access)
-ctime +n --- последнее изменение статуса
не менее n*24
часов (change)
-mtime +n --- последнее изменение не
менее n*24 часов
(change)
-inum n --- inode=n
-regex expr --- рег. выр. на весь
путь
-iregex expr --- рег. выр. на весь
путь; case не важен
-exec expr; --- `{}' = найденный файл команда expr выполняется для каждого найденного
файла. Например
find . –name ’*.tmp’ –exec rm ’{}’ \;
whereis filename PATH ищет файл filename в
стандартных папках (можно задавать имя без расширения)
locate, updatedb locate ищет файл в базе данных, обновляемой командой updatedb; по умолчанию ищется имя файла, содержащее заданное имя; можно задавать маску на полное (!) имя файла
which filename ищет файл filename в списке папок переменной PATH
Семейство
Bourne Shell: bsh, bash
C-Shell: csh
Korn Shell:
ksh
Shell существует для обеспечения ввода команд в режиме работы с командной строкой и для обеспечения выполнения командных файлов. Для запуска командного файла запускается отдельная версия интерпретатора командной строки, который, собственно, и обеспечивает последовательное выполнение строк командного файла. При этом по умолчанию запускает системный Shell (обычно в UNIX он называется sh, в Linux вместо него используется bash). Это поведение можно изменить, записав в первой строке командного файла
#!ShellName
где ShellName – имя интерпретатора командной строки, в котором должен выполняться командный файл (обязательно написание полного имени).
Для понимания работы Shell следует иметь представление о следующих понятиях, которые имеют представление в языке С:
· Дескрипторы ввода/вывода
· Потоки ввода/вывода
· Переменные среды
Shell в интерактивном режиме работы
может выступать в роли login-shell (если он запускается
командой login)
или non-login (если он запущен из
другого Shell).
Bash является стандартным
интерпретатором командной строки, использующимся в ОС Linux.
Если Bash является login-shell, то при его запуске выполняются файлы /etc/profile[, ~/.bashrc] (различные реализации могут иметь неслько разное поведение), если они существуют, и выполняется первый из существующих файлов из следующих:
~/.bash_profile
~/.bash_login
~/.profile
Здесь под ~ подразумевается домашняя папка пользователя.
При завершении
его работы выполняется командный файл ~/.bash_logout .
При начале работы non-login shell выполняются файлы [/etc/profile,] ~/.bashrc , если они существуют.
Отметим, что указанные командные файлы выполняются в рамках данного Shell.
Любая команда, вводимая в строке, воспринимается интерпретатором как набор слов, разделенных пробелами. Если необходимо включить пробелы в слово, то кусок текста можно объединить в слово путем заключения его в одинарные или двойные кавычки. Если первое слово в строке является командой интерпретатора, то строка обрабатывается соответствующим образом. Иначе, первое слово считается именем задачи.
Задача может быть или выполняемым файлом или командным файлом. В первом случае задача создавалась с помощью соответствующего компилятора и сборщика (линковщика), а во втором – задача является обычным текстовым файлом со списком команд, которые требуется выполнить. В любом случае для выполнения задачи необходимо, чтобы данный пользователь имел право на выполнение данного файла.
Имя задачи может
быть полным или неполным. При указании полного имени задачи следует обозначить
полный путь к файлу задачи. Это можно сделать одним из следующих способов:
·
Указать полный путь от корневой папки; при этом
полное имя задачи должно начинаться с символа `/’
· Указать относительный путь; при этом имя задачи состоит из имен папок, разделенных символами `/’ (в начале стоит имя папки, расположенной в текущей папке, т.е. символа `/’ в начале имени нет). Отметим, что в каждой папке есть ссылка на текущую папку с именем `.’ и ссылка на папку предыдущего уровня с именем `..’ .
·
Указать неполное имя, состоящее только из,
собственно, имени задачи без имен папок. В этом случае задача с данным именем
последовательно ищется во всех папках, указанных в переменной среды PATH
(папки в этой переменной разделены двоеточиями). Выполняется первая найденная
задача. Отметим, что автоматически в текущей папке поиск не производится, поэтому
для выполнения задачи из текущей папки следует указывать, например, ее
относительное имя: ./Taskname .
В ОС UNIX существуют переменные
среды (это свойство каждой программы, а не конкретного Shell; т.е. это свойство заложено в
самой ОС UNIX).
В bash переменную среды можно
задать простым присваиванием
Var=value
При этом, если
переменная не существовала, то она автоматически создается. Уничтожить переменную
можно командой unset :
unset Var
В дальнейшем переменную можно использовать в виде
$Var или ${Var}
Существуют
различные варианты расширенного использования имен переменных среды. Приведем
примеры
${Var:offset} подстрока переменной Var, начиная с позиции
offset (индексация с 0)
${Var:offset:length} подстрока
переменной Var, начиная с
позиции offset длиной length
${#Var } длина строки переменной Var
${Var#pattern} если начало строки
переменной Var соответствует
шаблону pattern, то результатом
подстановки является строка Var с отброшенной минимальной начальной частью строки, удовлетворяющей
шаблону pattern. Например:
X=file.ext
echo ${X#*.}
На экран будет выведено ext
${Var##pattern} то же, но
отбрасывается максимальная начальная часть строки, если начало строки,
удовлетворяющей шаблону pattern.
${Var%pattern}, ${Var%%pattern} то же, но
сравнение и отбрасывание происходит с конца строки. Например:
X=file.ext
echo ${X%.*}
На экран будет выведено file
${Var/pattern/string}, ${Var//pattern/string} заменить в строке Var максимальное
вхождение, отвечающее шаблону pattern на string. В первом случае – один раз, во втором – все вхождения. Например:
X=’bad file name.c’
echo ${X// /_}
На экран будет выведено bad_ file_name.c
Переменные среды
автоматически не передаются в программы, запущенные из данного shell. Это происходит только если переменная объявлена как export:
export Var
export Var=Value
Сделать переменную
Var неэкспортируемой можно с помощью команды
export -n Var
С переменами среды
из яхыка C можно работать с помощью функций
char *getenv( const
char *varname );
int putenv( const char
*envstring );
В Microsoft C:
main( int argc, char *argv[
], char *envp[ ] );
В gcc функции getenv/putenv модифицируют значения переменной
extern char **environ;
Данный массив указателей
терминируется нулевым указателем.
В Microsoft C функции _getenv/_putenv модифицируют значения переменной
extern char
**_environ;
Шаблоны используются для подстановки имен файлов, отвечающих шаблону, вместо шаблона. Shell при обработке командной строки ищет в ней все шаблоны, подставляет вместо них соответствующие имена файлов в виде списка файлов, разделенных пробелом и обрабатывает соответствующую строку далее. По умолчанию скрытые файлы не участвуют в подстановке. Они подставляются только в случае, когда точка явно указывается в начале имени файла.
В шаблонах используются следующие специальные символы
* любая последовательность символов (включая пустую последовательность)
? ровно один любой символ
[…] один из символов, набранных вместо `…’; можно задавать диапазон символов: [a-zA-Z] – соответствует одной букве английского алфавита; символ `~’ в первой позиции обозначает инвертирование: [~0-9] – соответствует одной не цифре.
Например:
cp * Dir
Получив эту команду, Shell подставляет вместо символа `*’ все имена файлов из текущей папки, кроме скрытых. Имена файлов, при этом, разделяются пробелами. Если таковых файлов не найдено, то шаблон используется без изменений. Далее происходит попытка скопировать все файлы, подставленные вместо символа `*’, в папку Dir. Если Dir не является именем папки и, при этом, подставлен более чем один файл, то выдается сообщение об ошибке. Если среди файлов встретились папки, то они игнорируются с соответствующим сообщением об этом (заметим, что, т.к. папка Dir находится в текущей папке, то и ее имя появится в списке подставленных файлов).
Следующий пример был бы корректен в MS DOS, т.к. шаблоны там обрабатываются непосредственно программами, к которым они адресуются:
mv *.c *.cpp
По логике, аналог данной записи в MS DOS: ren *.c *.cpp
Однако, в UNIX подобная запись, по вышеупомянутым причинам, смысла иметь не будет.
Если символы
шаблонов необходимо экранировать, то соответствующую запись можно поместить в
одинарные или двойные кавычки, или перед соответствующим символом можно
поставить символ `\’.
Например:
mv ’*.c’ x.c
данная команда переименовывает файл с именем `*.c’ в файл с именем `x.c’.
Двойные и одинарные кавычки имеют различия: двойные кавычки не экранируют подстановку имен переменных (вместо имени переменных подставляется ее значение при наличии знака доллара перед именем переменной) и значений подстановки стандартного потока вывода команды вместо ее вызова (происходит, если команда погружена в обратные кавычки), а одинарные дают полную экранизацию.
Отметим также, что кавычки объединяют все последовательность символов между ними в один параметр командной строки. Так, например, в следующей последовательности с символов с точки зрения bash содержится 3 слова:
1 2’3 4’
’5 6’
это следующие слова:
1
23 4
5 6
Bash дает
возможность ходить по списку выполненных команд клавишами вверх/вниз.
Список последних выполненных команд можно получить командой history. Список команд выводится на стандартный поток вывода, поэтому команды можно отфильтровать соответствующими фильтрами (см. об этом далее).
К уже выполненной
команде можно обратиться с помощью команд, начинающихся на `!’. Имеется в виду,
что команда !... может стоять в любом
месте командной строки.
Команда !! есть синоним предыдущей команды.
!-n вызывает команду с номером –n от текущей,
т.е. !-1 есть синоним !!.
!-n вызывает команду с номером n (список команд с номерами выдается командой history).
!string вызывает последнюю команду, начинающуюся на строку string.
!string вызывает последнюю команду, содержащую на
строку string.
Прямой ввод/вывод в языке С осуществляется набором функций
open
close
read
write
lseek
Если в случае
буферного ввода/вывода в качестве идентификатора файла использовалась
переменная типа FILE*,
то здесь будет использоваться целая переменная, называемая дескриптором
файла. Отметим, что данный дескриптор не имеет никакого отношения к inode, который часто
переводится на русский язык как `дескриптор’.
Функция open имеет одно из следующих описаний
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Функция открывает файл и возвращает дескриптор файла. В случае ошибки возвращается –1.
Возможные
значения поля flags
O_RDONLY
O_WRONLY
O_RDWR
Возможно объединение с помощью логического или со значениями, некоторые из которых приведены ниже:
O_CREAT
O_EXCL может использоваться в качестве семафора
O_TRUNC
O_APPEND
O_NONBLOCK или O_NDELAY
O_SYNC
Поле mode задает
привилегии, присваиваемые файлу.
Функции read/write в целом
аналогичны функциям fread/fwrite в случае буферного
ввода/вывода. Описание:
int read(
int handle, void *buffer, unsigned
int count );
int write
(int handle, void *buffer, unsigned
int count );
Наконец:
int _close(
int handle );
Существует возможность получения дескриптора для имеющего потока ввода/вывода и, наоборот, для дескриптора можно создать поток ввода/вывода, с ним ассоциированный:
int fileno(
FILE *stream );
FILE *fdopen(
int handle, const char *mode );
Для стандартных потоков stdin/stdout/stderr зарезервированы дескрипторы 0/1/2.
Любой вывод, адресуемый в дескриптор n , может быть перенаправлен в файл с именем FileName:
По умолчанию, перенаправляется вывод в дескриптор 1, т.е. перенаправляется стандартный поток вывода.
Ввод из любого дескриптора n может быть перенаправлен для ввода из файла с именем FileName:
По умолчанию, перенаправляется ввод для дескриптора 0, т.е. перенаправляется стандартный поток вывода.
Пример:
#include <stdio.h>
main()
{char str[256]; FILE *f;
f= fdopen(4,"r");
if(f==NULL){printf("Error\n");return
-1;}
while(fgets(str,255,f)!=NULL)printf(“%s”,str);
fclose(f);
return 0;
}
Данная программа c именем Prog может распечатать содержимое файла с именем FileName, если она вызвана следующим образом:
Отметим, что отчасти данная
идеология перенята в ОС MSDOS.
В этой системе данная программа работать не будет (функция fdopen вернет NULL),
но если вместо дескриптора 4 использовать дескриптор 0
(стандартный поток ввода), то она заработает.
Можно
перенаправлять вывод программы в конец существующего файла:
Можно потребовать вводить до некоторого ключевого слова:
Command <<KeyWord
Точнее, ввод осуществляется до
строки, состоящей из одного слова KeyWord.
Данный подход всегда используется
в сложных командных файлах, от которых требуется создание других сложных
файлов. В следующем примере многострочный текст LongText подклеивается
к концу файла OutputFileName :
cat >>OutputFileName <<_END_
LongText
_END_
Строку текста
легко подать на стандартный поток ввода команды с помощью следующей конструкции:
Prog <<<String
Перенаправить
одновременно поток ввода и вывода в один файл можно с помощью следующей конструкции:
Prog <>FileName
Две программы можно запустить одновременно, склеив поток stdout первой программы с потоком stdin второй:
Два потока вывода можно склеить:
n>&m - дескриптор вывода n становится копией m, например
find . -name t.t >ttt 2>&1 – весь вывод переходит в файл ttt
find . -name t.t >ttt 1>&2 – весь вывод идет на экран
find . -name t.t 1>&2 >ttt – запись 1>&2 игнорируется
find . -name t.t 2>&1 >ttt – запись 2>&1 игнорируется
Отметим, что нельзя так просто перенаправлять потоки в конвейере. Например, нельзя так просто склеить потоки stdout и stderr одной программы и передать их на поток stdin второй программы в конвейере. Но можно запустить первую программу в порожденном Shell. Это делается с помощью заключения команды в круглые скобки:
(Prog1 2>&1) | Prog2
Если в текст исходной команды вставить некоторую другую команду com2, заключенную в обратные кавычки, то перед выполнением исходной команды будет выполнена команда com2 и текст из ее потока stdout будет подставлен вместо ее вызова в исходной команде.
Например
echo `ls`
при выполнении
этой команды сначала будет выполнена команда ls. Далее список файлов из
данной папки, извлеченный из ее стандартного потока вывода, будут помещен в
качестве параметров команды echo. В результате на экране будет напечатан список
файлов текущей папки.
Почти эквивалентный синтаксис:
echo $(ls)
~ эквивалентно $HOME, т.е. домашняя папка
~+ эквивалентно $PWD , что содержит имя текущей папки
~- содержит имя предыдущей папки
${var} подставляется значение переменной var
${#var} подставляется длина содержимого переменной var
$[expr] подставляется значение выражения expr, например:
echo $[1 + 1]
команда распечатает на экране:
2
Оператор имеет следующий синтаксис:
for var in values
do
commands
done
Здесь
var имя переменной цикла
values список значений переменной через пробел
commands список команд
В командах можно использовать значение переменной цикла в виде $var.
Цикл можно
набрать и в одной строке:
for var in values ;do commands;
done
Если команд несколько, то их можно разделять символом `;’ .
Например:
for i in *; do echo $i; rm –f $i;
done
Напомним, что ключ –f заставляет команду rm удалять файл без запроса.
Следующий пример понижает регистр имен всех файлов в данной папке
for i in *; do mv $i `echo $i |dd
conv=lcase`;done
Оператор имеет
следующий синтаксис:
if logical
then
commands
fi
Оператор можно набрать и в одной строке:
if logical; then commands; fi
Здесь
logical логическое выражение
commands список команд
Под `логическим выражением’ понимается команда, код возврата которой служит признаком истины или лжи. Считается, что команда вернула значение истина, если код возврата процедуры равен 0 (заметим, что это не согласуется с понятием истины в языке С). Любое другое значение ассоциируется с ложью.
Если вышесказанное соотнести с тем, что при успешном выполнении программы принято использовать код возврата = 0, то получим, что имеет смысл следующая команда:
if prog; then echo OK; else echo
Failed; done
В результате выполнения данной строки на экран будет выведено OK, если команда prog выполнена успешно, иначе будет выведено Failed.
Осталось добавить, что вместо условного оператора можно подставлять выражения вида
[logical expression]
В выражении logical expression допустимо использование строковых операций сравнения:
==
<
>
<=
>=
!=
При их
использовании необходимо не забывать экранировать их от Shell.
Для численного сравнения используются операторы
-eq
-ne
-lt
-le
-gt
-ge
Кроме того,
возможны операции, связанные с существованием файлов. Например:
if [-e FileName]; then echo FileName
exists; fi
данная команда напечатает FileName exists если файл FileName существует.
Оператор имеет следующий синтаксис:
while logical
do
commands
done
Оператор можно набрать и в одной строке:
while logical; do commands; done
Здесь
logical логическое выражение (в том же смысле, что и в операторе if )
commands список команд
Приведем пример команд, создающих файлы с именами 1.tmp, 2.tmp,… , 100.tmp
i=0; while [$i –le 100]; do i=`expr
$i + 1`; echo ’’ >$i.tmp; done
Командный файл представляют собой обычный текстовый файл. При его выполнении его строки передаются на вход интерпретатору командной строки и он их последовательно обрабатывает и выполняет.
Обычно
командный файл выполняется в отдельно вызывающемся для данyого файла интерпретаторе. Например,
если у командного файла установлен атрибут x, то выполнить файл
можно просто с помощью написания его имени. При этом автоматически запускается
интерпретатор командной строки (обычно его имя берется из переменно среды SHELL, но это может быть и текущий shell) и ему на вход подается командный
файл. Задать другой интерпретатор, который должен обрабатывать данный командный
файл, можно с помощью написания его имени в первой строке командного файла
после символов `#!’:
#!/bin/ash
Командный файл
можно вызвать с помощью прямого вызова интерпретатора и передачи ему командного
файла в качестве параметра:
bash CommandFile
Кроме того
имеется возможность исполнения командного файла в текущем shell с помощью
команды `.’ (при этом
его синтаксис может быть ограничен):
. CommandFile
К параметрам командных файлов можно обращаться по номерам (аналогично MSDOS): $1, $2, …, $9. К имени, с помощью которого был вызван командный файл, можно обратиться через параметр $0.
К списку всех параметров можно обратиться через $*.
Если параметров
много, то можно использовать команду shift n, сдвигающую номера параметров
так, чтобы параметр с номером n+1 стал бы иметь номер 1. По умолчанию
происходит сдвиг на одну позицию (n=1).
Приведем пример командного файла, перебирающего все свои параметры и выводящего их на экран:
while [”_$1” != ”_”]
do
echo $1
shift
done
Определение функции:
function FunName() { commands;}
Здесь
FunName имя функции
commands список команд
Обращаться к параметрам можно по номерам: $1, $2 и т.д.
Просмотреть список определений всех функций можно с помощью команды
typeset –f
Например, часто используемый mc умеет при завершении своей работы менять текущую папку на последнюю текущую папку в самом mc. Ясно, что для любой программы текущая папка является параметром только данной программы и сменить ее можно только в рамках данной программы. Для решения этой проблемы пишется функция с именем mc, которая реально вызывается вместо самого mc. Функция имеет примерно следующий вид:
function mc(){/usr/bin/mc >
/tmp/mc.tmp; cd ”`cat /tmp/mc.tmp `”}
Сам mc ответственен за то, чтобы при завершении своей работы выводить имя последней текущей папки в поток stdout.