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$$ '
<a href="">/^@@@/</a> { 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
изменяется только версия файла, а не сам файл истории.