Главная » Программирование для UNIX » Отслеживание изменений файла: get и put

0

В этом разделе, завершающем длинную главу, будет обсуждаться большой  и более  сложный, чем  рассмотренные ранее, пример, иллюстрирующий взаимодействие оболочки с программами awk и sed.

Программа развивается по мере исправления ошибок и добавления новых  возможностей. Бывает удобно  отслеживать эти  версии, особенно если с ними работают на других машинах. Пользователи вполне могут  спросить: «Что  изменилось с тех  пор, как мы получили эту версию?» или: «Как исправлена такая-то ошибка?» К тому  же  при  наличии резервной копии применение новых идей  будет  более безопасным: если  что-то  не заработает, можно безболезненно вернуться к исходной программе.

В качестве одного  из решений можно предложить хранить копии всех версий, но это тяжело организовать, к тому же требуется много диско-

вого пространства. Вместо  этого извлечем выгоду из такого предположения: у следующих друг  за другом версий должно быть очень  много  общего, которое можно сохранить только единожды. Команда diff  –e

$ diff -e  old  new

генерирует список команд ed,  которые  преобразуют old  в new.  Таким образом, можно хранить все версии файла в одном  (отдельном) файле, сохраняя одну полную версию и набор команд редактора, преобразующих ее в любую другую версию.

Два  очевидных решения: хранить самую последнюю версию и набор  команд, «переводящих время назад», или  же  хранить самую  первую версию и набор  команд, «переводящих часы  вперед». Хотя второй вариант легче  запрограммировать, первый  работает быстрее, если  версий много – ведь обычно  интерес вызывают более поздние версии.

Итак, остановимся на  первом варианте. В едином файле, назовем его файлом предыстории (history file), находится текущая версия и наборы редактирующих команд, которые превращают каждую версию в предыдущую (то есть в ближайшую более старую). Каждый набор команд редактора начинается со строки, выглядящей следующим образом:

@@@   пользователь дата сводка

Сводка – это одна  строка, описывающая изменения, предоставленная

пользователем.

Есть две команды для хранения версий: get извлекает версию из файла предыстории, а put вносит новую  версию в этот файл, предварительно запросив однострочное сообщение об изменениях.

Прежде чем  представить программу, приведем  пример, показывающий, как работают get и put и как хранятся версии:

$ echo a line  of  text >junk

$ put  junk

Summary:  make  a new  file                Введите описание

get: no file  junk.H                                Истории не существует…

put: creating  junk.H                              … поэтому put создает ее

$ cat  junk.H

a line  of  text

@@@   you Sat  Oct   1 13:31:03  EDT  1983 make a  new file

$ echo another  line  >>junk

$ put  junk

Summary:  one line added

$ cat  junk.H

a line  of  text another  line

@@@   you Sat  Oct   1  13:32:28  EDT  1983 one line  added 2d

@@@   you Sat  Oct   1 13:31:03  EDT  1983 make a  new file

$

«Набор редактирующих команд» состоит  из одной-единственной строки  2d, которая удаляет строку 2 в файле, превращая новую версию в исходную.

$ rm junk

$ get  junk                                                       Самая новая версия

$ cat  junk

a line  of  text another  line

$ get  -1  junk

$ cat  junk                                  Предыдущая перед самой новой версией

a line  of  text

$ get  junk                                                       Снова  самая новая версия

$ replace another  ‘a different’ junk      Изменить ее

$ put  junk

Summary:  second  line  changed

$ cat  junk.H

a line  of  text

a different line

@@@   you Sat  Oct   1 13:34:07  EDT  1983 second  line changed 2c

another line

.

@@@   you Sat  Oct   1  13:32:28  EDT  1983 one line  added 2d

@@@   you Sat  Oct   1 13:31:03  EDT  1983 make a  new file

$

Редактирующие команды, извлекающие нужную версию, прогоняются по файлу сверху вниз: первый набор преобразует новейшую версию во вторую по свежести, следующий  набор  превращает вторую в третью,  и т. д. Поэтому на самом  деле преобразование нового файла в старый  при выполнении команд ed происходит постепенно, по одной  версии за раз.

Трудности могут  возникнуть, если  в файле есть строки, начинающиеся с трех символов @, а раздел BUGS команды diff(1) предупреждает о строках, которые содержат только точку. Именно потому, что обозна чение  @@@  вряд  ли может встретиться в тексте, оно было  выбрано для  маркировки команд редактирования.

Несмотря на  то что  было  бы поучительно показать, как развивались команды get и put, они достаточно длинные и описание их различных форм заняло бы слишком много времени. Поэтому представлены будут только их окончательные версии. Команда put проще:

#  put:    ввести  файл  в  историю 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             # предыдущая  версия

then                                                     #  объединить части вместе cp  $1 /tmp/put.b$$                   #  текущая версия

echo "@@@   `getname`  `date` $Summary"  >>/tmp/put.b$$

diff –e $1 /tmp/put.a$$  >>/tmp/put.b$$     # последние  отличия sed  –n ‘/^@@@/,$p’  <$HIST  >>/tmp/put.b$$  # старые отличия overwrite $HIST  cat  /tmp/put.b$$                 #  поместить обратно

else                                                     #  создать  новый echo "put:  creating $HIST"

cp $1 $HIST

echo "@@@   `getname`  `date` $Summary"  >>$HIST

fi

rm –f  /tmp/put.[ab]$$

После  считывания однострочной сводки изменений put вызывает get для  того,  чтобы извлечь предыдущую версию файла из файла предыстории. Параметр –o команды get указывает альтернативный файл для  вывода. Если  get  не находит файла предыстории, она возвращает код ошибки, и put создает новый файл предыстории. Если же файл существует, то оператор then создает новую  историю во временном файле из (по порядку) самой  новой версии, строки @@@, команд редактора, преобразующих  новейшую версию в  ближайшую  предыдущую, старых команд редактора и строк @@@. В заключение временный файл копируется в файл предыстории посредством команды overwrite.

Команда get сложнее, чем put, в основном потому, что у нее есть пара метры.

#  get:  извлечь  файл  из  истории 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  ;;

done

esac shift

*)    INPUT=$1.H  ;; esac

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

# разбить на текущую  версию  и команды редактирования  sed  <$INPUT  –n ‘1,/^@@@/w  /tmp/get.a’$$’

/^@@@/,$w  /tmp/get.b’$$

#  выполнить  редактирование 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  и –o указывают альтернативный  ввод и вывод; –[0–9] выбирают конкретную версию, при  этом  0  – самая новая (значение по умолчанию), –1 – версия на одну старше самой новой, и т. д. Аргументы обрабатываются в цикле while, содержащем  команды test и shift. Выбран цикл while, а не for, потому что некоторые параметры (–i, –o) поглощают следующий аргумент, вот почему необходимо применение shift, а циклы for и команды shift не в состоянии взаимодействовать должным образом,  если  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 – они имеют особый  смысл, только если  перед  ними стоит  точка с запятой, разделитель строк или  другой символ конца команды.

Наконец мы добрались до кода, который и выполняет собственно всю работу. Сначала sed разбивает файл предыстории на две части: самую свежую версию и набор  команд редактирования. Затем программа awk обрабатывает команды редактирования.  Строки  @@@   подсчитываются (но не выводятся), и до тех пор,  пока  их количество не превышает номер искомой версии, команды редактирования пропускаются (вспом ните, что awk по умолчанию выводит строку ввода). Две команды добавляются после  команд исторического файла: $d удаляет отдельную строку @@@, которую sed оставила для текущей версии, а команда w записывает файл в место его окончательного расположения. Необходимо использовать overwrite, потому что команда get изменяет только версию файла, а не файл истории.

Упражнение 5.29.  Напишите команду version, которая делала бы следующее:

$ version -5  file

выводила бы сводку изменений, дату изменения и пользователя, кото рый произвел изменение выбранной версии в файле предыстории, а

$ version sep  20 file

сообщала бы,  какая версия была  текущей 20  сентября. Это было  бы удобно в:

$ get  `version sep  20 file`

(Для  удобства version может показывать имя файла предыстории.) ~

Упражнение 5.30.  Измените get  и  put  так,  чтобы  они  обрабатывали файл предыстории в отдельном каталоге вместо  того, чтобы загромождать рабочий каталог файлами вида .H. ~

Упражнение 5.31.  Не все версии файла стоит хранить после  того,  как получена хорошая версия. Как  удалить версию из середины файла истории? ~

Оглянемся назад

Когда  надо написать новую программу, то первое, о чем начинаешь думать  – это как написать ее на своем  любимом языке программирования. В нашем случае этим языком обычно  является оболочка.

Несмотря на то что ее синтаксис не совсем привычен, оболочка – это превосходный язык программирования, язык высокого уровня: операторами являются целые программы. Так как оболочка интерактивна, то программы можно разрабатывать в интерактивном режиме и совершенствовать их постепенно, пока  не заработают. Затем, если программы  предназначены не  только для себя, можно  «навести на  них  гля нец»  и доработать для сообщества пользователей. В тех  очень  редких

случаях, когда программа оболочки оказывается малопроизводительной,  можно переписать ее на Си, имея под рукой проверенный работающий прототип. (Этот способ  будет  несколько раз  применен в следующей  главе.)

Такой обобщающий подход  характерен для среды  программирования UNIX  – здесь принято использовать то, что уже сделано другими вместо того,  чтобы  опять начинать с нуля, начать с чего-то  маленького и усовершенствовать его; применять для обкатки новых идей различные инструментальные средства.

В данной главе было  представлено множество примеров, которые легко   выполнить,   используя  существующие программы и   оболочку. Иногда достаточно просто перегруппировать аргументы, как в случае с cal. Иногда оболочка предоставляет цикл для просмотра множества имен  файлов или  последовательности команд, например в watchfor  и checkmail. Даже сложные примеры все равно  требуют меньше работы, чем при программировании на Си; например, 20-строчная версия программы news заменяет версию из 350 (!) строк на Си.

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

История и библиография

Идея  команд get и put взята из системы SCCS (Source Code Control System  – система  управления исходными текстами), созданной  Марком Рочкиндом (Marc  Rochkind) и описанной им в статье «The  source code control  system» (Система управления исходными  текстами), появившейся в журнале «IEEE Trans. on Software Engineering» в 1975  году. SCCS является гораздо более мощной и гибкой, чем простые программы, описанные в этой главе; она предназначена для хранения больших программ в производственной среде. Однако основа  SCCS – это все та же программа diff.

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии