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

[+]7.11.1. Предотвращение уничтожения существующих файлов
[+]7.11.2. Некоторые отличия между sh(1) и csh(1)
[+]7.11.3. Модификаторы переменных в csh(1)
[+]7.11.4. Работа с историей команд

Описание:  Кандидат BSDA должен свободно пользоваться оболочками sh(1), csh(1) или tcsh(1). Кандидат должен уметь изменять поведение обеих оболочек временно или постоянно, включая: предотвращать уничтожение существующих файлов, использовать историю команд, определять псевдонимы команд для экономии времени в командной строке. Кандидат должен знать как временно отменить псевдоним.

Практика:  sh(1), csh(1) и tcsh(1), включая !, !!, $, 0, h, t, r, p, \.

Комментарий

В системах BSD поставляется две оболочки, наличие которых требуется стандартом POSIX: sh(1) и csh(1). Первая традичионно используется для написания сценариев. Вторая больше приспособлена для работы в интерактивном режиме. Конечно ничто не мешает вам установить у себя bash(1) или другую, более или менее «продвинутую» оболочку, однако в нашем курсе мы изучаем только штатные средства BSD, и на то есть свои основания (см. Раздел 7.7.2, «Почему sh(1)).

Поскольку данный раздел посвящён интерактивной работе с оболочками, мы будем описывать в нём почти исключительно csh(1). Полное описание данной оболочки, на русском языке, доступно здесь: [url://csh-1998].

С другой стороны, я отдаю себе полный отчёт в том, насколько популярна оболочка bash(1) среди системных администраторов, поэтому иногда буду делать реверансы в её сторону в стиле «... а в bash(1) тоже есть подобная функция, она делатся так: ...». Кроме того, в случае если некая упоминаемая возможность есть и в bash(1) и в sh(1) я буду делать реверансы в пользу sh(1), хотя последний крайне неудачен с точки зрения интерактивной работы.

[Важно]Важно
csh(1) по умолчанию является оболочкой пользователя root. Никогда не меняйте её на bash(1) или любую другую оболочку из иерархии /usr/local! От пользователя root требуются некоторые действия в критические моменты жизнедеятельности системы, когда иерархия /usr несмонтирована. Поэтому уметь работать в csh(1) может быть жизненно важно.

7.11.1. Предотвращение уничтожения существующих файлов

Для этой цели в оболочках существует режим «noclobber». В csh(1) он включается путём задания переменной окружения noclobber.

% touch test
% set noclobber=1
% echo hello > test
test: Файл существует.
% unset noclobber
% echo hello > test
        

В sh(1) этот режим можно включить используя аргумент командной строки -C. Таким образом, можно использовать shebang #!/bin/sh -C.

7.11.2. Некоторые отличия между sh(1) и csh(1)

Существует ряд отличий между оболочками с точки зрения языка. Язык csh(1) сделан более C-подобным. Изменения довольно глубоки, в частности csh(1) трактует 1 как истину, а 0 как ложь, в отличие от sh(1), поэтому он преобразует на лету коды возврата программ 0 к 1, а всё, что больше 0 к нулю. В csh(1) иной синтаксис написания циклов и других конструкций, больше типов данных. Так в csh(1) поддерживаются массивы (массивы поддерживаются и в bash(1)).

csh(1) поддерживает работу с историей команд и completion. (К стыду своему не знаю как это будет по-русски, имеется ввиду явление когда имена файлов дописываются при нажатии клавиши <TAB>). Ни то, ни другое не поддерживается в sh(1) (но поддерживается в bash(1)).

7.11.3. Модификаторы переменных в csh(1)

Если в переменной в csh(1) содержится имя файла, то с ним можно работать используя различные модификаторы:

% set name=/home/emin/BSDCert/BSDCert.xml
% echo $name
/home/emin/BSDCert/BSDCert.xml
% echo $name:h 1
/home/emin/BSDCert
% echo $name:t
BSDCert.xml
% echo $name:e
xml
% echo $name:h:h 2
/home/emin
% set names=(/home/emin/BSDCert/*) 3
% echo $names
/home/emin/BSDCert/BSDCert.xml /home/emin/BSDCert/Makefile /home/emin/BSDCert/de
fault.css /home/emin/BSDCert/index.shtml /home/emin/BSDCert/macro.vim /home/emin
/BSDCert/single.xsl /home/emin/BSDCert/split.xsl
% echo $names[1] 4
/home/emin/BSDCert/BSDCert.xml
% echo $names[1]:h
/home/emin/BSDCert
% echo $names[1]:t
BSDCert.xml
% echo $names:h 5
/home/emin/BSDCert /home/emin/BSDCert/Makefile /home/emin/BSDCert/default.css /h
ome/emin/BSDCert/index.shtml /home/emin/BSDCert/macro.vim /home/emin/BSDCert/sin
gle.xsl /home/emin/BSDCert/split.xsl
% echo $names:gh 6
/home/emin/BSDCert /home/emin/BSDCert /home/emin/BSDCert /home/emin/BSDCert /hom
e/emin/BSDCert /home/emin/BSDCert /home/emin/BSDCert
% echo $names:gt
BSDCert.xml Makefile default.css index.shtml macro.vim single.xsl split.xsl
% echo $names:ge
xml css shtml vim xsl xsl
        
1 Следующие модификаторы позволяют отобразить некоторые части имени файла: путь к файлу, имя файла, расширение файла. Полный список модификаторов приведён ниже в таблице.
2 Модификатор можно вызвать несколько раз.
3 В этой строке мы определили массив,
4 Мы можем работать с отдельным элементом массива, как с простой переменной. (В bash(1) элементы массива нумеруются с нуля, а не с единицы, а ссылки на них ОБЯЗАНЫ браться в фигурные скобки: echo ${names[0]}. В csh(1) фигурные скобки необязательны.)
5 При применении модификатора к массиву, он применяется только к первому элементу, что непрактично.
6 Однако, комбинируя модификатор с флагом g (от global), мы можем применить его ко всем элементам массива.

Похожие фокусы возможны, впрочем, и в sh(1) (правда в нём нет массивов):

$ name=/home/emin/BSDCert/BSDCert.xml
$ echo $name
/home/emin/BSDCert/BSDCert.xml
$ echo ${name%/*}
/home/emin/BSDCert
$ echo ${name##*/}
BSDCert.xml
$ echo ${name##*.}
xml
        

Впрочем, последний пример явно потерпит фиаско, если в имени файла расширения не будет, и, что ещё хуже, если в имени файла его не будет, а в имени каталога, в котором он находится, оно будет.

Таблица 7.11. Модификаторы переменных в csh(1)

МодификаторОписание
h Удалить имя файла, сохранив компоненты пути (то есть удалить в слове текст справа до ближайшего символа /). От слова head.
gh Применить модификатор h глобально ко всем элементам массива
r Удалить расширение файла, указанное через точку, и саму точку
gr Применить модификатор r глобально ко всем элементам массива
e Удалить имя файла вместе с точкой, сохранив расширение имени
ge Применить модификатор e глобально ко всем элементам массива
t Сохранить имя файла, удалив компоненты пути (то есть удалить текст слева от самого правого символа / и сам этот символ). От слова «tail».
gt Применить модификатор t глобально ко всем элементам массива
q Запретить дальнейшую модификацию слова. Слово заключается в кавычки
x Разбить на слова по разделителям и запретить дальнейшую модификацию. Результат заключается в кавычки.

7.11.4. Работа с историей команд

Для просмотра истории команд служит встроенная команда history:

% history
   1  14:27   set name=/home/emin/BSDCert/BSDCert.xml
   2  14:28   echo $name
   3  14:28   echo $name:h
   4  14:28   echo $name:t
   5  14:28   echo $name:e
   6  14:28   echo $name:h:h
   7  14:29   set names= ( /home/emin/BSDCert/* )
   8  14:29   echo $names[1]
   9  14:29   echo $names[1]:h
  10  14:30   echo $names[1]:t
  11  14:30   echo $names:h
  12  14:30   echo $names:gh
  13  14:30   echo $names:gt
  14  14:30   echo $names:ge
        

При помощи символа ! можно выполнить некоторые действия из истории, например:

% !1 1
set name=/home/emin/BSDCert/BSDCert.xml
% !7
set names= ( /home/emin/BSDCert/* )
% !?his? 2
history
   1  14:27   set name=/home/emin/BSDCert/BSDCert.xml
   2  14:28   echo $name
   3  14:28   echo $name:h
   4  14:28   echo $name:t
   5  14:28   echo $name:e
   6  14:28   echo $name:h:h
   7  14:29   set names= ( /home/emin/BSDCert/* )
   8  14:29   echo $names[1]
   9  14:29   echo $names[1]:h
  10  14:30   echo $names[1]:t
  11  14:30   echo $names:h
  12  14:30   echo $names:gh
  13  14:30   echo $names:gt
  14  14:30   echo $names:ge
  15  15:30   history
  16  15:31   set name=/home/emin/BSDCert/BSDCert.xml
  17  15:31   set names= ( /home/emin/BSDCert/* )
  18  15:31   history
% -2 3
set names= ( /home/emin/BSDCert/* )
% !! 4
set names= ( /home/emin/BSDCert/* )
% !e 5
echo $names:ge
xml css shtml vim xsl xsl
        
1 На команду можно ссылаться по её номеру,
2 или содержащемуся в ней шаблону.
3 Отрицательные номера ведут отсчёт с конца списка — в данном случае мы вызвали вторую с конца команду.
4 Примитив !! эквивалентен -1, т.е. выполняет повторно последнее действие.
5 выполняется последняя команда начинавшаяся с e, т.е. echo names:ge

Разберём чуть более сложный пример:

% touch abc cba
% cat !!* 1
cat abc cba
% echo 'aaa\
? bbb\
? ccc' > abc
% echo 'aaa\
? bcb\
? ccc'> cba
% diff !t* 2
diff abc cba
2c2
< bbb
---
> bcb
% echo !t:0 3
echo touch
touch
% echo !t:1 !t:2 4
echo abc cba
abc cba
% echo !?ab?% 5
echo abc
abc
% echo !t^ !t$ 6
echo abc cba
abc cba
        
1 Вызвав !! в аргументах мы добились того, что в аргументы команды cat(1) были переданы аргументы предыдущей команды (т.е. touch(1)). Благодара знаку * были переданы все аргументы.
2 Мы записали при помощи встроенной команды echo в новые файлы некоторую информацию, теперь мы можем применить не команду cat(1) а команду diff(1). !t ссылается на команду начавшуюся с буквы t, т.е. touch(1), а *, как и раньше означает подстановку всех аргументов.
3 На аргументы, впрочем, можно ссылаться по номеру при этом номер 0 ссылается на первый аргумент (т.е. имя команды),
4 а номера 1, 2 и т.д. на последующие. Здесь !t — указание на то, что мы работаем с командой touch(1), а то, что идёт после двоеточия — указывает на то, какая часть команды нас интересует. Т.е. нас интересует первый и второй аргумент этой команды. Можно ссылаться на диапазоны намеров, например: echo !..:2- — все аргументы начиная со второго; echo !..:2-5 — аргументы со второго по пятый; echo !..-2 — аргументы вплоть до второго. Двоеточием здесь заменена подходящая случаю ссылка на команду.
5 Здесь мы сослались на аргумент содержащий в себе текст «ab», по шаблону. Благодаря знаку % мы ссылались не на всю команду, а только на найденный аргумент.
6 Наконец, символ ^ ссылается на слово номер 1, а $ на последнее слово.

Разрешается не ставить двоеточие перед знаками *, ^, $, - и %

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

p
Распечатать новую команду, но не выполнять её
&
Повторить предыдущую подстановку
s/pattern/subst/
Заменить pattern на subst,Символ / можно заменить на любой, отсутствующий в искомом тексте и в строке подстановки, если subst пустой, то pattern удаляется.

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

% history
  1  cat /home/ivanov/file1.c
  2  cc pa1.c pa2.c pa3.c pa4.c >& errors &
  ...
        

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

!1:0 !1^:t:r
Сперва будет подставлено нулевое слово первой строки, т.е. cat. Затем первое слово первой строки (/home/ivanov/file1.c), к которому будет последовательно применено два модификатора: :t и :r, в результате останется file1. Итого, получаем команду cat file1.
!1:0 !1^:h/document
Аналогично получаем cat /home/ivanov/document.
!1:0 !1^:h:s?ivanov?sidorov?/document
cat /home/sidorov/document
!1:0 !1^:h:s?ivanov?sidorov?/doc !1^:&:p
cat /home/sidorov/doc /home/sidorov/file1.c. Здесь модификатор & заставляет повторить предыдущую замену, а модификатор :p заставляет просто напечатать результат на экране, вместо того, чтобы выполнить действие.
!1:0 !2:1-4:gs?pa?ff?:p
Во второй строке выбираются слова с первого по четвёртое, и в них во всех, (флаг g) делается замена pa на ff, результат печатается: cat ff1.c ff2.c ff3.c ff4.c

Существует способ редактирования последней командной строки:

% echo abc cba
abc cba
% ^abc^cba^
echo cba cba
cba cba
        

Представьте себе, что у вас определён следующий псевдоним:

% alias sp "sort \!* | print"
        

Теперь команды sp one two и sort one two | print тождественны, так как в !* будут подставлены аргументы, с которыми вызвана команда sp.