Главная » Программирование для UNIX » Какие команды мы выполняем, или команда which

0

Создание персональных версий программ, подобных cal, создает неко торые  трудности.  Наиболее явная из  них  формулируется  следующим образом: если  вы  работаете вместе  с Мэри  и вводите cal, будучи зарегистрированным как mary, отработает стандартная, а не новая версия команды (если только в каталоге /bin, принадлежащем Мэри, нет ссылки на соответствующий файл). Это не очень  удобно (вспомните, например, что сообщения об ошибках, выдаваемые исходной программой cal, не очень-то полезны), но это всего  лишь одно из  проявлений одной  большой  проблемы. Поскольку оболочка ищет команды  в  каталогах, ука занных в переменной PATH, всегда  есть шанс  получить не ту версию команды, которая ожидается. Например, если ввести команду echo, то путем к файлу, который исполнится, может быть ./echo  или /bin/echo, или

/usr/bin/echo, или  что-то  еще,  в зависимости от компонентов переменной  PATH  и местоположения файлов. И если  исполняемый файл с пра вильным  именем,  но  делающий  не  то, чего  ожидает  пользователь, встретится на пути  поиска раньше, то возникнет путаница. Наверное, самым ярким примером является команда test (о ней будет рассказано позже): это  имя  очевидным образом подходит для  временных версий программы, которые и вызываются (гораздо чаще, чем хотелось бы) при обращении к настоящей test.1  Полезной может оказаться программа, информирующая о том, какая версия программы будет выполняться.

1        Дальше будет  показано, как избежать  подобных  проблем в  командных файлах, где обычно используется test.

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

for  i  in  каждый компонент  PATH do

done

если файл с заданным именем есть в каталоге i, вывести его полное путевое имя

Поскольку любая команда может быть запущена из-под обратных ка вычек `…`, то очевидно следующее решение – применить команду sed к $PATH, заменяя двоеточия пробелами. Протестируем при  помощи нашего старого друга echo:

$ echo $PATH

:/usr/you/bin:/bin:/usr/bin         4 компонента

$ echo $PATH  | sed  ‘s/:/ /g’

/usr/you/bin /bin  /usr/bin           Выведено только 3!

$ echo `echo  $PATH  | sed  ‘s/:/ /g’`

/usr/you/bin  /bin /usr/bin          Также только 3

$

Отчетливо видна проблема: пустая строка в 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  file проверяет, существует ли файл и доступен ли он для чтения, а test –w  file проверяет, существует ли файл и разрешена ли в него запись. В седьмой версии нет команды test  –x (она существовала в

System V и других версиях), которая бы вполне подошла для нашей задачи. Будем использовать test –f, она проверяет, существует ли файл и не является ли он каталогом (то есть, другими словами, является ли он обычным файлом). Сейчас  в обращении находится несколько версий программы test, так что обратитесь к руководству для получения информации о вашей системе.

Любая команда возвращает код завершения – число, по значению которого оболочка определяет, что произошло. Код завершения – это целое число, принято соглашение о том,  что 0 означает «истина» (команда выполнилась успешно), а ненулевая величина означает «ложь» (команда не выполнилась успешно). Обратите внимание на что, что в языке  Си  значения истины и  лжи прямо  противоположны только что описанным.

Поскольку «ложь» может задаваться различными ненулевыми вели чинами, то в этом  коде  завершения часто  зашифровывается причина неудачи. Например, команда grep возвращает 0, если найдено соответствие, 1 – если соответствие не найдено, и 2 – если в имени файла или  шаблоне есть ошибка. Код завершения возвращается любой  программой, несмотря на то, что его значение часто  бывает не интересно поль зователю. Команда test  –  это  необычная команда,  ее  единственное предназначение заключается в возврате кода завершения. Она ничего не выводит и не изменяет файлы.

Оболочка хранит код завершения последней программы в переменной

$?:

$ cmp  /usr/you/.profile /usr/you/.profile

$                                                                    Нет вывода; они одинаковые

$ echo $?

0                          Ноль означает успешное выполнение: файлы идентичны

$ cmp  /usr/you/.profile /usr/mary/.profile

/usr/you/.profile /usr/mary/.profile differ:  char  6,  line 3

$ echo $?

1                    Ненулевая величина означает, что файлы были различными

$

У некоторых команд, например cmp и grep, есть параметр -s, и если  он задан, то команда  возвращает соответствующий код  завершения,  но ничего не выводит.

Оператор оболочки if выполняет команды в зависимости от кода  завершения команды:

if команда

then

else fi

команды, если условие истинно команды, если условие ложно

Положение разделителей строк имеет  важное значение: fi, then и else распознаются, только если  находятся в начале строки или  следуют за точкой с запятой. Часть else может отсутствовать.

Оператор if всегда  запускает команду, являющуюся условием, тогда  как оператор case осуществляет сравнение с шаблоном непосредственно в оболочке. В некоторых версиях UNIX  (в том числе в System V) test является встроенной функцией оболочки, поэтому if и test будут  выполнены так  же быстро, как и case. Если  же test не является встроенной,  то операторы case  более  производительны, чем  операторы if, и именно их следует применять для любого сопоставления с шаблоном:

case  "$1"  in hello)    команда esac

выполнится быстрее, чем

if test "$1"  = hello      Медленнее, если test не встроена в оболочку

then

команда

fi

Именно по этой  причине операторы case  иногда  применяются в оболочке для тестирования того, что в большинстве языков программирования тестировали бы с  помощью  оператора if. С другой стороны, в операторе case  нет  простого способа,  позволяющего определить, есть ли права на чтение файла, это лучше сделать, используя test и if.

Теперь  все готово  для  написания первой версии команды which, кото рая  будет сообщать, какой файл соответствует команде:

$ cat  which

# which  cmd:  какая  из имеющихся  в $PATH  команд  выполняется,  версия 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      # если  можно, используйте test –x then

echo $i/$1

exit  0                  # найдено

fi done

exit 1                                  # не найдено

$

Давайте опробуем ее:

$ 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. Старое значение PATH, несомненно, должно быть  сохранено (только для  команды which, чтобы  опреде лить последовательность каталогов для просмотра).

$ cat  which

# which  cmd:  какая  команда выполняется,  окончательная  версия

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        #  это  только  /bin/test then                              #  или  /usr/bin/test

echo $i/$1

exit  0                  # найдено

fi done

exit 1                # не найдено

$

Команда which теперь работает даже в случае наличия на пути  поиска фальшивой test (или sed, или echo).

$ 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

$

В оболочке есть  два  оператора для  комбинирования команд: || и &&, они более компактны, и часто  работать с ними удобнее, чем с оператором if. Так, || может заменить некоторые операторы if:

test –f  имя5файла || echo file имя5файла  does  not  exist

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

if test ! –f  имя5файла                     Символ !  инвертирует условие

then

echo file  имя5файла  does  not  exist fi

Несмотря на его внешний вид оператор || не имеет  ничего общего с каналами; это  условный  оператор, логическое ИЛИ. Выполняется команда, находящаяся слева от ||. Если код завершения равен нулю (выполнено успешно), то команда справа от || игнорируется. Если  же левая часть  возвращает ненулевую величину (неуспешно), то выполняется правая часть, и значение всего  выражения равно  коду завершения правой части. Другими словами, ||  – это оператор логического ИЛИ, который не выполняет команду правой части, если левая выполнилась успешно. Аналогично && соответствует логическому И;  он выполняет команду своей правой части, только если успешно выполнилась левая.

Упражнение 5.4.  Почему перед  выходом команда which не устанавливает PATH в opath? ~

Упражнение 5.5. Если в оболочке завершение case реализуется посред ством esac, а завершение if – с помощью fi, то почему для завершения do используется done? ~

Упражнение 5.6.  Добавьте параметр –a для which, чтобы  она печатала все файлы в PATH, а не выходила после первого найденного. Подсказка: match=’exit0′. ~

Упражнение 5.7.  Измените which так, чтобы  она знала о встроенных в оболочку функциях типа exit. ~

Упражнение 5.8.  Измените which так, чтобы  она  проверяла права на выполнение файлов. Пусть она  печатает сообщение об ошибке в случае,  если файл не найден. ~

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии