/bin
или /usr/bin
, будут недоступны для overwrite
.Теперь команда
overwrite
выполняется верно (хотя и она получилась несколько громоздкой):$ cat notice
Unix is a Trademark of Bell Laboratories
$ overwrite notice sed 's/UNIXUNIX(TM)/g' notice
command garbled: s/UNIXUNIX(TM)/g
overwrite: sed failed, notice unchanged
$ cat notice
UNIX is a Trademark of Bell Laboratories
He изменился$ overwrite notice sed 's/UNIX/UNIX(TM)/g' notice
$ cat notice
UNIX(TM) is a Trademark of Bell Laboratories
$
Типичной задачей является использование редактора
sed
для замены всех вхождений одного слова на другое слово. Имея под рукой команду overwrite
, легко написать программу на языке shell
для ее решения:$ cat replace
# replace: replace str1 in files with str2, in place
PATH=/bin:/usr/bin
case $# in
0|1|2) echo 'Usage: replace str1 str2 files' 1>&2; exit 1
esac
left="$1"; right="$2"; shift; shift
for i do
overwrite $i sed "s@$left@$right@g" $i
done
$ cat footnote
UNIX is not an acronym
$ replace UNIX Unix footnote
$ cat footnote
Unix is not an acronym
$
(Вспомните: если список в цикле
for
пуст, то по умолчанию он равен $*
.) Мы использовали @
вместо /
для разбиения в команде подстановки, поскольку менее вероятно, что @
вступит в конфликт с входной строкой. Команда replace
устанавливает PATH
равным /bin:/usr/bin
, исключая $HOME/bin
. Это означает, что overwrite
должна находиться в /usr/bin
, чтобы команда replace
сработала. Мы сделали такое предположение для простоты; если вы не можете поместить overwrite
в /usr/bin
, вам придется добавить $HOME/bin
к PATH
в команде replace
или явно задать полное имя overwrite
. В дальнейшем будем полагать, что команды, которые мы создаем, находятся в /usr/bin
, где им и следует быть.Упражнение 5.17Почему команда
overwrite
не использует сигнал 0 в команде trap
, чтобы файлы удалялись при выходе из нее? Подсказка: попробуйте нажать клавишу DEL во время выполнения следующей программы:trap "echo exiting; exit 1" 0 2
sleep 10
Упражнение 5.18Добавьте флаг
-v
к команде replace
для вывода всех измененных строк на /dev/tty
.Подсказка:
s/$left/$right/g $vflag
.Упражнение 5.19Увеличьте надежность команды
replace
, чтобы ее выполнение не зависело от символов в строке замены.Упражнение 5.20Можно ли использовать
replace
для замены i
на index
всюду в программе? Какие вы внесли бы изменения, чтобы добиться этого?Упражнение 5.21Достаточно ли команда
replace
эффективна и удобна, чтобы находиться в каталоге /usr/bin
? Не лучше ли вводить по мере необходимости подходящие команды редактора sed
(да или нет)? Обоснуйте свой ответ.Упражнение 5.22(Усложненное.) Команда
$ overwrite файл 'who | sort'
не выполняется. Объясните причину этого и исправьте ее. Подсказка: посмотрите
eval
в справочном руководстве по sh(1)
. Как ваше решение повлияет на интерпретацию специальных символов в команде?5.6 Команда zap
: уничтожение процесса по имени
Команда
kill
только завершает процесс с указанным номером. Если нужно уничтожить определенный фоновый процесс, обычно приходится выполнить команду ps
, чтобы узнать номер процесса, а затем ввести этот номер в качестве аргумента для команды kill
. Однако нелепо иметь программу, выдающую номер процесса, который сразу же передается вручную другой программе. Имеет смысл написать программу, скажем zap
, для автоматического выполнения такой работы. Здесь, правда, есть одно препятствие: уничтожение процессов опасно, поэтому следует принять меры для обеспечения сохранности нужных процессов. Хорошей защитой всегда служат диалоговое выполнение zap и использование команды pick
для выбора "жертв".Кратко напомним вам о команде
pick
: она выдает поочередно свои аргументы, спрашивая ответ у пользователя; если ответ — y
, то аргумент выводится (команда pick
обсуждается в следующем разделе). В нашем случае pick
используется для подтверждения, что процессы, выбранные по имени, — именно те, которые пользователь хочет уничтожить:$ cat zap
# zap pattern: kill all processes matching pattern
# BUG in this version
PATH=/bin:/usr/bin
case $# in
0) echo 'Usage: zap pattern' 1>&2; exit 1
esac
kill `pick \`ps -ag | grep "$*"\` | awk '{print $1}'`
Обратите внимание на вложенные знаки слабого ударения, защищенные символами обратной дробной черты,
awk
программа выделяет номер процесса из выходных данных команды ps
, выбранной с помощью pick
:$ sleep 1000 &
2216
$ ps -ag
PID TTY TIME CMD
...
2216 0 0:00 sleep 1000
...
$ zap sleep
2216?
0? q
Что происходит?$
Проблема состоит в том, что выходные данные команды
ps
разбиты на слова, которые воспринимаются и обрабатываются командой pick
как отдельные аргументы вместо того, чтобы обрабатываться сразу по строке. Обычная процедура интерпретатора заключается в разбиении строк на аргументы с границами пробел/не пробел, как показано ниже:for i in 1 2 3 4 5
В этой программе нужно контролировать процесс разбиения интерпретатором строк на аргументы, чтобы только символ перевода строки разделял соседние "слова".
Внутренняя переменная интерпретатора
IFS
(internal field separator — внутренний разделитель полей) представляет собой строку символов, которая разделяет слова в списке аргументов, находящихся в знаках слабого ударения или циклах for
. Обычно IFS
содержит пробелы, символы табуляции и конца строки, но мы можем заменить ее на что-либо нужное, например просто на символ перевода строки:$ echo 'echo $#' >nargs
$ cx nargs
$ who
you tty0 Oct 1 05:59
pjw tty2 Oct 1 11:26