Книга: UNIX — универсальная среда программирования
5.9 Команды get и put: контроль изменении файла
Разделы на этой странице:
5.9 Команды get
и put
: контроль изменении файла
В последнем разделе этой длинной главы мы приведем большой и более сложный пример, в котором продемонстрируем вам взаимодействие языков shell
, awk
и sed
.
Программа развивается по мере того, как мы устраняем ошибки и добавляем в нее новые средства. Иногда полезно сохранять ее разные версии, особенно в ситуации, когда кто-то переносит программу на другую машину, и возникает вопрос: "Что изменилось с тех пор, как мы получили версию вашей программы?" или "Как вы устранили такие- то ошибки?" К тому же наличие копий упрощает эксперимент: если у вас что-либо не получилось, то можно безболезненно вернуться к исходной программе.
Одно из решений состоит в том, чтобы хранить копии всех версий программы, но это трудно организовать и, кроме того, требует большого объема памяти на диске. Мы же будем основываться на подобии последовательных версий, что позволяет хранить только их общую часть. Команда
$ diff -е old new
порождает список команд редактора ed
, преобразующих файл old
в new
. Таким образом, можно хранить все версии на базе одного файла, сохраняя одну полную версию и множество команд редактирования, преобразующих ее в любую другую версию.
Существуют два очевидных решения: хранить целиком последнюю версию и иметь команды редактирования для возврата к старым версиям или хранить самую старую версию и иметь команды редактирования для перехода к новым. Хотя второе решение чуть проще запрограммировать, первое предпочтительнее, поскольку из множества версий почти всегда интереснее выбрать новые.
Мы рассмотрим первое решение. В едином файле, называемом файлом истории, хранится текущая версия, за которой следует множество команд редактирования, преобразующих каждую версию в предыдущую (т.е. более старую). Любой набор команд редактирования начинается такой строкой:
@@@ пользователь дата сводка
Сводка — это одна строка, которая вводится пользователем и описывает изменения.
Для работы с версиями используются две команды: get
выделяет версию из файла истории, a put
заносит новую версию в файл истории после запроса на ввод сводки изменений. Прежде чем привести программу, покажем, как выполняются get
и put
и как сохраняется файл истории:
$ echo строка текста > junk
Введите описание
$ put junk
Summary: создадим новый файл
get: no file junk.H
Файл-история не существует
put: creating junk.H
…и put создает его
$ cat junk.H
сделаем новый файл
строка текста
@@@ you Sat Oct 1 13:31:03 EDT 1983
$ echo еще строка >>junk
$ put junk
Summary: одна строка добавлена
$ cat junk.H
строка текста
еще одна строка текста
@@@ you Sat Oct 1 13:31:28 EDT 1983 одна строка добавлена
2d
@@@ you Sat Oct 1 13:31:03 EDT 1983 сделаем новый файл
$
Команды редактирования представляют собой одну строку 2, которая исключает вторую строку файла, преобразуя новую версию в исходную:
$ rm junk
Самая новая версия
$ get junk
$ cat junk строка текста еще строка текста
Версия новейшая, но одна
$ get -l junk
$ cat junk
строка текста
Опять самая новая версия
$ get junk
$ replace еще 'другая' junk
Изменим ее
$ put junk
Summary: изменена вторая строка
$ cat junk.H
строка текста
другая строка
@@@ you Sat Oct 1 13:34:07 EDT 1983 одна строка добавлена
2d
@@@ you Sat Oct 1 13:31:03 EDT 1983 создадим новый файл
$
Для получения нужной версии файла в файле истории записаны команды редактирования. Первая группа команд преобразует самую последнюю версию в предыдущую, вторая группа преобразует предыдущую в пред-предыдущую версию и т.д. Таким образом, мы преобразуем новый файл в его старую версию, запуская каждый раз редактор ed
.
Очевидно, может возникнуть проблема, если в изменяемом файле есть строки, начинающиеся с трех символов. Кроме того, в разделе ошибок описания команды diff(1)
(см. справочное руководство по UNIX) есть предупреждение о строках, состоящих из одной точки. Мы выбрали @@@
для разделения команд редактирования, поскольку такая строка является редкостью для обычного текста.
Конечно, было бы полезно показать здесь процесс развития команд put
и get
, но из-за ограниченного объема книги мы приведем только их окончательные варианты. Команда put
проще команды get
:
# put: install file into history
PATH=/bin:/usr/bin
case $# in
1) HIST=$1.H ;;
*) echo 'Usage: put file' 1>&2; exit 1 ;;
esac
if test ! -r $1
then
echo "put: can't open $1" 1>&2
exit 1
fi
trap 'rm -f /tmp/put.[ab]$$; exit 1' 1 2 15
echo -n 'Summary: '
read Summary
if get -o /tmp/put.a$$ $1 # previous version
then # merge pieces
cp $1 /tmp/put.b$$ # current version
echo"@@@ `getname` `date` $Summary" >>/tmp/put.b$$
diff -e $1 /tmp/put.a$$ >>/tmp/put.b$$ # latest diffs
sed -n '/^@@@/,$p' <$HIST >>/tmp/put.b$$ # old diffs
overwrite $HIST cat /tmp/put.b$$ # put it back
else # make a new one
echo "put: creating $HIST"
cp $1 $HIST
echo "@@@ `getname` `date` $Summary" >>$HIST
fi
rm -f /tmp/put.[ab]$$
После считывания одной строки сводки команда put
обращается к get
для получения предыдущей версии файла из файла истории. Флаг -о
команды get
указывает на переключение выходного файла. В том случае, когда get
не может найти файл истории, она возвращает код завершения ошибки, и put
создает файл истории. Если файл истории существует, то в командах после then
создается временный файл такого формата: самая последняя версия, строка @@@
, команды редактора для преобразования этой версии в предыдущую, старые команды редактора и строки В конце временный файл копируется в файл истории с помощью команды overwrite
.
Команда get
в отличие от put
включает флаги:
# get: extract file from history
PATH=/bin:/usr/bin
VERSION=0
while test "$1" != ""
do
case "$1" in
-i) INPUT=$2; shift ;;
-o) OUTPUT=$2; shift ;;
-[0-9]) VERSION=$1 ;;
-*) echo "get: Unknown argument $i" 1>&2; exit 1 ;;
*) case "$OUTPUT" in
"") OUTPUT=$1 ;;
*) INPUT=$1.H ;;
esac
esac
shift
done
OUTPUT=${OUTPUT?"Usage: get [-o outfile] [-i file.H] file"}
INPUT=${INPUT-$OUTPUT.H}
test -r $INPUT || { echo "get: no file $INPUT" 1>&2; exit 1; }
trap 'rm -f /tmp/get.[ab]$$; exit 1' 1 2 15
# split into current version and editing commands
sed <$INPUT -n '1,/^@@@/w /tmp/get.a'$$'
/^@@@/,$w /tmp/get.b'$$
# perform the edits
awk </tmp/get.b$$ '
/^@@@/ { count++ }
!/^@@@/ && count > 0 && count <= - "$VERSION"
END { print "$d"; print "w", "'$OUTPUT'" }
' | ed - /tmp/get.a$$
rm -f /tmp/get.[ab]$$
Флаги выполняют обычные функции: -i
и -о
задают переключение входного и выходного потоков, — -[0-9]
определяет версию: -0
— новая версия (значение по умолчанию), -1
— предыдущая версия и т.д.). Цикл по аргументам организуется с помощью команд while
, test
и shift
, а не с помощью for
, поскольку некоторые флаги (-i
, -о
) используют еще один аргумент, и поэтому нужно сдвигать их командой shift
, которая плохо согласуется с циклом for
, если она находится внутри него. Флаг редактора ed
отключает вывод числа символов, обычный при чтении и записи в файл.
Строка
test -r $INPUT || {echo "get: no file $INPUT" 1>&2; exit 1;}
эквивалентна конструкции
if test ! -r $INPUT
then
echo "get: no file $INPUT" 1>&2
exit 1
fi
(такую конструкцию мы использовали в команде put
), но запись ее короче, и она понятнее программистам, хорошо знакомым с операцией ||
. Команды, заключенные между {
и }
, выполняются не порожденным, а исходным интерпретатором. Это необходимо для того, чтобы команда exit
обеспечивала выход из get
, а не из порожденного интерпретатора. Символы {
и }
подобны do
и done
— они приобретают специальные значения, если следуют за точкой с запятой, символом перевода строки или другим символом завершения команды.
В заключение мы рассмотрим те команды в get
, которые и решают задачу. Вначале с помощью редактора sed
файл истории разбивается на две части, содержащие самую последнюю версию и набор команд редактирования. Затем в awk
-программе обрабатываются команды редактирования. Строки @@@
подсчитываются (но не печатаются), и до тех пор, пока их число не превышает номера нужной версии, команды редактирования пропускаются (напомним, что действие, принятое по умолчанию, в awk
-программе сводится к выводу входной строки). К командам редактирования из файла истории добавлены еще две команды ed
: $d
удаляет одну строку @@@
, которую редактор sed
оставил в текущей версии, а команда w
помещает файл в отведенное ему место. Команда overwrite
здесь не нужна, поскольку в get
изменяется только версия файла, а не сам файл истории.
Упражнение 5.29
Напишите команду version
, выполняющую два задания:
$ version -5 файл
выдает сводку изменений, дату изменения и имя пользователя, произведшего изменения выбранной из файла истории версии, а
$ version sep 20 файл
выдает номер версии, являющейся текущей 20 сентября. Типичное использование этой возможности ясно из обращения:
$ get 'version sep 20 файл'
(Команда version
может для удобства создавать эхо имени файла истории.)
Упражнение 5.30
Измените команды get
и put
так, чтобы для работы с файлом истории они использовали отдельный каталог, а не загромождали текущий каталог файлами.
Упражнение 5.31
Когда программа уже работает, не имеет смысла запоминать все версии. Как бы вы организовали исключение версий из середины файла истории?
- 5.1 Совершенствование команды cal
- 5.2 Что представляет собой команда which?
- 5.3 Циклы while и until: контроль входа в систему
- 5.4 Команда trap: обработка прерываний
- 5.5 Команда overwrite: замена файла
- 5.6 Команда zap: уничтожение процесса по имени
- 5.7 Команда pick: пробелы или аргументы
- 5.8 Команда news: служба информации пользователей
- 5.9 Команды get и put: контроль изменении файла
- 5.10 Заключение
- 2.1.3. Функция getopt_long()
- Эффективная работа с временными файлами сортировки
- Единое имя файла параметров InterBase
- Параметры конфигурационного файла InterBase
- Chapter 11. Iptables targets and jumps
- 13. Зарабатываем на своих файлах: файлообменники, загружаеми получаем процент за скачивание
- Where to get iptables
- ACCEPT target
- CLASSIFY target
- CLUSTERIP target
- CONNMARK target
- CONNSECMARK target