ПРИЕМЫ ПРОФЕССИОНАЛЬНОЙ РАБОТЫ В UNIX

ГЛАВА 10.

СМЕШАННЫЕ ПРИЕМЫ

СОДЕРЖАНИЕ


Введение

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


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

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


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 "\nВведите десятичное число (<> to exit): \c"
23               read DEC
24               if [ "$DEC" = ""]
25                 then  exit
26               fi
27               HEX='. dtoh'
28               echo "\n${DEC}d = ${HEX}x";;
29        2)     echo"\nBведите шестнадцатиричное число в верхнем регистре (<> to exit):  \c"
30               read HEX
31               if [ "$HEX" = ""]
32                 then  exit
33               fi
34               DEC='. htod'
35               echo "\n${HEX}x= ${DEC}d;;
36        3)    echo "\nBведите десятичное число в верхнем регистре (<> to exit): \c"
37               read DEC
38               if [ "$DEC" = ""]
39                 then  exit
40               fi
41               OCT='. dtoo'
42               echo "\n${DEC}d = ${OCT}o";;
43        4)   echo "\nBведите восьмеричное число (<> to exit): \c"
44               read OCT
45               if [ "$OCT" = ""]
46                 then  exit
47               fi
48               OCT='. otod'
49               echo "\n${OCT}o = ${DEC}d";;
50        5)   echo "\nBведите восьмеричное число (<> to exit): \c"
51               read OCT
52               if [ "$OCT" = ""]
53                 then  exit
54               fi
55               HEX='. otoh'
56               echo "\n${OCT}o = ${HEX}x";;
57        6)   echo "\nBведите шестнадцатиричное число в верхнем регистре (<> to exit): \c"
58               read НЕХ
59               if [ "$НЕX" = ""]
60                 then  exit
61               fi
62               OCT='. htoo'
63               echo "\n${HEX}x = ${OCT}o";;
64        *)     echo "\n$CHOICE-неизвестная команда";;
65        esac
66  done
Командный файл dtoh запускается, используя команду ".". Это означает: "Выполните программу, используя тот же shell". Процедура dtoh использует переменную DEC для ввода числа и выдает преобразованное число на стандартный вывод. Чтобы записать это число в переменную, мы делаем присвоение, потом запускаем программу, используя командную подстановку. Строка 28 выдает на экран первоначальное десятичное число и шестнадцатиричное, к которому оно было преобразовано.
Варианты 2, 3, 4, 5 и 6 работают аналогично. Единственное, что меняется - это имя переменной, которое соответствует типу преобразования и название командного файла (скрипта), который вызывается для этого преобразования.


Модули преобразования

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

dtoh

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

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

Синтаксис: $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 <

Описание

Зачем нам нужен dtoh?

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

Что делает dtoh?

Dtoh выполняет преобразование десятичных в шестнадцатиричные, используя существующее базовое преобразование, которое UNIX обеспечивает посредством команды bc. Манипулируя вводом и выводом команды bc, мы можем использовать ее возможности преобразования.

Пояснение

Технология, по которой все это происходит, весьма проста. Метод выполнения - используя команду языка shell ".". Это означает использовать тот же shell для запуска команды, а не дочерний. Когда команда выполняется этим же языком shell, все переменные, которые в данный момент имеют какие-то значения, доступны для командного файла dtoh. Мы используем это преимущество и предполагаем для dtoh, что Вы правильно установили переменные языка shell.
Строка 4 вызывает команду bc, которая представляет из себя калькулятор самого нижнего уровня. По умолчанию bc использует десятичный ввод и вывод. Мы устанавливаем основание выхода в 16 в строке 5. Это означает, что любое число, которое мы печатаем с этого момента, печатается как шестнадцатиричное число.
Строка 6 вводит десятичное число для bc, расширяя его значение как ввод для bс. Bc выдает на экран число, записанное в шестнадцатиричной системе счисления. Мы преобразовали число.
Единственное, что осталось сделать - это поместить результат в переменную языка shell в вызывающей программе. Но это уже сделано в вызывающей программе благодаря способу вызова процедуры. Преобразование происходит внутри bc.

Исследования

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

$ DEC="150"; (. .dtoh) > 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 <

Описание

Поскольку это и последующие средства преобразования работают аналогично dtoh, кроме представляемой специфики преобразования, мы опускаем детальные пояснения.


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" ;
$echo $OCT
Присвоить HEX шестнадцатиричное значение, преобразовать его, напечатать восьмеричное число.

Исходный текст для htoo


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


Тонкости bc

Один из важных моментов - это порядок, в котором мы изменяли основания. Сначала - большее основание, потом меньшее. После того как мы изменяем ibase на 16, все входные числа рассматриваются как шестнадцатиричные. Так что строка 6 не имеет нового представления: мы устанавливаем obase равным 8, а число 8 имеет одно и тоже значение в шестнадцатиричной и десятичной системах.
Что бы произошло, если бы мы приравняли obase к 10? Число 10 было бы интерпретировано в шестнадцатиричном смысле, а шестнадцатиричное 10 - это 16. Нам бы пришлось записать obase в шестнадцатиричной системе (основание 16, поскольку 10х входное = 16d выходное). Вам придется отслеживать подобные вещи.


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 <


Хитрости языка shell

Каждый язык программирования имеет свои собственные синтаксические "подводные камни". Нужны годы, чтобы разобраться во всех тонкостях и, кажется, что изучить их все просто невозможно. Shell в этом плане ничем не отличается от других языков.
В этом разделе мы рассмотрим некоторые моменты, о которых Вам нужно знать, если серьезно программировать на языке shell. Это очень узкие области, но они могут стать серьезным препятствием, если Вы столкнетесь с ними при написании программ.

Читайте ввод с клавиатуры, пока находитесь в цикле, присоединенном к програмному каналу

Команда чтения языка shell читает данные из стандартного ввода. В ранних версиях языка shell (изменено недавно только в System V), Вы не имели возможности перенаправить ввод для команды чтения.
Вопрос в том, как читать с клавиатуры, пока стандартный ввод присоединен к концу программного канала? Решение вопроса - это брать ввод непосредственно с клавиатуры. UNIX обеспечивает это через устройство /dev/tty. Рассмотрим следующий пример:

who | awk '{ print $1 }' | while read NAME
do
        echo  "mail to $NAME: \c"
        KB = 'Line < /dev/tty'
        if [ "$KB" = "y"]
          then mail $NAME
        fi
done
Эта программа отображает на экране список зарегистрировавшихся пользователей и спрашивает Вас, не хотите ли Вы послать сообщение по почте кому-нибудь из пользователей. Если Вы отвечаете "Y", команда почты запускается, причем ей на вход передается имя пользователя, которого Вы выбрали.
Вывод программы awk перенаправлен на стандартный ввод цикла while. Команда чтения читает из stdin, который является списком имен пользователей, полученным из выходного потока команды who. Вам выдается подсказка отправить сообщение к конкретному человеку, потом Вы переходите к команде read. Мы собираемся использовать команду Line (1). Она читает по одной строке с данного устройства. Мы вызываем команду line и перенаправляем ее стандартный ввод на специальное устройство /dev/tty. Теперь ввод идет с клавиатуры в то время как цикл все еще читает свой stdin из программного канала.
Переменная KB получает значение, которое сравнивается с "y". Если переменная имеет значение "y", то вызывается команда mail, которая в качестве параметра получает имя пользователя, записанное в данный момент в переменной NAME.

Запуск дочернего языка shell

Shell - очень многосторонняя программа. Она может работать в интерактивном, фоновом и командном режимах. Ее ввод может быть перенаправлен, подключен к программному каналу, он может быть из встроенного текста или из выходного потока присоединенной команды.
Мы хотим обратить Ваше внимание на гибкость языка shell на уровнях его выполнения. Shell может порождать другие языки shell для выполнения частей его работы. До какой глубины он может опуститься? Изменяют ли работающие дочерние языки shell выполнение и результат команд, запущенных из языков shell первого уровня? На это трудно ответить. Все, что мы можем - это опробовать некоторые вещи и посмотреть представляет ли интерес то, что мы изучили или нет. Shell может получать свой ввод со стандартного ввода, который является либо вводом с клавиатуры, либо из файла, либо из программного канала. Эту гибкость языка shell мы можем использовать в нашу пользу. Синтаксис выглядит следующим бразом: <> echo $@ | sh - Мы отображаем аргументы в командной строке и посылаем ее в программный канал. Shell в конце программного канала читает текст командной строки из stdin (определенного -) и выполняет команды. Этот метод также иллюстрируется в главе 9 в команде umntsys.
Если мы можем перенаправить стандартный ввод команды, мы можем также использовать возможность ввода языка shell "документ здесь". Используя обозначение <<, мы можем ограничить область текста и направить его в стандартный канал ввода командного файла.
Чтобы продемонстрировать цикл аналогичный предыдущему, используя только встроенный текст, рассмотрим следующий пример:

     sh << !
     $@
     !
Эта программа вызывает shell и заставляет стандартный ввод для языка shell идти из текста, ограниченного символами !. Мы передаем все из командной строки, так что это будет аналогично тому, как если бы мы печатали команды для языка shell первого уровня.

Уровни языка shell и ввод-вывод

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

     $ ls &
     1034
     $ file1
     file2
     file3
где file1, file2 и file3 - вывод команды ls. Все, что мы хотим сделать - это поместить процесс номер id (в данном случае 1034) в переменную, доступ к которой мы получим позже. Один интересный момент здесь - если Вы делаете ту же самую команду внутри командного файла, номер процесса id не печатается.
Для того, чтобы на самом деле захватить процесс с номером id, нужно проявить находчивость. Необходимо немного поэкспериментировать, чтобы найти рабочую комбинацию:

      $(ls &) 2 > 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 |
+--------------+------------------------------------------------------------------------+


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

Ранее в этой книге уже шла речь о фильтрах. Не все команды являются фильтрами или могут быть использованы в качестве фильтров. Вспомните определение фильтра - это команда, которая берет ввод из аргументов командной строки, если они есть. Иначе ввод читается из cтандартного ввода.
Почему все команды не могут действовать как фильтры? Потому, что они не предназначены для этого. Возьмем, например, ls. Что делает ls? Она выдает список файлов в текущем каталоге. Если мы говорим "ls file", он выдает информацию только для этого файла. Если мы говорим "echo file | ls", ls не дает информацию о файле, но выдает список файлов в текущем каталоге, потому что ls не cмотрит в стандартный ввод, если в командной строке нет аргументов.
Один важный момент, связанный с фильтрами - это способ их вызова. Если Вы помещаете имя файла в командную строку, фильтр открывает файл и читает данные. Помните, что фильтры хотят читать данные. Если Вы присоединяете стандартный ввод к фильтру, он думает, что то, что он читает из программного канала- это данные. Если Вы дадите фильтру имена файлов, Вы не получите того результата, который ожидаете. Давайте рассмотрим несколько примеров. Команда 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\n2\n3" | while read LINE
 8   do
 9         N=2
 10        echo "значение в цикле N = $N"
 11  done
 12
 13  echo "конечное значение N = $N"
<..>ному каналу, мы создаем дочерний 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. Cледовательно, Вы можете выйти в 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
Cначала нужно перейти в режим вставки, чтобы мы могли поместить команду в наш файл редактора. Мы печатаем команду подстановки и нажимаем ESC, чтобы закончить работу в режиме вставки. Команда подстановки говорит "В строках, которые начинаются с непустого символа, за которым следуют один или несколько символов такого же типа, поставить пробел перед непустой последовательностью символов". Далее мы печатаем "add", где "а" обозначает именованный регистр a и dd обозначает переместить строку в буфер. Теперь строка подстановки находится в буфере а. Чтобы ее выполнить мы просто напечатаем @a в командном режиме vi.
Чтобы выйти мы можем выполнить ту же последовательность действий, но поместить команду типа

     :!ps -ef
в редактор и переписать ее в буфер. Потом, когда мы говорим @a, мы входим в shell и запускаем команду ps. Команды такого типа можно помещать в именованные буферы от a-z.
Последний способ использования макросов для поддержки выхода - это через команду map. Эта команда есть в ex и адресуется предшествующим двоеточием : из vi. Cинтаксис для команды map выглядит так:

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

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

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


Команда "One-Liners" - крошечная, но мощная

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

ACCTCOM


* прочитать всю Вашу учетную информацию, начиная с последней команды.

  acctcom  -b  -u$LOGNAME

* показать все учетные записи,  запущенные с Вашего терминала и того,
  который запущен как суперпользователь

  acctcom  -u# -l'tty'

BANNER


* напечатать сообщение на трех строках

  banner "line 1" "line2" "line3"

* напечатать день недели и дату на одной строке, время на другой

  banner "'date|cut  -d' ' -f1,3'" "'date|cut -d''-f4'"

* послать сообщение на экран другого пользователя

  banner "Привет" "там" > /dev/tty01

BASENAME


* очистить путь

  echo "Я за устройством 'basename\'tty\' ' ''

BC


* передать  формулу  на  вход  команде  bc,  которая должна выполнить
  умножение и присвоит результат PROD

  PROD = 'echo $NUM1 * $NUM2 | bc'

CAT


 *  передать символы с клавиатуры в файл

    cat > file   (печатать пока не встретится символ ^D
                  для прекращения чтения)

 *  получить ввод из конструкции "документ здесь"
    cat << -!
               Это образец текста, который печатается на экране !

CC


* множественная компиляция в фоновом режиме из одной командной
  строки

  cc file1.c & cc file2  & cc file3.c &

CD


* перейти в каталаг, в котором находится файл

  cd 'имя директория\'путь файл\''

* перейти в каталог, который записан в переменной

  DESTINATION="/usr/bin"
  cd $DESTINATION

* перейти в каталог, записанный в файле

  cd 'cat dest_file'

CHMOD


* включить бит исполнения файла

  chmod  +x file

* включить бит смены идентификатора пользователя и разрешить
  всем выполнение файла

  chmod 4755 file

* установить бит sticky во включенное состояние

 chmod 1755 file

CHOWN


* установить себя владельцем файла

  chown $LOGMAME files

* то же самое другим способом

  chown 'who am i| cut -d' '-f1' files

* изменить право собственности для дерева

  cd dest
  find  . -print | sort | while read FILE
  do
     chown russ $FILE
  done

CP


 * скопировать три уровня файлов в один вкаталог /tmp
   cp */*/* /tmp

 * то же самое другим способом
   cp 'find . -type f -print' /tmp

 * переключить пользовательское предложение
   cp -z

CPIO


  * переместить дерево системы файлов в новое местоположение
    cd $SRC
    find  .-print | sort | cpio -pdv $DEST

  * скопировать дерево системы файлов на гибкий диск
    cd $HOME
    find  .-print | sort | cpio -ocBv > /dev/rfd0

  * восстановить копию на стримере
    cd $DEST
    cpio -icBvt < /dev/rfd0

  * выполнить "ls -l" на копии стримера
    cpio -icBvt < /dev/rfd0

CRON


 * запустите Ваш генератор сообщений о статусе каждый  четверг
   в 6:00 a.m.
   0 6 * * 4 /usr/russ/bin/status_msg

 * chmod на файл паролей

  * * * * *  /bin/su root -c "chmod 777 /etc/passwd"

CU


 * непосредственно обратиться  последовательному  порту  на  скорости
   1200 бод

   cu  -ltty00 dir

 * непосредственно  обратиться  к последовательному порту на скорости
   9600 бод

   cu -ltty00 -s9600 dir

 * автоматически  вызвать  другую   систему,   используя   комбинацию
   dial/modem

   cu -acua0 555-1212

CUT


 * отсечь первое поле из файла passwd

   cut -d:  -f1 /etc/passwd

 * отсечь имя из листинга команды who

   who | cut -d' ' -f1
   who | awk '{print $1}

DD


 * полная гибкая копия дорожка за дорожкой

   dd if = /dev/fd0 of=/dev/fd1

DOS


 * скопировать все файлы данного каталога на дискету DOS

   doscp * a:

 * скопировать все файлы с дискеты DOS в данный каталог
   dosls a: > /tmp/dosf
   for FILE in 'cat /tmp/dosf'
   do
         doscp  a:$FILE
   done

DU


 * выдать общее количество блоков для всех каталогов в /

   du -s /*

 * напечатать количество использованного места в каталоге
   каждого пользователя

   echo "total bytes: 'expr\'du -s $1\' \* 512'"

ECHO


 * напечатать значение переменной shell'а
   echo  $PATH  $CDPATH

 * напечатать вывод вперемешку с обычным текстом

   echo  " Мое имя  $LOGNAME или
   'logname' или  'who am i|cut -d'  ' -f1'"

 * напечатать символы упраления в кавычках и без

   echo "\n\t Это записано в кавычках"
   echo \\n\\t Это записано без кавычек

 * напечатать и оставить курсор в конце той же строки

   echo -n "prompt: "
   echo  "prompt: \c"

ED


 * запустить ed автоматичски с конструкцией
   "документ здесь"

   ed  /etc/passwd <<-!
        1,$p
        g/root/s//noroot
        w
        q
   !

EXPR


 * умножить два числа
   expr 512 \*  1024

 * увеличить переменную на предопределенное значение

   x = 0; INC = 5
   X='expr $X + $INC'

FILE


 * найти все текстовые файлы
   file * | fgrep text

 * напечатать имена только текстовых файлов
   file * | fgrep text | cut -d: -f1

 * more все текстовые файлы
   more 'file * | fgrep text | cut -d: -f1'

FIND


 * найти все файлы в системе
   find / -print | sort

 * найти все файлы и распечатать список в формате long
   find / -exec ls -ld {} \;

 * напечатать имена всех регулярных файлов
   find / -type f print

 * найдите все каталоги и распечатайте содержимое
   find / -type d print | while read DIR
   do
         echo "listing $DIR"
         ls $DIR
   done

 * найдите все файлы, которые были модифицированы в последние
   24 часа и распечатайте их список в формате long
   find / -atime -0 -exec ls -ld {} \;

 * найдите все файлы setuid и setgid
   find / -perm -4000 -o -perm -2000  -exec ls -ld {} \;

FINGER


  * укажите всех пользователей, вышедших из системы
    finger 'who | cut -d' ' -f1'

  * укажите всех пользователей в файле passwd
    cut -d: -f1 /etc/passwd | while read NAME
    do
       finger $NAME
    done

GREP


  * найти случаи употребления шестнадцатиричных чисел в файле
    данных
    od -x datafile | grep 'A3F09'

  * найти свое имя в системе
     find / -type f print | while read FILE
     do
        grep  "russ" $FILE /dev/null
     done

HEAD


  * озаглавьте все текстовые файлы в текущем каталоге
    file * | fgrep text | cut -d: -f1 | while read FILE
    do
       echo "--------"
       echo "$FILE"
       echo "--------"
       head "$FILE"

ID


  * определить, кто в данный момент является суперпользователем
    if [ "'id'" = "uid=0(root) gid=0(root)" ]
     then echo "you are root"
    fi

  * то же самое другим способом
    if id | fgrep root > /dev/null
     then echo "you are root"
    fi

KILL


  * уничтожьте себя (выгрузите)
    kill -9 0
    kill -9 $$

  * завершите работу системы
    kill -1 1

  * уничтожьте последний процесс, запущенный в фоновом режиме
    kill -9 $!

  * уничтожьте процесс, идентификатор которого находится в файле
    kill -9 'cat idfile'

LINE


  * взять строку с терминала
    LINE='line < /dev/tty'

  * взять строку из стандартного ввода
    cat datafile | while LINE = 'line'
    do
       echo $LINE
    done

LOGIN


 * перейти из сгенерированной подсказки login ???

   login: ^d
   login:

 * получить некоторую внутреннюю информацию
  (программа strings - это BSD)

   strings /bin/login | more

LOGNAME


  * напечатать информацию о своем пароле
    grep '^'logname ':' /etc/passwd

  * получить информацию о своем процессе
    ps -fu  'logname'

LS


  * выдать список скрытых файлов
    ls -ad .*

  * выдать размер файла в байтах
    ls -l file

  * выдать размер файла в блоках
    ls -s file

  * выдать информацию о правах доступа по записи всех
    зарегестрировавшихся в системе
    ls -li 'who |sed "s/^[^ ]* *\([^ ]*\) .*$/\/dev\/\1/p"'

  * получить помощь по испоьзованию команды
    ls -z

  * выдать список только каталогов
    ls -al |grep "^d"

MAIL


  * послать почту всем пользователям

    cut -d: -f1 |while read USER
    do
           echo "mailing to $USER"
           mail $USER
    done

  * послать почту из файла
    mail russ < /etc/passwd

  * послать почту из программного канала
    echo "Это текст почты" | mail russ

MORE


 * напечатать все файлы текущего каталога
   more *

 * напечатать 10 строк за раз
   more -10 file
   cat file | more -10

MKDIR


  * опуститься на максимальную глубину
    while :
    do
       mkdir x
       cd x
    done

  * то же самое другим способом
    PATH="x"
    while :
    do
       mkdir $PATH
       PATH="$PATH/x"
    done

NCHECK


  * найти все файлы, присоединенные к vi
    ls -li /bin/vi
    40 -rwwx--x--t  1109344 Feb  14  1985 /bin/vi
    ncheck  -i  40  /dev/root

  * найти все файлы установки идентификатора пользователя
    ncheck  -s

NM


  * посмотреть символьные таблицы всех nonstripped
    исполняемых файлов

  nm 'file *| grep "not stripped"|sed "s/^\(.*\):.*$/\1/"

OD


  * посмотреть символы в именах файлов в текущем каталоге
    od -c .

  * напечатать значение функциональных клавиш, комбинаций
    клавиш, и.т.д.

    od -cb  (нажмите комбинацию клавиш)

    ^d   (печатает строку)
         (нажмите что-нибудь еще)
    ^d   (печатает следующую строку)
    ^d   (выыходит из od)

  * cделать дамп копии на стримере
    od  -c /dev/rfd0

  * сделать дамп файловой системы
    od -c /dev/root

PASSWD


  * как суперпользователь Вы  можете  установить  в  качестве  пароля
    любую строку

    # passwd russ
    Changing password for russ
    (Изменение пароля для russ)
    Enter new password (minimum of 5 characters)
    (Введите новый пароль (минимум 5 символов))
    Please use combination of upper, lowercase letters
    and numbers
    (Просьба использовать комбинации чисел и букв в
     верхнем и нижнем регистрах)
    New password: junk
    (Новый пароль: junk)
    Re-enter new password: junk
    (Новый пароль: junk)
     #

  * как обычный пользователь Вы должны будете вводить пароль с учетом
    количественных ограничений и ограничений по длине

     $ passwd russ
     Changing password for russ
     (Изменение пароля для russ)
     Enter new password (minimum of 5 characters)
     (Введите новый пароль (минимум 5 символов))
     Please use combination of upper, lowercase letters
     and numbers
     (Просьба использовать комбинации чисел и букв в
      верхнем и нижнем регистрах)
     New password: junk
     (Новый пароль: junk)
     Too short. Password unchanged.
     (Слишком короткий. Пароль не изменен)
     $

PR


  * вывести многоколоночный список имен файлов
    ls $@ | pr -5t
  * напечатать файлы из списка
    pr 'find . -name "*.c" -print | sort'

PS


  * напечатать полную информацию обо всех активных процессах
    ps   -aef

  * напечатать информацию обо всех процессах, управляемых
    Вашим терминалом
    ps  -f

  * напечатать информацию о процессах, связанных с терминалом tty00
    ps  -ft00

  * напечатать информацию о процессах, связанных с пользователем russ
    ps  -furuss

  * BSD синтаксис для печати всех процессов
    ps  -aux

  * BSD синтаксис для печати всех процессов, связанных с
    терминальным устройством
    ps -xut00

PWD


  * сохранить текущий рабочий каталог
    PWD='pwd'

  * вернуться в ранее сохраненный рабочий каталог
    cd $PWD

RM


  * удалить все файлы, кроме каталогов с файлами
    rm *

  * удалить пустые каталоги
    rmdir dirs

  * удалить каталоги, имеющие файлы
    rm -r dirs

  * удалить все файлы в режиме, когда система не будет
    задавать никаких вопросов
    rm  -rf *

  * удалить каждый файл в системе по отдельности
    rm  -rf /

SH


  * прочитать список поэлементно

    for ELEMENT in 'cat /etc/motd'
    do
         echo $ELEMENT
    done

  * прочитать список построчно
    cat /etc/motd | while read LINE
    do
       echo  $LINE
    done

  * цикл пока - навсегда (while-forever)
    while :
    do
       echo  $PS1
       read  CMD
       case  $CMD in
       "")  break;;
       esac
    done

  * управляемый цикл while
    read CMD
    while [  "$CMD"  != ""]
    do
       case  $CMD in
       user-cmd) do_it;;
       esac
       echo  $PS1
       read CMD
    done

  * переполнениие тестового стека при обработке
    прерывания
    trap  "echo trapping; kill $$"  2 3 15

  * выгрузка из языка shell несколькими способами

    exit
    eof character (usually control -d)
    kill  -9 0
    kill  -9 $$

STTY


  * посмотрите все свои установки
    stty  -a

  * посмотрите терминальные установки
    другого терминала
    stty  -a < /dev/tty01

  * установить передачу бод на другую скорость для
    другого терминала
    stty 300 < /dev/tty01

  * динамически установить control-A как клавишу
    прерывания
    stty intr ^a

  * включить эхо-сопровождение терминала
    stty -echo

SU


  * тестовый цикл для уничтожения легких паролей

  awk '{FS =":"; print $1,$5} '/etc/passwd|while read N C
  do
      echo "\n$N\t$C"
      su $N
  done

TAIL


  * проследить в реальном времени запись транзакций файла
    входа в систему (logfile) uucp

    tail -f /usr/spool/uucp/LOGFILE

  * посмотреть последнюю строку файла
    tail -1 file

  * посмотреть последние 10 символов переменной
    echo "$VAR" | tail  -10c

TAR


  * сделать копии файлов в Вашем home-каталоге не разрывая  файл,  но
    формируя копии на куски по 1200 блоков
    cd
    tar cvefbk  /dev/rfd0 10 1200 .

  * выполнить команду "ls-l" для копий файлов
    tar  tvf /dev/rfd0

  * восстановить копии файлов
    cd $DEST
    tar xvf /dev/rfd0

  * скопировать файлы в tar , отсортировав их
    tar cvfk  /dev/rfd0 1200  'find  . -print | sort'

TEE


 * отправьте свой вывод на экран другого терминала
   sh | tee /dev/tty01

 * захватите вывод других команд
   fsck /dev/root | tee capture
   cu -ltty00 dir | tee capture

TEST


 * проверьте эквивалентность двух строк
   test "$S1" = "$S2"

 * проверьте эквивалентность двух чисел
   test "$N1" -eq "$N2"

 * то же самое другим способом
   (заметьте что /bin/[присоединен к /bin/test)

  [ "$S1"  =  "$S2" ]
  [ "$N1" -eq "$N2" ]

TOUCH


 * сделайте текущим доступ и измените время всех файлов в Вашем
   home каталоге

   find $HOME -exec touch {} \;
   find $HOME -print | while read FILE
   do
      touch $FILE
   done

TTY


 * показать права доступа по записи на Вашем терминале
   ls -l 'tty'

 * включить  и отключить доступ пользователей к Вашему терминалу
   chmod 666 'tty'
   chmod 600 'tty'

UUCP


 * скопировать  имена  всех  файлов в файл в общедоступный каталог на
   другой системе

   for FILE in 'cat datafile'
   do
         echo "копирование $FILE"
         uucp $FILE  sys! ~/user
   done

 * поставить файл в очередь,  не инициировать вызов,  взять  файл  из
   Вашего  первоначального  каталога,  не  копирровать  его  в  spool
   каталог.
   uucp -r  -c file sys!/tmp

VI


  * выполните текущую строку как команду языка shell
    :.w !sh -

  * то же самое другим способом, используя  макрос
    "ayy
     @a

  * выйти непосредственно в shell
    :sh

  * скомпилировать текущий файл
    :!sh

  * запустить имя текущего файла как комаду языка shell
    :!cc %

  * запустить еще раз последнюю команду
    :!%

  * запустить команду и поместить ввывод на текущую
    строку (переписать)
    :.!who am i

  * запустить команду и поместить ввывод на новую строку
    :.r !who am i
    :r !who am i

  * отредактировать файл, который находится где-то в
    системе
    :e  'path termcap'

  * поместить long листинг файла, который находится
    где-то в файле редактора
    :.!ls  -l 'path init'

WC


  * печатает количество человек, зарегистрированных в системе
    echo "Всего 'who | wc -l' человек вошло в систему"

  * печатает количество сторк во всех исходных файлах
    find /usr/src -name "*.c" -exec cat {} \; | wc -l

WHO


  * печатает количество и имена зарегистрированных
    пользователей
    who | awk '{ print "user:",$1,"\tdevice:",$2
                 cnt = cnt + 1
               } END { print cnt,"пользователи, вышедшие из системы"}'

  * печатает
    who | while read NAME TTY TIME
    do
      echo "пользователь: $NAME  tty: $TTY  time: $TIME
    done