Книга: UNIX — универсальная среда программирования
5.2 Что представляет собой команда which?
Разделы на этой странице:
5.2 Что представляет собой команда which
?
При обзаведении собственными версиями команд, аналогичных cal
, возникает ряд трудностей. В частности, когда вы работаете как пользователь Мэри и вошли в систему под именем mary
, то, вводя команду cal
, получаете стандартную версию команды вместо новой, если, конечно, не установили в своем каталоге bin
связь с новой командой cal
. Это может привести к путанице: вспомните, что сообщения об ошибках в исходной, команде cal
не очень вразумительны. Мы привели всего лишь один пример возникающих трудностей. Поскольку интерпретатор осуществляет поиск команд среди каталогов, задаваемых переменной PATH
, всегда есть вероятность столкнуться не с той версией команды, которую вы ожидали. Например, если вы задали команду, скажем echo
, имя выполняемого на самом деле файла будет ./echo
, /bin/echo
, /usr/bin/echo
или какое-то другое в зависимости от компонентов вашей переменной PATH
и от того, где находятся файлы. Может случиться, что в вашей последовательности поиска ранее, чем вы ожидали, окажется выполняемый файл с правильным именем, но не с теми результатами. Наиболее типичным примером в такой ситуации является команда test
, которую мы обсудим позднее. Ее название настолько распространено для временной версии программы, что вызовы "не тех" команд test
происходят раздражающе часто[12]. Здесь весьма полезным средством явилась бы команда, которая помогла бы выяснить, какая версия программы должна выполняться.
Один из вариантов решения — цикл поиска по каталогам, указанным в PATH
, выполняемого файла с данным именем. В гл. 3 мы использовали цикл for
по именам файлов и аргументам. Здесь же нужен такой цикл:
for i in компонента в PATH
do
если заданное имя в каталоге i
печать полного путевого имени
done
Поскольку любую команду можно запустить внутри символов слабого ударения очевидное решение состоит в том, чтобы запустить sed
по значению PATH
, заменив двоеточия на пробелы. Мы можем проверить это с помощью нашего старого друга echo
:
$ echo $PATH
4 компонента
:/usr/you/bin:/bin:/usr/bin
$ echo $PATH | sed 's/:/ /a'
Только три выдано!
/usr/you/bin /bin /usr/bin
$ echo `echo $PATH | sed 's/:/ /g'`
По-прежнему только три
/usr/you/bin /bin /usr/bin
$
Однако такое решение проблематично. Пустая строка в PATH
служит синонимом '.'
. Поэтому перевод двоеточий в пробелы не слишком удачен — теряется информация о пустых компонентах. Для создания правильного списка каталогов пустую компоненту PATH
нужно перевести в точку. Пустая компонента может быть в середине, начале или конце строки, так что вам придется потрудиться, чтобы учесть все возможные случаи:
$ echo $PATH | sed 's/^:/./
> s/::/:.:/g
> s/:$/:./
> s/:/ /g'
. /usr/you/bin /bin /usr/bin
$
Мы могли бы записать это с помощью четырех отдельных команд sed
, но так как редактор sed
производит замены по порядку, можно выполнить все операции за один вызов.
После задания каталогов в компонентах PATH
упомянутая выше команда test(1)
может вывести сообщение о том, существует ли файл в каждом каталоге. В принципе команда test
— одна из самых "неуклюжих" программ UNIX. Например, команда "test -r файл"
проверяет, существует ли файл и можно ли его читать; "test -w файл"
проверяет, существует ли файл и можно ли в него писать, но в седьмой версии нет команды test -х
(хотя в System V и других версиях есть), а именно она нам и нужна. Мы примем, что обращение "test -f файл"
будет проверять, существует ли файл и не является ли он каталогом, т.е. представляет ли он собой обычный файл. Но вам следует обратиться к соответствующей странице справочного руководства, поскольку имеет хождение несколько версий.
Каждая команда вырабатывает код завершения — значение, передаваемое интерпретатору и показывающее, что произошло. Это небольшое целое число, которое устанавливается по соглашению. Так, нуль может означать "истину" (команда успешно завершена), а ненулевое значение трактуется как "ложь" (выполнение команды было неудачным). Обратите внимание на то, что выбранные здесь значения противоположны значениям истины и лжи в языке Си.
Поскольку ложь может представлять множество различных значений, причина неудачи обозначается кодом завершения по лжи. Например, команда grep возвращает 0, если произошло сопоставление, 1 — если сопоставления не было, и 2 — в случае ошибки в шаблоне или именах файлов. Каждая программа возвращает код завершения, хотя обычно нас не интересует его значение. Команда test
неординарна: ее единственное назначение состоит в передаче кода завершения. Она ничего не выводит и не изменяет файлы.
Интерпретатор хранит код завершения последней программы в переменной $?
:
$ cmp /usr/you/.profile /usr/you/.profile
Выдачи нет, файлы совпадают
$
$ echo $?
0 означает успех, файлы идентичны
0
$ cmp /usr/you/.profile /usr/mary/.profile
He нуль означает, что файлы различны
/usr/you/.profile /usr/mary/.profile differ: char 6, line 3
$ echo $?
1
$
У некоторых команд, таких, как cmp
и grep
, есть флаг -s
, который заставляет их завершить выполнение с определенным кодом, но подавляет вывод. Оператор if
языка shell
запускает команды в зависимости от кода завершения некоторой команды, а именно:
if команда
then
команды, если условие верно
else
команды, если условие ложно
fi
Местоположение символов перевода строк очень важно: fi
, then
и else
распознаются только после символа перевода строки или точки с запятой.
Оператор if
всегда запускает команду (условие), тогда как в операторе case
сопоставление с шаблоном производится самим интерпретатором. В некоторых версиях UNIX, включая System V, test
является встроенной командой интерпретатора, поэтому if
и test
будут выполняться так же быстро, как и case
. Если test
— не встроенная команда, то операторы case
более эффективны, чем операторы if
, и следует использовать именно их для поиска шаблонов;
$ case "$1" in
hello) command
esac
выполняется быстрее, чем
if test "$1"==hello
Медленнее, если test не встроенная
then
command
fi
Это одна из причин, по которой в языке shell
иногда для проверки условий применяются операторы case
, хотя в большинстве языков программирования использовались бы операторы if
. С другой стороны, с помощью оператора case
непросто определить, имеется ли право доступа к файлу на чтение; здесь предпочтение следует отдать команде test
и оператору if
.
Итак, теперь мы готовы воспользоваться первой версией команды which
, которая выведет сообщение о том, какой файл соответствует команде:
$ cat which
# which cmd: which cmd in PATH is executed, version 1
case $# in
0) echo 'Usage: which command' 1>&2; exit 2
esac
for i in `echo $PATH | sed 's/^:/.:/
s/::/:.:/g
s/:$/:./
s/:/ /g'`
do
if test -f $i/$1 # use test -x if you can
then
echo $i/$1
exit 0 # found it
fi
done
exit 1 # not found
$
Проверим ее:
$ cx which
Сделаем ее выполняемой
$ which which
./which
$ which ed
/bin/ed
$ mv which /usr/you/bin
$ which which
/usr/you/bin/which
$
Первый оператор case
осуществляет контроль ошибки. Обратите внимание на переключение 1>&2
в команде echo
, которое выполняется для того, чтобы сообщение об ошибке не пропало в программном канале. Встроенная команда интерпретатора exit
может использоваться для передачи кода завершения. В нашем примере exit 2
передает код завершения в ситуации, когда команда не выполняется, exit 1
— в ситуации, когда файл не удалось найти, и exit 0
— в ситуации, когда файл найден. Если нет явного оператора exit
, кодом завершения командного файла является код завершения последней выполняемой команды.
Что произойдет, если в вашем текущем каталоге есть программа под именем test
? (Мы предполагаем, что test
не является встроенной командой.)
$ echo 'echo hello' >test
Сделаем поддельную команду test
$ cx test
Сделаем ее выполняемой
$ which which
Попробуем which теперь
hello
Неудача!
./which
$
Вывод: требуется больший контроль. Можно запустить команду which
(если нет команды test
в текущем каталоге), чтобы определить полное имя для test
и задать его явно. Но это не лучшее решение, поскольку команда test
может присутствовать в различных каталогах в разных версиях системы, а команда which
зависит от sed
и echo
, так что необходимо указать и их полные имена. Можно поступить проще — установить значение PATH
в командном файле так, чтобы поиск команд осуществлялся только в /bin
и /usr/bin
. Конечно, это возможно только в команде which
, причем прежнее значение PATH
следует сохранить для определения последовательности каталогов при поиске.
$ cat which
# which cmd: which cmd in PATH is executed, final version
opath=$PATH
PATH=/bin:/usr/bin
case $# in
0) echo 'Usage: which command' 1>&2; exit 2
esac
for i in `echo $opath | sed 's/^:/.:/
s/::/:.:/g
s/ :$/:./
s/:/ /g'`
do
if test -f $i/$1 # this is /bin/test
then # or /usr/bin/test only
echo $i/$1
exit 0 # found it
fi
done
exit 1 # not found
$
Теперь команда which
выполняется даже в том случае, если есть "поддельная" команда test(sed
или echo
) среди каталогов, входящих в PATH
.
$ ls -l test
Все еще здесь
-rwxrwxrwx 1 you 11 Oct 1 06:55 test
$ which which
/usr/you/bin/which
$ which test
./test
$ rm test
$ which test
/bin/test
$
В языке shell
имеются две операции, объединяющие команды ||
и &&
, использование которых часто более компактно и удобно, чем оператора if
. Например, операция ||
может заменить некоторые операторы if
:
test -f имя_файла || echo имя_файла не существует
эквивалентно
if test ! -f имя_файла
! обращает условие
then
echo имя файла не существует
fi
Операция ||
, несмотря на свой вид, не имеет ничего общего с конвейерами — это обычная операция, означающая ИЛИ. Выполняется команда слева от ||
. Если ее код завершения 0 (успех), справа от ||
команда игнорируется. Если команда слева возвращает другое значение (неудача), выполняется команда справа, и значение всего выражения есть код завершения правой команды. Иными словами, ||
представляет собой обычную операцию ИЛИ, которая не выполняет свою правую часть, если левая часть завершилась успешно. Соответственно &&
есть обычная операция И, выполняющая свою правую часть, только если левая часть завершилась успешно.
Упражнение 5.4
Почему в команде which
перед выходом из нее не восстанавливается значение PATH
из opath
?
Упражнение 5.5
Если в языке shell
используется esac
для завершения оператора case
и fi
для завершения оператора if
, почему для завершения оператора do
применяется done
?
Упражнение 5.6
Введите в команду which
флаг -а
, чтобы выводились все файлы из PATH
, а не только первый найденный.
Подсказка: match='exit 0'
Упражнение 5.7
Модифицируйте команду which
так, чтобы она учитывала встроенные в язык shell
команды типа exit
.
Упражнение 5.8
Модифицируйте команду which
так, чтобы она проверяла права доступа файлов. Как изменить ее для получения диагностического сообщения, если файл не удалось найти?
- 5.1 Совершенствование команды cal
- 5.2 Что представляет собой команда which?
- 5.3 Циклы while и until: контроль входа в систему
- 5.4 Команда trap: обработка прерываний
- 5.5 Команда overwrite: замена файла
- 5.6 Команда zap: уничтожение процесса по имени
- 5.7 Команда pick: пробелы или аргументы
- 5.8 Команда news: служба информации пользователей
- 5.9 Команды get и put: контроль изменении файла
- 5.10 Заключение
- which
- 1.1.1. Что такое объект
- Что делать
- 5.4 Команда trap: обработка прерываний
- Что делать, если при установке принтера появляется сообщение Невозможно завершение операции. Подсистема печати недоступн...
- Что дает грамотная должностная инструкция
- Как сделать, чтобы компьютер выключался
- ПОМОГАЙТЕ ДРУГИМ ПРИДЕРЖИВАТЬСЯ ПОЧТОВОГО «ЭТИКЕТА»
- Предисловие Кое-что новенькое – поговорим напрямую
- На что обращать внимание
- Что такое продажа?
- Сохранение рабочей книги с именем, представляющим собой текущую дату