Некоторые приемы программирования
Пример 1
Пример 2
Пример 3
Описанные ниже некоторые приемы программирования, возможно, помогут Вам в решении Вашей задачи. В большинстве случаев Вам понадобятся команды обработки текстов и потоковый редактор sed. Информацию о файлах, как правило, можно получить от команды ls и других команд файловой системы. Краткие описания всех необходимых команд есть в нашей справочной системе.
Конвейеры и временные файлы
При решении большинства задач Вам придется применять конвейеры
команд. Не всегда, однако, удается непосредственно использовать
выход одной команды на входе другой. Иногда требуется
подготовить два источника данных (например, для соединения), иногда
целесообразно сохранить результаты выполнения команды для повторного
их использования. В таких случаях можно сохранять результаты во
временном файле.
Однако, формируя имя временного файла, не забывайте о том, что Вы можете не иметь прав на создание файлов в том каталоге, который является текущим в момент выполнения команды. Поэтому временные файлы целесообразно создавать в домашнем каталоге пользователя, выполняющего скрипт. Таким образом, выполнение команды с перенаправлением ее результата во временный файл будет выглядеть так:
команда параметры... > $HOME/tempfile
Обработка списка параметров
Если в Вашей задаче предусматривается обработка списка параметров,
то она может быть выполнена в таком цикле:
while [ $# -ne 0 ]
do
обработка параметра $1
shitf
done
В каждой итерации этого цикла обрабатывается 1-й параметр ($1). В конце цикла командой shift список параметров сдвигается, 2-й параметр становится первым и т.д. Цикл выполняется до тех пор, пока количество параметров в списке ($#) не станет равным 0.
Обработка структурированных файлов
В некоторых задачах файлы состоят из текстов, организованных
в строки и столбцы. Такие же тексты выдают на выходе некоторые
системные утилиты, например, утилита ls
с опцией -l. Для обработки таких файлов удобно
использовать команды, ориентированные на структурированные файлы:
выделение полей - cut,
сортировка - sort,
реляционное соединение - join
(не забывайте, что join требует, чтобы файлы были
предварительно отсортированы!).
Следует, однако, иметь в виду, во-первых, что у этих команд разные
опции задания разделителей в тексте, и при их совместном употреблении
следует быть внимательным в этом отношении, а во-вторых, то, что поля
в структурированных файлах зачастую разделяются переменным числом пробелов,
а все эти команды требуют единственного разделителя между полями.
Поэтому прежде чем применять к структурированному файлу такую команду,
нужно заменить множественные пробелы единственным, что можно сделать,
например, таким обращением к потоковому редактору
sed:
sed -n 's/ */ /gp'которая заменяет один пробел и любое количество следующих за ним пробелов на один пробел. (В этой команде редактирования в шаблоне поиска перед символом '*' стоят два пробела, а шаблон замены состоит из единственного пробела. Опция g предписывает заменить все вхождения.)
Как правило, в задачах, связанных со структурированными файлами, обрабатываются только избранные поля файла. Рекомендуем Вам для облегчения своей работы уже на начальном этапе обработки избавиться от ненужных столбцов при помощи команды cut
Построчная обработка файлов
Предположим (типичная для некоторых задачах ситуация), что нам нужно
читать некоторой файл - исходный_файл строку за строкой
и обрабатывать каждую строку. Некоторые из возможных вариантов такой
обработки следующие:
# начальное значение счетчика строк - 1
n=1
# бесконечный цикл, выход - по break
while [ 1 ]
do
# выполняется sed, операция печати строк,
# значение переменной $s подставляется в номер строки.
# результат sed - прочитанная строка - присваивается
# переменной value
value=`sed -n ''$n'p' исходный_файл`
# если в value прочиталась пустая строка - выход из цикла
if [ "$value" = "" ]; then
break;
fi
обработка $value
# вычисление нового номера строки
n=`expr $n +1'
done
# цикл выполняется пока в исходном файле есть строки
while -s test исходный_файл
do
# читается 1-я строка и ее значение присваивается
# переменной value
value=`head -n1 исходный_файл`
обработка $value
# удаление 1-й строки из файла
# результат перенаправляется во временный файл, затем
# исходный_файл удаляется, временный файл
# переименовывается в исходный_файл
sed '1d' исходный_файл > $HOME/temp1
rm исходный_файл
mv $HOME/temp1 исходный_файл
done
# список для цикла считывается из исходного_файла
for value in `cat исходный_файл`
do
обработка $value
done
Имейте в виду, что если текст исходного_файла
содержит пробелы, то в каждой итерации цикла будет считываться
одно слово.
Выделение строк по значению в столбце
В некоторых задачах требуется в структурированный файл
ограничить только теми строками, которые имеют определенное значение в
определенном столбце. Простое решение можно получить при помощи
команд
sed или
grep,
использовав заданное значение в составе шаблона:
var=искомое_значение
sed -n '/спецификации_определяющие_позицию'$var'p' файл
но в некоторых случаях (если трудно сформулировать
спецификации_определяющие_позицию) может оказаться удобнее применить
искусственный прием: записать искомое_значение в отдельный
файл и выполнить его соединение с исходным файлом:
# запись искомого_значения во временный файл
echo "искомое_значение" >$HOME/tempfile
# сортировка исходного файла по требуемому столбцу
sort +позиция-1 -позиция -t'разделитель' файл | # соединение: 1-й файл - результат предыдущей команды, в нем
# для соединения берется требуемый столбец, 2-й файл - временный
# файл, в нем - столбец первый и единственный
join -j1 позиция -j2 1 -t'разделитель' - $HOME/tempfile
Задание
Проверить, работает ли в сети заданный пользователь. Если да -
отправить ему сообщение по write. Если
пользователя нет в сети или он заблокировал терминал, -
отправить сообщение по mail.
Формат применения этой команды должен быть:
команда пользователь...
Ниже мы приводим текст скрипта (мы назвали его ex7_1) с подробными комментариями.
#!/bin/bash2
# ============================================================== #
# Скрипт ex7_1: отправка сообщения/письма заданным пользователям #
# ============================================================== #
# Комментарий !/bin/bash2 описывает местонахождения командного
# интерпретатора shell. Он помогает системе найти интерпретатор,
# а также помогает команде file определить тип файла-скрипта.
# В большинстве случаев он не является обязательным, но является
# "правилом хорошего тона" в программировании.
# =======================================================
#
# Текст сообщения/письма заносится в файл temp01
# Обратите внимание: временный файл размещается в домашнем
# каталоге пользователя.
echo Это тестовое сообщение > $HOME/temp01
echo для проверки связи из скрипта. >>$HOME/temp01
echo Желаю успеха! >>$HOME/temp01
echo ap10999 >>$HOME/temp01
# ========================================================
# Параметры команды представляют собой список имен пользователей.
# Следующим оператором организуется цикл, который выполняется до
# тех пор, пока остаются необработанные параметры.
while [ $# -ne 0 ]
do
# ======================================================
# Значение 1-го параметра присваивается переменной us
us=$1
# ======================================================
# Удаляется файл temp02. Обратите внимание: диагностика команды
# rm подавляется (направляется в "системную корину"). Эта диагностика
# может сообщать о том, что удаляемого файла не существует -
# она нам не нужна.
rm $HOME/temp02 2>/dev/null
# ======================================================
# Попытка выдачи сообщения пользователю.
# Системный ввод команды write направляется на файл temp01.
# Если не удается установить соединение с пользователем (пользователя
# нет в системе или он заблокировал свой терминал) команда write
# выводит диагностическое сообщение (мы его подавляем) и
# возвращает false.
if write $us <$HOME/temp01 2>/dev/null
then
# ====================================================
# Если сообщение прошло - OK! выводим на свой терминал
# сообщение об этом, и - "обработка пользователя" закончена.
echo "Пользователю $us отправлено сообщение"
else
# ====================================================
# Если сообщение не прошло - попытка отправки письма
# Системный ввод команды mail направляется на файл temp01.
# Команда mail всегда возвращает true. Она только запускает
# дочерний процесс sendmail, и, если отправка письма не удалась,
# это будет выяснено только в дочернем процессе, которая выдаст
# диагностику, эту диагностику перенаправляем во временный
# файл temp02.
mail $us <$HOME/temp01 >$HOME/temp02
# ====================================================
# Ожидаем 1 сек, чтобы дочерний процесс успел записать
# в файл temp02 сообщение о том, что письмо не отправлено
sleep 1
# ====================================================
# Проверяем, есть ли что-нибудь в файле temp02
if test -s $HOME/temp02
then
# ==================================================
# Если файл temp02 не пустой - пользователь не найден
echo "Пользователь $us не найден"
# ==================================================
# в этом случае письмо попадает в файл dead.letter,
# который нам не нужен
rm $HOME/dead.letter 2>/dev/null
else
# ==================================================
# Если файл temp02 пустой - письмо отправлено
echo "Пользователю $us отправлено письмо"
# ====================================================
# Конец условия if test -s $HOME/temp02
fi
# ======================================================
# Конец условия if write $us ...
fi
# ======================================================
# Сдвиг списка параметров
shift
# ========================================================
# Конец цикла while
done
# ========================================================
# После выхода из цикла
# Удаляются временные файлы
rm $HOME/dead.letter 2>/dev/null
rm $HOME/temp01 2>/dev/null
rm $HOME/temp02 2>/dev/null
# ======== Конец скрипта ex7_1 ================================= #
Ниже приведен образец выполнения созданной команды:
bash2-2.05$ sh ex1_7 ap10999 ap10998 ap1097 Пользователю ap10999 отправлено сообщение Пользователю ap10998 отправлено письмо Пользователь ap1097 не найден bash2-2.05$ |
Задание
Вывести названия всех файлов в заданном каталоге, имеющих одинаковую
первую строку.
Формат применения этой команды должен быть:
команда каталог...
Cкрипт назван ex7_2, его текст с подробными комментариями приводится ниже.
#!/bin/bash2
# ============================================================== #
# Скрипт ex7_2: поиск совпадающих первых строк #
# ============================================================== #
# ========================================================
# Запоминаем текущий каталог
mydir=`pwd`
# ========================================================
# Параметры команды представляют собой список каталогов.
# Следующим оператором организуется цикл, который выполняется до
# тех пор, пока остаются необработанные параметры.
while [ $# -ne 0 ]
do
# ======================================================
# Переход в каталог, задаваемый 1-м параметром;
# диагностика направляется во временный файл
cd $1 2>$HOME/temp01
# ======================================================
# Проверка - не пустой ли файл с диагностикой
if test -s $HOME/temp01
then
# ====================================================
# Если есть диагностика - каталог недоступен
echo "Каталог $1 недоступен/не существует"
# ====================================================
# Переход к следующему параметру
shift
# ====================================================
# Пропуск остатка тела цикла
continue
fi
# ======================================================
# Мы вошли в заданный каталог
echo "Каталог: $1"
# ======================================================
# Выбираются файлы размером больше 0,
# список файлов направляется в поток
find -size +0c -type f |
# ======================================================
# Файлы в списке выведены с указанием каталога ./,
# от этих символов нужно избавиться;
# результат записывается во временный файл
sed 's/.\///' >$HOME/temp01
# ======================================================
# Цикл пока в файле temp01 что-то остается
while test -s $HOME/temp01
do
# ====================================================
# Выбирается в переменную $f1 первая строка из
# временного файла - имя очередного файла
f1=`head -n1 $HOME/temp01`
# ====================================================
# Выбирается в переменную $s1 первая строка из
# выбранного файла
s1=`head -n1 $f1`
# ====================================================
# $n - признак того, что совпадение строк не найдено
# $m - номер второго файла из списка
n="no"; m=2;
# ====================================================
# Бесконечный цикл - выход по break
while [ 1 ]
do
# ==================================================
# Выбирается $m-я строка из списка файлов - имя файла
f2=`sed -n ''$m'p' $HOME/temp01`
# ==================================================
# Если выбрана пустая строка - список файлов закончился
if [ "$f2" = "" ]; then
# ================================================
# в этом случае - выход из цикла
break
# ================================================
# Конец условия if [ "$f2" = "" ]
fi
# ==================================================
# Выбирается 1-я строка из этого файла
s2=`head -n1 $f2`
# ==================================================
# Строка сравнивается с запомненной ранее строкой из 1-го
# файла в списке
if [ "$s1" = "$s2" ]; then
# ================================================
# Проверка - были ли уже совпадения
if [ "$n" = "no" ]; then
# ==============================================
# Если совпадений не было - выводится имя 1-го файла из списка
echo -n "$f1 "; n="yes"
# ==============================================
# Конец условия if [ "$n" = "no" ]
fi
# ================================================
# Выводится имя файла, в котором найдено совпадение
echo -n "$f2 "
# ================================================
# Файла, в котором найдено совпадение, удаляется из файла-списка
sed ''$m'd' $HOME/temp01 > $HOME/temp02
cat $HOME/temp02 >$HOME/temp01
# ================================================
# Конец действий при совпадении
else
# ================================================
# Если нет совпадения - переход к следующему файлу из списка
m=`expr $m + 1`
# ================================================
# Конец действий при отсутствии совпадения
fi
# ==================================================
# Конец цикла перебора остальных файлов списка
done
# ====================================================
# Если были найдены совпадения - выводится текст общей строки
if [ "$n" = "yes" ]; then
echo "- $s1"
fi
# ====================================================
# Удаление 1-й строки из списка имен файлов
sed '1d' $HOME/temp01 > $HOME/temp02
cat $HOME/temp02 >$HOME/temp01
# ====================================================
# Конец цикла перебора имен файлов
done
# ======================================================
# Переход к следующему параметру
shift
# ======================================================
# Конец списка перебора параметров
done
# ========================================================
# Удаление временного файла
rm $HOME/temp*
# ========================================================
# Возврат в исходный каталог
cd "$mydir"
# ======== Конец скрипта ex7_2 ================================= #
Образец выполнения созданной команды:
bash2-2.05$ ex7_2 ../ap10999 /home/metod Каталог ../ap10999 недоступен/не существует Каталог: /home/metod Hum-Dum.txt H1 - Humpty-Dumpty nikOl_01 nikOl_02 nikOl_03 nikOl_04 nikOl_05 nikOl_06 nikOl_07 nikOl_08 nikOl_09 nikOl_10 nikOl_11 nikOl_12 nikOl_13 nikOl_14 nikOl_15 nikOl_16 nikOl_17 nikOl_1 8 - Николай Олейников bash2-2.05$ |
Задание
По информации файлов /home/metod/query* для заданного кода
товара определить, сколько его экземпляров было продано в заданный месяц.
Формат применения:
команда месяц код_товара...
Месяц задается в формате: MON-YY, например: JAN-90, JUN-89, NOV-17 и т.п.
Cкрипт назван ex7_3, его текст с подробными комментариями приводится ниже.
#!/bin/bash2
# ============================================================== #
# Скрипт ex7_3: выборка объема продаж заданного товара #
# ============================================================== #
# Выбирается 1-й параметр и список параметров сдвигается
mon=$1; shift
# =======================================================
# Месяц записывается в файл
echo "$mon" >$HOME/temp01
# =======================================================
# Остальные параметры команды представляют собой список кодов товаров.
# Цикл, который выполняется до тех пор, пока остаются
# необработанные параметры.
while [ $# -ne 0 ]
do
# =====================================================
# Код товара запоминается в переменной и записывается в файл
code=$1
echo "$code" >$HOME/temp02
# =====================================================
# КОНВЕЙЕР 1
# =====================================================
# В файле query5 удаляются лишние пробелы
sed -n 's/ */ /gp' /home/metod/query5 |
# =====================================================
# Выбираются столбцы: код заказа, код товара, количество
cut -f2,3,5 -d' ' |
# =====================================================
# Сортировка по коду товара
sort +1 -2 -t' ' |
# =====================================================
# Соединение с файлом - кодом товара
join -j1 2 -j2 1 -t' ' - $HOME/temp02 |
# =====================================================
# Выбираются столбцы: код заказа, количество
# (код товара больше не нужен)
cut -f2,3 -d' ' |
# =====================================================
# Сортировка по коду заказа и сохранение в файле
sort +0 -1 -t' ' >$HOME/temp03
#
# =====================================================
# КОНВЕЙЕР 2
# =====================================================
# В файле query4 удаляются лишние пробелы
sed -n 's/ */ /gp' /home/metod/query4 |
# =====================================================
# Выбираются столбцы: код заказа, дата
cut -f1,4 -d' ' |
# =====================================================
# Сортировка по коду заказа
sort +0 -1 -t' ' |
# =====================================================
# Соединение с файлом - месяцем
sed -n '/'$mon'/p' |
# =====================================================
# Выбирается столбец кода заказа
# (дата больше не нужна)
cut -f1 -d' ' |
# =====================================================
# Сортировка по коду
sort +0 -1 -t' ' |
# =====================================================
# Соединение с файлом заказов по заданному товару
join -j1 1 -j2 1 - $HOME/temp03 |
# =====================================================
# Выбирается столбец количества
cut -f2 -d' ' >$HOME/temp01
# =====================================================
# Начальное значение суммы - 0
sum=0
# =====================================================
# Содержимое файла temp01 составляеn список цикла for
for value in `cat $HOME/temp01`
do
# ===================================================
# Суммирование элементов списка
sum=`expr "$sum" + "$value"`
done
# =====================================================
# Вывод результата
echo "Продано товара $code в $mon - $sum шт."
# =====================================================
# Сдвиг списка параметров
shift
# =====================================================
# Конец цикла перебора параметрjв
done
# =======================================================
# После выхода из цикла - удаление временных файлов
rm $HOME/temp01
rm $HOME/temp02
rm $HOME/temp03
# ======== Конец скрипта ex7_3 ================================= #
Образец выполнения созданной команды:
bash2-2.05$ sh sss JAN-91 100860 100861 100862 Продано товара 100860 в JAN-91 - 100 шт. Продано товара 100861 в JAN-91 - 20 шт. Продано товара 100862 в JAN-91 - 0 шт. bash2-2.05$ |