ГЛАВА 10. Смешанные приемы

Введение
Способы преобразования в языке shell
conv
Модули преобразования
dtoh
dtoo
htod
htoo
Тонкости bc
otod
otoh
Встроенный ввод
С редактором ed
С файлом a.out
C архивами языка shell
Управление статусом цикла
Фильтры и синтаксис
Недостатки/особенности программирования на языке shell
Программа для перенаправления ошибки
Некорректный код возврата
Хитрости редактора Vi
Возвращение в язык shell
Поддержка Escape
Макросы
Команда "One-Liners" - крошечная, но мощная

Введение

Эта книга является итогом многолетней работы по подбору и развитию инструментальных средств ОС UNIX. Многие вещи, которые не хотелось бы оставлять без внимания, не вписались в контекст предыдущих глав. Это и законченные процедуры, подобные представленным ранее, и небольшие, но очень мощные фрагменты программ. Кроме того, высказаны некоторые полезные идеи и представлены методы обработки общих ситуаций на языке shell.

Способы преобразования

Поскольку компьютеры и их резидентные утилиты используют при работе разные системы счисления, часто возникает необходимость преобразования оснований систем счисления. Эти преобразования обеспечиваются хорошо знакомыми специалистам командами UNIX bc (калькулятор произвольной точности) и dc (которая предположительно расшифровывается как настольный калькулятор ("desk calculator")). Большинство из существующих возможностей либо носят очень ограниченный характер, либо их тяжело использовать в ряде ситуаций, поэтому будет рассмотрен вопрос как использовать существующие возможности UNIX, чтобы любое преобразование было как можно более легко осуществимым.

Название: conv

conv Переводит числа из одной системы счисления в другую

НАЗНАЧЕНИЕ

Обеспечивает возможность преобразования основания системы счисления

ВЫЗОВ

 conv

ПРИМЕР ВЫЗОВА

$conv Вызвать главное меню различных преобразований
2 Выбрать опцию 2 ( из шестнадцатиричной в десятичную)
FFF Ввести шестнадцатиричное число FFF. На выходе программы получим десятичный эквивалент

ИСХОДНЫЙ ТЕКСТ ДЛЯ ФУНКЦИИ conv

  1 :
  2 # @(#) conv v1.0 Преобразование основания системы
   счисления, используя shell Автор: Russ Sage
  3
  4 while :
  5 do
  6   echo "
  7
  8 Преобразование оснований
  9 ------------------------
  10 1 - Десятичное в шестнадцатиричное
  11 2 - Шестнадцатиричное в десятичное
  12 3 - Десятичное в восьмеричное
  13 4 - Восьмеричное в десятичное
  14 5 - Восьмеричное в шестнадцатиричное
  15 6 - Шестнадцатиричное в восьмеричное
  16
  17 enter choice (1-6, <>): c"
  18  read CHOICE
  19
  20  case $CHOICE in
  21  "") exit;;
  22  1)  echo "пВведите десятичное число (<> to exit): c"
  23    read DEC
  24    if [ "$DEC" = ""]
  25     then exit
  26    fi
  27    HEX='. dtoh'
  28    echo "
${DEC}d = ${HEX}x";;
  29  2)  echo"
Введите шестнадцатиричное число
     в верхнем регистре (<> to exit): c"
  30    read HEX
  31    if [ "$HEX" = ""]
  32     then exit
  33    fi
  34    DEC='. htod'
  35    echo "
${HEX}x= ${DEC}d;;
  36  3) echo "
Введите десятичное число в
    верхнем регистре (<> to exit): c"
  37    read DEC
  38    if [ "$DEC" = ""]
  39     then exit
  40    fi
  41    OCT='. dtoo'
  42    echo "
${DEC}d = ${OCT}o";;
  43  4) echo "
Введите восьмеричное число
    (<> to exit): c"
  44    read OCT
  45    if [ "$OCT" = ""]
  46     then exit
  47    fi
  48    OCT='. otod'
  49    echo "
${OCT}o = ${DEC}d";;
  50  5) echo "
Введите восьмеричное число
    (<> to exit): c"
  51    read OCT
  52    if [ "$OCT" = ""]
  53     then exit
  54    fi
  55    HEX='. otoh'
  56    echo "
${OCT}o = ${HEX}x";;
  57  6) echo "
Введите шестнадцатиричное число
    в верхнем регистре (<> to exit): c"
  58    read НЕХ
  59    if [ "$НЕХ" = ""]
  60     then exit
  61    fi
  62    OCT='. htoo'
  63    echo "
${HEX}x = ${OCT}o";;
  64  *)  echo "
$CHOICE-неизвестная команда";;
  65  esac
  66 done

ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ

CHOICE Выбор команд из главного меню
DEC Выдает десятичное значение как результат преобразования
HEX Выдает шестнадцатиричное значение как результат преобразования
OCT Выдает восьмеричное значение как результат преобразования

ОПИСАНИЕ

Зачем нам нужна функция conv ?

Выполнение числовых операций большого объема в командных файлах языка shell - это далеко не самая хорошая идея. Командные файлы являются весьма медленными сами по себе, а выполнение математических операций еще больше замедляет их работу. Однако, процедуры языка shell имеют математические возможности, и Вы, возможно, захотите ими воспользоваться. Если Вам нужно преобразовать несколько чисел в процессе написания программы, то для этой цели достаточно удобно вызвать процедуру языка shell. Поскольку conv - это программа, управляемая меню, Вам не придется беспокоиться о запоминании сложного синтаксиса, котрый используют некоторые системные утилиты преобразования.

Что делает conv?

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

Режим работы программы выбирается из главного меню. В меню есть шесть пунктов. После того как Вы выбираете число между 1 и 6, программа просит Вас ввести число которое Вы хотите преобразовать. Происходит преобразование и на выходе программы Вы получаете два значения - число, которое Вы преобразовываете и число, к которому оно было преобразовано. Преобразования осуществляются путем вызова внешних процедур, о которых будет идти речь дальше в этой главе, так что перед запуском conv необходимо убедиться, что Вы включили их в вашу систему и разместили в том же каталоге, что и conv.

Если Вы введете команду, не вошедшую в вышеупомянутый перечень, то будет выдано сообщение об ошибке и опять будет выведено главное меню.

ПОЯСНЕНИЕ

Строки 4-66 - это один большой бесконечный цикл while. Мы используем бесконечный цикл, чтобы в случае ошибочного ввода программа вернулась в главное меню для повторного ввода. Для того, чтобы выйти из программы, нужно прервать цикл, т.е. выйти из цикла. Строки 6-17 печатают меню и выдают подсказку для выбора. Если Вы просто нажмете "Ввод", программа завершит свою работу.

Строка 18 читает ввод с клавиатуры, и строки 20-65 выполняют выбор по условию для этой величины. Если получен нулевой (пустой) ввод, то программа завершает свою работу.

Строки 22-28 осуществляют перевод чисел из десятичной в шестнадцатиричную системы счисления. Поскольку все модули перевода отвечают одному и тому же образцу, то детально мы рассмотрим только данный модуль.

Подсказка запрашивает число в строке 23. В строках 24-26 проверяется, не было ли введенное значение пустым. Строка 27 выглядит несколько загадочно, вызывая один из внешних командных файлов dtoh для преобразования десятичных чисел в шестнадцатииричные. Обратите внимание на то как одна программа выполняет другую.

Командный файл dtoh запускается, используя команду ".". Это означает : "Выполните программу, используя тот же shell". Процедура dtoh использует переменную DEC для ввода числа и выдает преобразованное число на стандартный вывод. Чтобы записать это число в переменную, мы делаем присвоение, потом запускаем программу, используя командную подстановку. Строка 28 выдает на экран первоначальное десятичное число и шестнадцатиричное, к которому оно было преобразовано.

Варианты 2, 3, 4, 5 и 6 работают аналогично. Единственное, что меняется - это имя переменной, которое соответствует типу преобразования и название командного файла (скрипта), который вызывается для этого преобразования.

МОДУЛИ ПРЕОБРАЗОВАНИЯ

Теперь давайте рассмотрим отдельно каждый из модулей перевода. Эти модули или командные файлы языка shell используют команду UNIX bc, чтобы осуществлять преобразования оснований систем счисления. Нельзя сказать, что команда bc - это наиболее простой и удобный способ перевода, но тем не менее она работает, и единственное, что нам нужно, - это изучить ее и поместить в командный файл.

НАЗВАНИЕ : dtoh

dtoh Десятичные в шестнадцатиричные.

НАЗНАЧЕНИЕ

Преобразовывает входные десятичные числа в выходные шестнадцатиричные числа.

СИНТАКСИС


 $DEC="decimal_number"; HEX='.dtoh'

ПРИМЕР ВЫЗОВА


  $DEC="25";HEX='.dtoh'
  $echo $HEX

Присвоить DEC начальное значение 25, вызвать dtoh для его преобразования и записать результат в HEX. Вывести результаты на экран.

ИСХОДНЫЙ ТЕКСТ ДЛЯ dtoh


  1 :
  2 # @(#) dtoh v1.0 Преобразование языка shell--десятичные в
      шестнадцатиричные Автор: Russ Sage
  3
  4  bc < HEX
  $ echo "шестнадцатиричное число: 'cat HEX'"

Обозначение () запускает вызываемую процедуру в дочернем языке shell. Используя "." для ее выполнения, мы по прежнему имеем доступ к переменной DEC. Стандартный вывод перенаправляется в HEX. Эхо сопровождение получает значение, используя командную подстановку. Результат cat помещается в эхо предложение.

НАЗВАНИЕ: dtoo

dtoo Десятичные в восьмеричные

НАЗНАЧЕНИЕ

Переводит входные десятичные числа в выходные восьмеричные.

СИНТАКСИС

  DEC="decimal_number"; OCT='.dtoo'

ПРИМЕР ВЫЗОВА

  $DEC="16";OCT='.dtoo'
  $echo $OCT

Присвоить DEC начальное значение 16, вызвать dtoo для ее преобразования и записать результат в OCT. Вывести результаты на экран.

ИСХОДНЫЙ ТЕКСТ ДЛЯ dtoo

  1 :
  2 # @(#) dtoo v1.0 Преобразование языка shell--десятичные в
     восьмеричные Автор: Russ Sage
  bc <

НАЗВАНИЕ: htod

htod Шестнадцатиричные в десятичные

НАЗНАЧЕНИЕ

Переводит входные шестнадцатиричные числа в выходные десятичные.

СИНТАКСИС

  HEX="hex_number"; DEC='.htod'

ПРИМЕР ВЫЗОВА

  $HEX="1EAC" ; DEC='.htod'
  $echo $DEC

Присвоить HEX шестнадцатиричное значение, преобразовать его, напечатать результаты.

ИСХОДНЫЙ ТЕКСТ ДЛЯ htod

  1 :
  2 # @(#) dtoo v1.0 Преобразование языка shell--шестнадцатиричные
      в десятичные Автор: Russ Sage
  bc <

НАЗВАНИЕ: htoo

htoo Шестнадцатиричные в восьмеричные

НАЗНАЧЕНИЕ

Переводит входные шестнадцатиричные числа в выходные восмеричные.

СИНТАКСИС

  HEX="hex_number"; OCT ='.htoo'

ПРИМЕР ВЫЗОВА

  $HEX="F1E" ;
  $OCT='.htoo'
  $echo $OCT

Присвоить HEX шестнадцатиричное значение, преобразовать его, напечатать восьмеричное число.

ИСХОДНЫЙ ТЕКСТ ДЛЯ htoo

  1 :
  2 # @(#) htoo v1.0 Преобразование с помощью языка shell --
    шестнадцатиричные в восьмеричные Автор: Russ Sage
  bc <

НАЗВАНИЕ: otod

otod Восьмеричные в десятичные

НАЗНАЧЕНИЕ

Переводит входные восьмеричные числа в выходные десятичные.

СИНТАКСИС

  OCT="octal_number"; DEC = '.otod'

ПРИМЕР ВЫЗОВА

  $OCT="777" ;
  $DEC='.ot
  $echo $DECod'

Присвоить OCT восьмеричное число, преобразовать его в десятичное, напечатать результат.

ИСХОДНЫЙ ТЕКСТ ДЛЯ otod

  1 :
  2 # @(#) otod v1.0 Преобразование языка shell--восьмеричные в
      десятичные Автор: Russ Sage
  bc <

НАЗВАНИЕ: otoh

otoh Восьмеричные в шестнадцатиричные

НАЗНАЧЕНИЕ

Переводит входные восьмеричные числа в выходные шестнадцатиричные.

СИНТАКСИС

  OCT="octal_number"; HEX = '.otoh'

ПРИМЕР ВЫЗОВА

  $OCT="777" ;
  $DEC='.otoh'
  $echo $HEX

Присвоить OCT восьмеричное число, преобразовать значение OCT в шестнадцатиричное, запуская otoh. Присвоить результат преобразования HEX, отобразить значение HEX.

ИСХОДНЫЙ ТЕКСТ ДЛЯ otoh

  1 :
  2 # @(#) otoh v1.0 Преобразование с помощью языка shell --
      восьмеричные в шестнадцатиричные
      Автор: Russ Sage
  bc < idfile

Команда ls запускается как дочерний shell, используя обозначение (). Дочерний shell помещается в фоновый режим, используя символ & . Когда результат процесса id отображен, он направляется в файл ошибок дочернего языка shell, который его выполняет. Мы просто перенаправляем стандартную ошибку в файл и получаем число! Теперь мы можем сделать что-нибудь типа:

  $ kill -9 'cat idfile'

где процесс id, переданный kill, генерируется из команды cat, которая печатает процесс id, захваченный ранее. Это может дать программам опцию "kill self", где они могут отслеживать их id, чтобы вам не пришлось это делать. Программа watch, которую мы видели в главе 6 делает нечто подобное.

ВСТРОЕННЫЙ ВВОД

Редактор vi удобен до тех пор, пока Вам не нужно делать построчное редактирование текста или редактирование в командном режиме. Sed тоже неплохой редактор, но в нем не предусмотрена возможность перемещения по файлу. Sed может перемещаться только вперед по файлу до конца файла. Все наши проблемы может решить скромный редактор ed.

С редактором ed

Еd является интерактивным редактором и в нем есть все необходимое для обработки выражений. Поскольку он читает стандартный ввод для своих команд, мы можем помещать в stdin встроенный текст для управления собственно редактором. Еd читает команды, как если бы они были даны с клавиатуры. Он не знает, что запущен в командном режиме. Это открывает совершенно новый способ использования мощи интерактивного редактирования в командном режиме.

В качестве примера рассмотрим следующую программу. Помните, что все специальные символы в языке shell должны быть заключены в кавычки, например $. Если они не заключены в кавычки, то ввод будет некорректным.

  ed file << -!
   1, $s/^ *//
   w
   q
  !

В этом примере редактируется файл под названием "file" и над ним выполняется несколько команд. Первая команда говорит "От первой строки до последней, для каждой строки, имеющей пустые символы в начале строки, за которыми следует любое количество таких же символов, заменить эти символы "ничем". Запишите файл и выйдите." Эта процедура удаляет пробелы из начала строки.

С файлом a.out

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

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

  $ (a.out < text
  > input
  > lines
  > !
  ) | more

Это можно напечатать непосредственно с клавиатуры. Мы используем символы скобок, поскольку, если непосредственно печатать этот код, shell будет выдавать подсказку PS2 вплоть до знака !, затем выполнит команду. Единственное, что мы можем сделать, чтобы он не вышел автоматически - это продолжать запрашивать ввод, опуская его на уровень ниже.

C архивами языка shell

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

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

  $ cat archive
  #
  # Это архивный файл текстовых файлов 1, 2 и 3
  #
  
  echo "извлекаем текстовый файл 1"
  cat > text1.sh << !
  #
  # Это пример текстового файла 1
  #
  
  who | sort
  !
  echo "извлекаем текстовый файл 2"
  cat > text2 << !

Это содержимое второго файла. Это не программа, а просто текст. Заметьте, что ему не нужно строк комментария, потому что запущенный shell знает, что это ввод. Но не пытайтесь запускать text2, т.к. у Вас все равно ничего не получится.

  !
  
  echo "извлекаем текстовый файл 1"
  cat > text3.c << !
  
  /* Это содержимое файла 3, Си программа */
  
  main()
  {
  printf("hello world");
  }
  !
  
  #
  # конец архивного файла
  #

При выполнении архив проходит через три команды cat. Первая команда cat создает файл text1.sh (командный файл языка shell), text2 (обычный текст) и text3.c (Си-программа). Все это выполняется после того, как Вы наберете на клавиатуре "archive". Это удобно, если нужно перенести текст в другое место. Вместо того чтобы пересылать три файла, нам нужно переслать один. Вместо трех файлов, соединенных вместе, у нас три отдельно упакованнных файла, каждый из которых восстанавливает себя при запуске архива. Таким образом, нам не придется гадать, пытаясь представить какой текст в какой файл попадет.

Управление статусом цикла

Иногда условные выражения цикла нужно подбирать специальным образом, чтобы они отвечали нашим потребностям. Это происходит не часто, однако бывают ситуации, когда Вы можете захотеть использовать определенный синтаксис. В таблице 10-1 приведены три разных способа сделать условие цикла while "истинным". Помните, что shell ищет успешный статус выхода - статус (0) из последней синхронно выполняемой команды.

Таблица 10-1
Способы заставить цикл быть "истинным"

Цикл Условие со значением "истина"
while true True - это команда в /bin, которая возвращает статус 0
while[1 -eq 1] Мы используем здесь тестовую команду, чтобы возвратить статус 0
while : Мы используем встроенное предложение shell'а, чтобы возвратить статус 0

Фильтры и синтаксис

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

Почему все команды не могут действовать как фильтры? Потому, что они не предназначены для этого. Возьмем, например, ls. Что делает ls? Она выдает список файлов в текущем каталоге. Если мы говорим "ls file", он выдает информацию только для этого файла. Если мы говорим "echo file | ls", ls не дает информацию о файле, но выдает список файлов в текущем каталоге, потому что ls не смотрит в стандартный ввод, если в командной строке нет аргументов.

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

Давайте рассмотрим несколько примеров. Команда UNIX wc - это фильтр. Мы можем вызывать ее как "wc file1 file2 file3", чтобы она подсчитала слова в трех файлах. Что было бы, если бы мы сказали: "ls file1 file2 file3 | wc" ? Wc подсчитала бы сумму символов, которые выдала бы ls. В данном случае в строке file1, file2, и file3 - 15 символов. Как нам получить реальные файловые данные, а не имена файлов в wc? Изменяя способ, которым мы вызываем wc:

cat file1 file2 file3 | wс

Путем предварительного соединения файлов, данные передаются на вход wc, и wc суммирует подсчитанные суммы символов, содержимого файлов. Та же самая концепция применима для всех команд фильтров. Еще одной подобной командой является awk. Мы можем сказать "awk file", и команда прочитает содержимое файла, или "cat file | awk", и получим такой же результат. Мы не можем использовать синтаксис "ls file | awk", т.к. awk выполняет свою программу только над символами в имени "file".

Недостатки/особенности программирования на языке shell

В этом разделе мы рассмотрим некоторые недочеты языка shell. До конца пока не ясно, почему проявляются эти дефекты или особенности. Так уж shell работает и такой уж он на самом деле.

Программа для перенаправления ошибки

  1 :
  2 # @(#) перенаправление ошибочного значения переменной в цикл,
   присоединенный к программному каналу
  3
  4 N=1
  5 echo "начальное значение N = $N"
  6
  7 echo "1
2
3" | while read LINE
  8 do
  9   N=2
  10  echo "значение в цикле N = $N"
  11 done
  12
  13 echo "конечное значение N = $N"

Программа показывает, что различные присвоения, сделанные в дочерних языках shell, не распространяются на их родителей. Строка 4 присваивает N начальное значени 1. Затем значение N отображается в строке 5 для проверки. Вся хитрость этой программы заключена в строке 5. Мы отправляем символы "1 новая строка 2 новая строка 3" в программный канал и даем это на вход циклу while. Таким образом, мы заставляем цикл выполнить три итерации. Присоединяя вывод к программному каналу, мы создаем дочерний shell, чтобы выполнить цикл while. Внутри цикла while мы изменяем значение N и печатаем его для проверки.

В конце цикла мы печатаем окончательное значение N. Оно больше не равно 2 как это было внутри цикла, а равно 1, как это было после первого присвоения. Ниже представлен пример прогона этой программы.

  $ redir
  начально значение N = 1
  значение в цикле N = 2
  значение в цикле N = 2
  значение в цикле N = 2
  конечное значени N = 1

Это показывает, что значение переменной передается вниз дочернему языку shell, но изменения в дочернем shell не передаются родительскому.

Некорректный код возврата

Откуда shell знает, являются ли корректными те или иные коды возврата? На это сложно ответить. Иногда оказывается, что код ошибочен, когда ошибку содержит ваша программа.

НАПРИМЕР

  1 :
  2 # @(#) ошибка кода возврата
  3
  4 echo "Введите команду : с"
  5 read CMD
  6
  7 eval $CMD
  8 echo "$? = $?"
  9
  10 if [ $? -eq 0 ]
  11  then  echo хороший выход - $?
  12  else  echo плохой выход - $?
  13 fi

Программа начинается с того, что в строке 4 Вам выдается подсказка для введения команды. Команда читается, строка 7 оценивает не требуются ли ей дополнительные переменные и выполняет ее. Помните, что нам нужно оценить параметр в том случае, если кто-нибудь сказал что-то типа "echo $HOME". Если команды eval нет, то печатается литеральная строка $HOME. После команды eval печатается действительное значение $HOME. Так что нам приходится использовать команду eval в этой ситуации. После того как команла выполнена строка печатает статус выхода, ссылаясь на $?. Это совершенно нормально.

Строка 10 затем использует тестовую команду, чтобы программа разветвилась в зависимости от кода возврата. Это и есть то место, где ошибка. Величина, которую видит тестовая команда не совпадает с той, которую печатает эхо.

  $ status
  введите команду: ls -z
  ls : illegal option --z
  usage : -1ACFRabcdfglmnopqrstux [files]
  $? = 2
  хороший выход - 0

Это показывает, что ls запустили в ошибочном режиме. Ls напечатал свое сообщение об ошибке и возвратил код возврата равный 2. Однако тестовая команда видит $? как значение 0 и выбирает ветвь истина. Можете ли вы найти ошибку в строке 8 программы ? Это хорошая нота для окончания раздела об ошибках.

Хитрости редактора Vi

Одна из самых замечательных особенностей UNIX - это возможность выйти из програм так, чтобы Вы могли запускать другие команды вне языка shell. Это было разработано внутри UNIX и является простым и мощным средством. Использование некоторых приемов, описанных ниже может сделать разработку программы более простой и быстрой.

Возвращение в shell

Возвращение в shell - очень полезная возможность в редакторе vi. Вы можете записать Вашу программу в редакторе, выйти из него, запустить программу, вернуться назад в редактор и т.д. Этот цикл редактирование - трансляция - проверка может быть выполнен из редактора. С этими возможностями входа и выхода из редактора Вы можете закончить сеанс работы без реального уничтожения редактора. Редактор vi также является редактором ex. Vi - это визуальная часть ex. Следовательно, Вы можете выйти в shell двумя путями. Первый - используя переменную sh, которая установлена в редакторе ex. Вы можете набрать

  : sh

пока вы в vi или просто "sh", если Вы в ex. Редактор прямо покидает shell, который Вы определили в переменной sh. Откуда редактор знает какой shell Вы запускаете? Это можно определить по регистрационной переменной окружения языка shell. Если Ваш shell - /bin/sh, а Вы хотите запустить /bin/shV, Вы можете переустановить значение переменной, напечатав ":set sh=/bin/shV"

Другой способ выхода из vi - это с использованием синтаксиса:

  :!sh

где "sh" дает Вам shell (/bin/sh). Обратите внимание, что происходит. Вы запускаете shell (запускаемый по :!), которому дана команда запустить shell (:!sh). Когда Вы, наконец, запукаете этот shell, получается, что у Вас запущен лишний shell. Это очень наглядно представлено в листинге ps, приведенном ниже

  UID  PID PPID C STIME TTY TIME COMMAND
  russ  35  1 0 Jul 5 co 0:50 -shv
  russ 1233  35 0 04:30:15 co 0:57 vi file
  russ 1237 1233 0 04:43:13 co 0:01 sh -c sh
  russ 1238 1237 0 04:43:15 co 0:02 sh

В третьей строке все сказано. Из vi вы запустили shell с опцией -с для запуска языка shell . Это бесполезная трата целого shell! А если использовать указанный выше синтаксис или просто ":sh", то такая ситуация не возникнет.

Поддержка Escape

Кроме того, что редактор vi можно покинуть по Esc, он поддерживает некоторые другие возможности для выхода. Обладая различными возможностями выхода, инструментальные средства могут выполнять для Вас большую часть работы. Первый вариант синтаксиса ":!cmd", который является префиксом для запуска любой команды вне редактора. В этом случае команда может быть любой командой раздела (1).

Второй вариант синтаксиса - это ":!!". Это означает выйти (:!) и использовать последнюю командную строку как аргумент для запуска в новом языке shell. Например, если мы сказали: ":!ls", потом ":!!", :ls будет запущена опять. Второй ! ссылается ко всей предыдущей командной строке.

Третий вариант синтаксиса - это ":!%". Это означает выйти (:!) и запустить команду, имя которой является именем этого файла (%). Когда вы нажимаете возврат каретки, % - замещается именем файла, что очень удобно при редактировании командных файлов. Вы можете сделать что-нибудь типа:

  $ vi tool
  . . . edit . . .
  :w

Вы вызываете vi с именем файла, так что vi запоминает имя "tool" в своем буфере. Вы можете изменить что-то прямо тут в редакторе, записать изменения на диск, затем запустить новую копию файла. Редактор заполняет файл с именем "tool" и запускает его. Когда Вы выходите из исполняемого файла "tool", Вы попадаете назад в редактор, готовые внести изменения в текст и запустить программу снова.

Одна из хороших последовательностей - это отредактировать файл, сделать изменения, записать их, запустить файл, используя %, внести изменения еще раз, перезапустить программу, напечатав :!!, что перезапускает последнюю команду escape, :!% . Таким образом цикл выходов и запусков программы образует три нажатия клавиатуры, :!!. Мы даже можем использовать эту возможность для компиляции программ на С. Если у нас есть командный файл, который называется "cg" (генератор компиляции) мы можем проще использовать vi:

  F = 'echo $ 1 | sed -e "s/^(.*).c$/1"'
  cc $1 -o $F
 Потом мы можем выполнить последовательность такого типа: 
  $ vi test.c
  ...edit...
  :!cg %
 или то же самое короче
  : !cg test.c
 и заканчивается созданием исполняемого модуля "test".

Макросы

Другая возможность vi, которая поддерживает простой выход, - это механизм макросов. Хотя главным образом макросы нужны для того, чтобы можно было помещать команды редактирования в именованные регистры, которые часто используются. Таким образом вместо того, чтобы использовать синтаксис опять и опять, Вы просто используете макрос. Ниже приведен пример макроподстановки:

  i
  s/^[^ ]*/ [^ ]*/
  "add
  @a

Сначала нужно перейти в режим вставки, чтобы мы могли поместить команду в наш файл редактора. Мы печатаем команду подстановки и нажимаем ESC, чтобы закончить работу в режиме вставки. Команда подстановки говорит "В строках, которые начинаются с непустого символа, за которым следуют один или несколько символов такого же типа, поставить пробел перед непустой последовательностью символов". Далее мы печатаем "add", где "а" обозначает именованный регистр a и dd обозначает переместить строку в буфер. Теперь строка подстановки находится в буфере а. Чтобы ее выполнить мы просто напечатаем @a в командном режиме vi.

Чтобы выйти мы можем выполнить ту же последовательность действий, но поместить команду типа

  :!ps -ef

в редактор и переписать ее в буфер. Потом, когда мы говорим @a, мы входим в shell и запускаем команду ps. Команды такого типа можно помещать в именованные буферы от a-z.

Последний способ использования макросов для поддержки выхода - это через команду map. Эта команда есть в ex и адресуется предшествующим двоеточием : из vi. Синтаксис для команды map выглядит так:

  :map lhs rhs

Это устанавливает отображение левой стороны на правую сторону. Пример присвоения выглядит так:

  :map w :!who^M

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

Самое смешное начинается, когда Ваш терминал имеет функциональные клавиши. Vi обращается к функциональным клавишам по #0 -#9. Теперь мы можем зарезервировать функциональные клавиши для команд выхода. Простое присвоение будет:

  :map #1 :!ps -ef^

Каждый раз, когда Вы нажимаете функциональную клавишу F1, запускается команда ps -ef.

Обновлено: 12.03.2015