$ nargs 'who'
10
10 полей, разделенных пробелом и концом строки$ IFS='
'
Только конец строки$ nargs `who`
2
Две строки, два поля$
После установки
IFS
равным символу перевода строки команда zap
выполняется отлично:$ cat zap
# zap pat: kill all processes matching pat
# final version
PATH=/bin:/usr/bin IFS='
' # just a newline
case $1 in
"") echo 'Usage: zap [-2] pattern' 1>&2; exit 1 ;;
-*) SIG=$1; shift
esac
echo ' PID TTY TIME CMD'
kill $SIG `pick \`ps -ag | egrep "$*"\` | awk '{print $1}`"
$ ps -ag
PID TTY TIME CMD
...
2216 0 0:00 sleep 1000
...
$ zap sleep
PID TTY TIME CMD
2216 0 0:00 sleep 1000? y
2314 0 0:02 egrep sleep? N
$
Мы здесь кое-что добавили: необязательный аргумент, обозначающий сигнал (обратите внимание на то, что
SIG
будет неопределенным, а значит, должен рассматриваться как пустая строка, если аргумент не задан), а также egrep
вместо grep
, чтобы разрешить более сложные шаблоны типа 'sleep | date'
. Первая команда echo
выдает столбец из заголовков выходных данных команды ps
.Вас может заинтересовать, почему эта команда называется
zap
, а не просто kill
. Основная причина заключается в том, что в отличие от случая с командой cal
мы не даем действительно новой команды kill
: zap
по необходимости является диалоговой командой, с одной стороны, а с другой — мы хотим сохранить имя kill
для настоящей команды. К тому же zap
чрезвычайно медленна из-за накладных расходов на все дополнительные программы, хотя самую длинную по времени реализации команду ps
все равно нужно выполнять. В следующей главе будет продемонстрировано более эффективное решение.Упражнение 5.23Измените команду
zap
так, чтобы она, выдавая заголовки из команды ps
, была не чувствительна к изменениям в формате вывода ps
. Насколько это усложнит программу?5.7 Команда pick
: пробелы или аргументы
Вы уже достаточно подготовлены для того, чтобы написать команду
pick
на языке shell
. Единственным новым средством является механизм чтения входного потока пользователя. Встроенная команда интерпретатора read
читает одну строку текста из стандартного входного потока и присваивает ее (без перевода строки) в качестве значения указанной переменной:$ read greeting
hello, world
Вводим новое значение для приветствия$ echo $greeting
hello, world
$
Самым типичным примером использования команды
read
в файле .profile
служит установка значений переменных среды при входе в систему, прежде всего установка переменных интерпретатора типа TERM
.Команда
read
может читать только из стандартного входного потока; его нельзя даже переключить. Ни одну из встроенных команд интерпретатора (в отличие от основных структур управления типа for
) нельзя переключить с помощью операций >
или <
:$ read greeting
goodbye
Тем не менее надо ввести значениеillegal io
Сейчас shell сообщает об ошибке$ echo $greeting
greeting получает введенное значение,goodbye
а не значение из файла$
Это можно считать ошибкой интерпретатора, но такова жизнь. К счастью, можно предусмотреть переключение в цикле, охватывающем команду
read
, что является основным принципом реализации команды pick
:# pick: select arguments
PATH=/bin:/usr/bin
for i # for each argument
do
echo -n "$i? " >/dev/tty
read response
case $response in
y*) echo $i ;;
q*) break
esac
done
Обращение
echo -n
подавляет заключительный символ перевода строки, так что переменную response
можно вывести на той же строке, что и приглашение. Конечно, приглашения выдаются на устройство /dev/tty
, поскольку стандартный выходной поток, по всей вероятности, не выводится на терминал.Оператор
break
заимствован из языка Си: он завершает выполнение самого внутреннего цикла, в нашем случае for
, когда вводится q
. Мы выбрали символ q
как сигнал прекращения процесса выбора потому, что это легко сделать, потенциально удобно и не противоречит другим программам.Интересно поэкспериментировать с пробелами в аргументах для команды
pick
:$ pick '1 2' 3
1 2?
3?
$
Если вы хотите узнать, как команда
pick
читает свои аргументы, запустите ее и нажмите клавишу RETURN после каждого приглашения. В том виде, в каком написана эта команда, она выполняется отлично: в цикле for i
аргументы обрабатываются правильно. Мы могли бы написать цикл другими способами:$ grep for pick
Выясните, что делает эта версияfor i in $*
$ pick '1 2' 3
1?
2?
3?
$
Эта версия не работаете поскольку операнды в цикле снова распознаются, а наличие пробелов в первом аргументе приводит к тому, что он разбивается на два аргумента. Попробуйте взять в кавычки
$*
:$ grep for pick
Попробуем другую версиюfor i in "$*"
$ pick '1 2' 3
1 2 3?
$
Такая версия тоже не работает, поскольку "
$*
" является единым словом, которое образовано из всех аргументов, объединенных вместе с разделяющими пробелами. Но решение все-таки есть (это почти черная магия): строка трактуется особым образом интерпретатором и преобразуется в нужное число аргументов для командного файла:$ grep for pick
Попробуем третью версиюfor i in "$@" '
$ pick '1 2' 3
1 2?
3?
$
Строка
$@
, не взятая в кавычки, идентична $*
; она обрабатывается иначе, только если заключена в кавычки. Мы использовали ее в команде overwrite
, чтобы сохранить аргументы для команды пользователя.В итоге мы можем сформулировать следующие правила: