• Пример A-1. manview: Просмотр страниц руководств man
  • Пример A-2. mailformat: Форматирование электронных писем
  • Пример A-3. rn: Очень простая утилита для переименования файлов
  • Пример A-4. blank-rename: переименование файлов, чьи имена содержат пробелы
  • Пример A-5. encryptedpw: Передача файла на ftp-сервер, с использованием пароля
  • Пример A-6. copy-cd: Копирование компакт-дисков с данными
  • Пример A-7. Последовательности Коллаца (Collatz)
  • Пример A-8. days-between: Подсчет числа дней между двумя датами
  • Пример A-9. Создание "словаря"
  • Пример A-10. Расчет индекса "созвучности"
  • Пример A-11. "Игра "Жизнь""
  • Пример A-12. Файл с первым поколением для игры "Жизнь"
  • Пример A-13. behead: Удаление заголовков из электронных писем и новостей
  • Пример A-14. ftpget: Скачивание файлов по ftp
  • Пример A-15. Указание на авторские права
  • Пример A-16. password: Генератор случайного 8-ми символьного пароля
  • Пример A-17. fifo: Создание резервных копий с помощью именованных каналов
  • Пример A-18. Генерация простых чисел, с использованием оператора деления по модулю (остаток от деления)
  • Пример A-19. tree: Вывод дерева каталогов
  • Пример A-20. Функции для работы со строками
  • Пример A-21. Directory information
  • Пример A-22. Объектно ориентированная база данных
  • Приложение A. Дополнительные примеры сценариев

    В этом приложении собраны сценарии, которые не попали в основной текст документа. Однако, они определенно стоят того, что бы вы потратили время на их изучение.

    Пример A-1. manview: Просмотр страниц руководств man

    #!/bin/bash

    # manview.sh: Просмотр страниц руководств man в форматированном виде.


    # Полезен писателям страниц руководств, позволяет просмотреть страницы в исходном коде

    #+ как они будут выглядеть в конечном виде.


    E_WRONGARGS=65


    if [ -z "$1" ]

    then

    echo "Порядок использования: `basename $0` имя_файла"

    exit $E_WRONGARGS

    fi


    groff -Tascii -man $1 | less


    # Если страница руководства включает в себя таблицы и/или выражения,

    # то этот сценарий "стошнит".

    # Для таких случаев можно использовать следующую строку.

    #

    # gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man

    #

    # Спасибо S.C.


    exit 0

    Пример A-2. mailformat: Форматирование электронных писем

    #!/bin/bash

    # mail-format.sh: Форматирование электронных писем.


    # Удаляет символы "^", табуляции и ограничивает чрезмерно длинные строки.


    # =================================================================

    # Стандартная проверка аргументов

    ARGS=1

    E_BADARGS=65

    E_NOFILE=66


    if [ $# -ne $ARGS ] # Проверка числа аргументов

    then

    echo "Порядок использования: `basename $0` имя_файла"

    exit $E_BADARGS

    fi


    if [ -f "$1" ] # Проверка наличия файла.

    then

    file_name=$1

    else

    echo "Файл \"$1\" не найден."

    exit $E_NOFILE

    fi

    # =================================================================


    MAXWIDTH=70 # Максимальная длина строки.


    # Удаление символов "^" начиная с первого символа строки,

    #+ и ограничить длину строки 70-ю символами.

    sed '

    s/^>//

    s/^ *>//

    s/^ *//

    s/ *//

    ' $1 | fold -s --width=$MAXWIDTH

    # ключ -s команды "fold" разрывает, если это возможно, строку по пробельному символу.


    # Этот сценарий был написан после прочтения статьи, в котором расхваливалась

    #+ утилита под Windows, размером в 164K, с подобной функциональностью.

    #

    # Хороший набор утилит для обработки текста и эффективный

    #+ скриптовый язык -- это все, что необходимо, чтобы составить серьезную конкуренцию

    #+ чрезмерно "раздутым" программам.


    exit 0

    Пример A-3. rn: Очень простая утилита для переименования файлов

    Этот сценарий является модификацией Пример 12-15.

    #! /bin/bash

    #

    # Очень простая утилита для переименования файлов

    #

    # Утилита "ren", автор Vladimir Lanin (lanin@csd2.nyu.edu),

    #+ выполняет эти же действия много лучше.


    ARGS=2

    E_BADARGS=65

    ONE=1 # Единственное или множественное число (см. ниже).


    if [ $# -ne "$ARGS" ]

    then

    echo "Порядок использования: `basename $0` старый_шаблон новый_шаблон"

    # Например: "rn gif jpg", поменяет расширения всех файлов в текущем каталоге с gif на jpg.

    exit $E_BADARGS

    fi


    number=0 # Количество переименованных файлов.


    for filename in *$1* # Проход по списку файлов в текущем каталоге.

    do

    if [ -f "$filename" ]

    then

    fname=`basename $filename` # Удалить путь к файлу из имени.

    n=`echo $fname | sed -e "s/$1/$2/"` # Поменять старое имя на новое.

    mv $fname $n # Переименовать.

    let "number += 1"

    fi

    done


    if [ "$number" -eq "$ONE" ] # Соблюдение правил грамматики.

    then

    echo "$number файл переименован."

    else

    echo "Переименовано файлов: $number."

    fi


    exit 0


    # Упражнения:

    # ----------

    # С какими типами файлов этот сценарий не будет работать?

    # Как это исправить?

    #

    # Переделайте сценарий таким образом, чтобы он мог обрабатывать все файлы в каталоге,

    #+ в именах которых содержатся пробелы, заменяя пробелы символом подчеркивания.

    Пример A-4. blank-rename: переименование файлов, чьи имена содержат пробелы

    Это даже более простая версия предыдущего примера.

    #! /bin/bash

    # blank-rename.sh

    #

    # Заменяет пробелы символом подчеркивания в именах файлов в текущем каталоге.


    ONE=1 # единственное или множественное число (см. ниже).

    number=0 # Количество переименованных файлов.

    FOUND=0 # Код завершения в случае успеха.


    for filename in * # Перебор всех файлов в текущем каталоге.

    do

    echo "$filename" | grep -q " " # Проверить -- содержит ли имя файла

    if [ $? -eq $FOUND ] #+ пробелы.

    then

    fname=$filename # Удалить путь из имени файла.

    n=`echo $fname | sed -e "s/ /_/g"` # Заменить пробелы символом подчеркивания.

    mv "$fname" "$n" # Переименование.

    let "number += 1"

    fi

    done


    if [ "$number" -eq "$ONE" ]

    then

    echo "$number файл переименован."

    else

    echo "Переименовано файлов: $number"

    fi


    exit 0

    Пример A-5. encryptedpw: Передача файла на ftp-сервер, с использованием пароля

    #!/bin/bash


    # Модификация примера "ex72.sh", добавлено шифрование пароля.


    # Обратите внимание: этот вариант все еще нельзя считать безопасным,

    #+ поскольку в сеть пароль уходит в незашифрованном виде.

    # Используйте "ssh", если вас это беспокоит.


    E_BADARGS=65


    if [ -z "$1" ]

    then

    echo "Порядок использования: `basename $0` имя_файла"

    exit $E_BADARGS

    fi


    Username=bozo # Измените на свой.

    pword=/home/bozo/secret/password_encrypted.file

    # Файл, содержащий пароль в зашифрованном виде.


    Filename=`basename $1` # Удалить путь из имени файла


    Server="XXX"

    Directory="YYY" # Подставьте фактические имя сервера и каталога.


    Password=`cruft <$pword` # Расшифровка.

    # Используется авторская программа "cruft",

    #+ основанная на алгоритме "onetime pad",

    #+ ее можно скачать с :

    #+ Primary-site: ftp://ibiblio.org/pub/Linux/utils/file

    #+ cruft-0.2.tar.gz [16k]


    ftp -n $Server <<End-Of-Session

    user $Username $Password

    binary

    bell

    cd $Directory

    put $Filename

    bye

    End-Of-Session

    # ключ -n, команды "ftp", запрещает автоматический вход.

    # "bell" -- звонок (звуковой сигнал) после передачи каждого файла.


    exit 0

    Пример A-6. copy-cd: Копирование компакт-дисков с данными

    #!/bin/bash

    # copy-cd.sh: copying a data CD


    CDROM=/dev/cdrom # устройство CD ROM

    OF=/home/bozo/projects/cdimage.iso # промежуточный файл

    # /xxxx/xxxxxxx/ измените для своей системы.

    BLOCKSIZE=2048

    SPEED=2 # Можно задать более высокую скорость, если поддерживается.


    echo; echo "Вставьте исходный CD, но *НЕ* монтируйте его."

    echo "Нажмите ENTER, когда будете готовы. "

    read ready # Ожидание.


    echo; echo "Создается промежуточный файл $OF."

    echo "Это может занять какое-то время. Пожалуйста подождите."


    dd if=$CDROM of=$OF bs=$BLOCKSIZE # Копирование.


    echo; echo "Выньте исходный CD."

    echo "Вставьте чистую болванку CDR."

    echo "Нажмите ENTER, когда будете готовы. "

    read ready # Ожидание.


    echo "Копируется файл $OF на болванку."


    cdrecord -v -isosize speed=$SPEED dev=0,0 $OF

    # Используется пакет Joerg Schilling -- "cdrecord" .

    # http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html


    echo; echo "Копирование завершено."


    echo "Желаете удалить промежуточный файл (y/n)? " # Наверняка большой файл получился.

    read answer


    case "$answer" in

    [yY]) rm -f $OF

    echo "Файл $OF удален."

    ;;

    *) echo "Файл $OF не был удален.";;

    esac


    echo


    # Упражнение:

    # Добавьте в оператор "case" возможность обработки, введенных пользователем, "yes" и "Yes".


    exit 0

    Пример A-7. Последовательности Коллаца (Collatz)

    #!/bin/bash

    # collatz.sh


    # Широко известная последовательность Коллаца (Collatz) (гипотеза Коллаца).

    # -------------------------------------------

    # 1) Принимает из командной строки "начальное" целое число.

    # 2) ЧИСЛО <--- НАЧАЛЬНОЕ ЗНАЧЕНИЕ

    # 3) Вывести ЧИСЛО.

    # 4) Если ЧИСЛО четное, разделить на 2,

    # 5)+ Если не четное -- умножить на 3 и прибавить 1.

    # 6) ЧИСЛО <--- РЕЗУЛЬТАТ

    # 7) Повторить, начиная с п. 3, заданное число раз.

    #

    # Теоретически, такая последовательность должна сходиться,

    #+ не зависимо от величины начального значения,

    #+ к повторению циклов "4,2,1...",

    #+ даже после значительных флуктуаций в самом начале.


    MAX_ITERATIONS=200

    # Для больших начальных значений (>32000), это значение придется увеличить.


    h=${1:-$$} # Начальное значение

    # если из командной строки ничего не задано, то берется $PID,


    echo

    echo "C($h) --- $MAX_ITERATIONS итераций"

    echo


    for ((i=1; i<=MAX_ITERATIONS; i++))

    do


    echo -n "$h "

    # ^^^^^

    # табуляция


    let "remainder = h % 2"

    if [ "$remainder" -eq 0 ] # Четное?

    then

    let "h /= 2" # Разделить на 2.

    else

    let "h = h*3 + 1" # Умножить на 3 и прибавить 1.

    fi


    COLUMNS=10 # Выводить по 10 значений в строке.

    let "line_break = i % $COLUMNS"

    if [ "$line_break" -eq 0 ]

    then

    echo

    fi


    done


    echo


    exit 0

    Пример A-8. days-between: Подсчет числа дней между двумя датами

    #!/bin/bash

    # days-between.sh: Подсчет числа дней между двумя датами.

    # Порядок использования: ./days-between.sh [M]M/[D]D/YYYY [M]M/[D]D/YYYY


    ARGS=2 # Ожидается два аргумента из командной строки.

    E_PARAM_ERR=65 # Ошибка в числе ожидаемых аргументов.


    REFYR=1600 # Начальный год.

    CENTURY=100

    DIY=365

    ADJ_DIY=367 # Корректировка на високосный год + 1.

    MIY=12

    DIM=31

    LEAPCYCLE=4


    MAXRETVAL=255 # Максимально возможное возвращаемое значение

    # для положительных чисел.


    diff= # Количество дней между датами.

    value= # Абсолютное значение.

    day= # день, месяц, год.

    month=

    year=


    Param_Error () # Ошибка в пвраметрах командной строки.

    {

    echo "Порядок использования: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY"

    echo " (даты должны быть после 1/3/1600)"

    exit $E_PARAM_ERR

    }


    Parse_Date () # Разбор даты.

    {

    month=${1%%/**}

    dm=${1%/**} # День и месяц.

    day=${dm#*/}

    let "year = `basename $1`" # Хотя это и не имя файла, но результат тот же.

    }


    check_date () # Проверка даты.

    {

    [ "$day" -gt "$DIM" ] || [ "$month" -gt "$MIY" ] || [ "$year" -lt "$REFYR" ] && Param_Error

    # Выход из сценария при обнаружении ошибки.

    # Используется комбинация "ИЛИ-списка / И-списка".

    #

    # Упражнение: Реализуйте более строгую проверку даты.

    }


    strip_leading_zero () # Удалить ведущий ноль

    {

    val=${1#0} # иначе Bash будет считать числа

    return $val # восьмеричными (POSIX.2, sect 2.9.2.1).

    }


    day_index () # Формула Гаусса:

    { # Количество дней от 3 Янв. 1600 до заданной даты.


    day=$1

    month=$2

    year=$3


    let "month = $month - 2"

    if [ "$month" -le 0 ]

    then

    let "month += 12"

    let "year -= 1"

    fi


    let "year -= $REFYR"

    let "indexyr = $year / $CENTURY"


    let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr + $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM"

    # Более подробное объяснение алгоритма вы найдете в

    # http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm


    if [ "$Days" -gt "$MAXRETVAL" ] # Если больше 255,

    then # то поменять знак

    let "dindex = 0 - $Days" # чтобы функция смогла вернуть полное значение.

    else let "dindex = $Days"

    fi


    return $dindex


    }


    calculate_difference () # Разница между двумя датами.

    {

    let "diff = $1 - $2" # Глобальная переменная.

    }


    abs () # Абсолютное значение

    { # Используется глобальная переменная "value".

    if [ "$1" -lt 0 ] # Если число отрицательное

    then # то

    let "value = 0 - $1" # изменить знак,

    else # иначе

    let "value = $1" # оставить как есть.

    fi

    }


    if [ $# -ne "$ARGS" ] # Требуется два аргумента командной строки.

    then

    Param_Error

    fi


    Parse_Date $1

    check_date $day $month $year # Проверка даты.


    strip_leading_zero $day # Удалить ведущие нули

    day=$? # в номере дня и/или месяца.

    strip_leading_zero $month

    month=$?


    day_index $day $month $year

    date1=$?


    abs $date1 # Абсолютное значение

    date1=$value


    Parse_Date $2

    check_date $day $month $year


    strip_leading_zero $day

    day=$?

    strip_leading_zero $month

    month=$?


    day_index $day $month $year

    date2=$?


    abs $date2 # Абсолютное значение

    date2=$value


    calculate_difference $date1 $date2


    abs $diff # Абсолютное значение

    diff=$value


    echo $diff


    exit 0

    # Сравните этот сценарий с реализацией формулы Гаусса на C

    # http://buschencrew.hypermart.net/software/datedif

    Пример A-9. Создание "словаря"

    #!/bin/bash

    # makedict.sh [создание словаря]


    # Модификация сценария /usr/sbin/mkdict.

    # Авторские права на оригинальный сценарий принадлежат Alec Muffett.

    #

    # Этот модифицированный вариант включен в документ на основе

    #+ документа "LICENSE" из пакета "Crack"

    #+ с которым распространяется оригинальный сценарий.


    # Этот скрипт обрабатывает текстовые файлы и создает отсортированный список

    #+ слов, найденных в этих файлах.

    # Он может оказаться полезным для сборки словарей

    #+ и проведения лексикографического анализа.


    E_BADARGS=65


    if [ ! -r "$1" ] # Необходим хотя бы один аргумент --

    then #+ имя файла.

    echo "Порядок использования: $0 имена_файлов"

    exit $E_BADARGS

    fi


    # SORT="sort" # Необходимость задания ключей сортировки отпала.

    #+ Изменено, по отношению к оригинальному сценарию.


    cat $* | # Выдать содержимое файлов на stdout.

    tr A-Z a-z | # Преобразовать в нижний регистр.

    tr ' ' '\012' | # Новое: заменить пробелы символами перевода строки.

    # tr -cd '\012[a-z][0-9]' | # В оригинальном сценарии: удалить все символы,

    #+ которые не являются буквами или цифрами.

    tr -c '\012a-z' '\012' | # Вместо удаления

    #+ неалфавитно-цифровые символы заменяются на перевод строки.

    sort |

    uniq | # Удалить повторяющиеся слова.

    grep -v '^#' | # Удалить строки, начинающиеся с "#".

    grep -v '^$' # Удалить пустые строки.


    exit 0

    Пример A-10. Расчет индекса "созвучности"

    #!/bin/bash

    # soundex.sh: Расчет индекса "созвучности"


    # =======================================================

    # Сценарий Soundex

    # Автор

    # Mendel Cooper

    # thegrendel@theriver.com

    # 23 Января 2002 г.

    #

    # Условия распространения: Public Domain.

    #

    # Несколько отличающаяся версия этого сценария была опубликована

    #+ Эдом Шэфером (Ed Schaefer) в Июле 2002 года в колонке "Shell Corner"

    #+ "Unix Review" on-line,

    #+ http://www.unixreview.com/documents/uni1026336632258/

    # =======================================================


    ARGCOUNT=1 # Требуется аргумент командной строки.

    E_WRONGARGS=70


    if [ $# -ne "$ARGCOUNT" ]

    then

    echo "Порядок использования: `basename $0` имя"

    exit $E_WRONGARGS

    fi


    assign_value () # Присвоить числовые значения

    { #+ символам в имени.


    val1=bfpv # 'b,f,p,v' = 1

    val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2

    val3=dt # и т.п.

    val4=l

    val5=mn

    val6=r


    # Попробуйте разобраться в том, что здесь происходит.


    value=$( echo "$1" \

    | tr -d wh \

    | tr $val1 1 | tr $val2 2 | tr $val3 3 \

    | tr $val4 4 | tr $val5 5 | tr $val6 6 \

    | tr -s 123456 \

    | tr -d aeiouy )


    # Символам в имени присваиваются числовые значения.

    # Удаляются повторяющиеся числа, если они не разделены гласными.

    # Гласные игнорируются, если они не являются разделителями, которые удаляются в последнюю очередь.

    # Символы 'w' и 'h' удаляются в первую очередь.

    }


    input_name="$1"

    echo

    echo "Имя = $input_name"


    # Перевести все символы в имени в нижний регистр.

    # ------------------------------------------------

    name=$( echo $input_name | tr A-Z a-z )

    # ------------------------------------------------


    # Начальный символ в индекса "созвучия": первая буква в имени.

    # --------------------------------------------


    char_pos=0 # Начальная позиция в имени.

    prefix0=${name:$char_pos:1}

    prefix=`echo $prefix0 | tr a-z A-Z`

    # Первую букву в имени -- в верхний регистр.


    let "char_pos += 1" # Передвинуть "указатель" на один символ.

    name1=${name:$char_pos}


    # ++++++++++++++++++++++++++++ Исключение отдельных ситуаций +++++++++++++++++++++++++++++++

    # Теперь мы передвинулись на один символ вправо.

    # Если второй символ в имени совпадает с первым

    #+ то его нужно отбросить.

    # Кроме того, мы должны проверить -- не является ли первый символ

    #+ гласной, 'w' или 'h'.


    char1=`echo $prefix | tr A-Z a-z` # Первый символ -- в нижний регистр.


    assign_value $name

    s1=$value

    assign_value $name1

    s2=$value

    assign_value $char1

    s3=$value

    s3=9$s3 # Если первый символ в имени -- гласная буква

    #+ или 'w' или 'h',

    #+ то ее "значение" нужно отбросить.

    #+ Поэтому ставим 9, или другое

    #+ неиспользуемое значение, которое можно будет проверить.


    if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]]

    then

    suffix=$s2

    else

    suffix=${s2:$char_pos}

    fi

    # ++++++++++++++++++++++++ Конец исключения отдельных ситуаций +++++++++++++++++++++++++++++++


    padding=000 # Дополнить тремя нулями.


    soun=$prefix$suffix$padding # Нули добавить в конец получившегося индекса.


    MAXLEN=4 # Ограничить длину индекса 4-мя символами.

    soundex=${soun:0:$MAXLEN}


    echo "Индекс созвучия = $soundex"


    echo


    # Индекс "созвучия" - это метод индексации и классификации имен

    #+ по подобию звучания.

    # Индекс "созвучия" начинается с первого символа в имени,

    #+ за которым следуют 3-значный расчетный код.

    # Имена, которые произносятся примерно одинаково, имеют близкие индексы "созвучия".


    # Например:

    # Smith и Smythe -- оба имеют индекс "созвучия" "S530".

    # Harrison = H625

    # Hargison = H622

    # Harriman = H655


    # Как правило эта методика дает неплохой результат, но имеются и аномалии.

    #

    #

    # Дополнительную информацию вы найдете на

    #+ "National Archives and Records Administration home page",

    #+ http://www.nara.gov/genealogy/soundex/soundex.html


    # Упражнение:

    # ----------

    # Упростите блок "Исключение отдельных ситуаций" .


    exit 0

    Пример A-11. "Игра "Жизнь""

    #!/bin/bash

    # life.sh: Игра "Жизнь"


    # ##################################################################### #

    # Это Bash-версия известной игры Джона Конвея (John Conway) "Жизнь". #

    # --------------------------------------------------------------------- #

    # Прямоугольное игровое поле разбито на ячейки, в каждой ячейке может #

    #+ располагаться живая особь. #

    # Соответственно, ячейка с живой особью отмечается точкой, #

    #+ не занятая ячейка -- остается пустой. #

    # Изначально, ячейки заполняются из файла -- #

    #+ это первое поколение, или "поколение 0" #

    # Воспроизводство особей, в каждом последующем поколении, #

    #+ определяется следующими правилами #

    # 1) Каждая ячейка имеет "соседей" #

    #+ слева, справа, сверху, снизу и 4 по диагоналям. #

    # 123 #

    # 4*5 #

    # 678 #

    # #

    # 2) Если живая особь имеет 2 или 3 живых соседей, то она остается жить.#

    # 3) Если пустая ячейка имеет 3 живых соседей -- #

    #+ в ней "рождается" новая особь #

    SURVIVE=2 #

    BIRTH=3 #

    # 4) В любом другом случае, живая особь "погибает" #

    # ##################################################################### #


    startfile=gen0 # Начальное поколение из файла по-умолчанию -- "gen0".

    # если не задан другой файл, из командной строки.

    #

    if [ -n "$1" ] # Проверить аргумент командной строки -- файл с "поколениемn 0".

    then

    if [ -e "$1" ] # Проверка наличия файла.

    then

    startfile="$1"

    fi

    fi


    ALIVE1=.

    DEAD1=_

    # Представление "живых" особей и пустых ячеек в файле с "поколением 0".


    # Этот сценарий работает с игровым полем 10 x 10 grid (может быть увеличено,

    #+ но большое игровое поле будет обрабатываться очень медленно).

    ROWS=10

    COLS=10


    GENERATIONS=10 # Максимальное число поколений.


    NONE_ALIVE=80 # Код завершения на случай,

    #+ если не осталось ни одной "живой" особи.

    TRUE=0

    FALSE=1

    ALIVE=0

    DEAD=1


    avar= # Текущее поколение.

    generation=0 # Инициализация счетчика поколений.


    # =================================================================


    let "cells = $ROWS * $COLS"

    # Количество ячеек на игровом поле.


    declare -a initial # Массивы ячеек.

    declare -a current


    display ()

    {


    alive=0 # Количество "живых" особей.

    # Изначально -- ноль.


    declare -a arr

    arr=( `echo "$1"` ) # Преобразовать аргумент в массив.


    element_count=${#arr[*]}


    local i

    local rowcheck


    for ((i=0; i<$element_count; i++))

    do


    # Символ перевода строки -- в конец каждой строки.

    let "rowcheck = $i % ROWS"

    if [ "$rowcheck" -eq 0 ]

    then

    echo # Перевод строки.

    echo -n " " # Выравнивание.

    fi


    cell=${arr[i]}


    if [ "$cell" = . ]

    then

    let "alive += 1"

    fi


    echo -n "$cell" | sed -e 's/_/ /g'

    # Вывести массив, по пути заменяя символы подчеркивания на пробелы.

    done


    return


    }


    IsValid () # Проверка корректности координат ячейки.

    {


    if [ -z "$1" -o -z "$2" ] # Проверка наличия входных аргументов.

    then

    return $FALSE

    fi


    local row

    local lower_limit=0 # Запрет на отрицательные координаты.

    local upper_limit

    local left

    local right


    let "upper_limit = $ROWS * $COLS - 1" # Номер последней ячейки на игровом поле.


    if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ]

    then

    return $FALSE # Выход за границы массива.

    fi


    row=$2

    let "left = $row * $ROWS" # Левая граница.

    let "right = $left + $COLS - 1" # Правая граница.


    if [ "$1" -lt "$left" -o "$1" -gt "$right" ]

    then

    return $FALSE # Выхол за нижнюю строку.

    fi


    return $TRUE # Координаты корректны.


    }


    IsAlive () # Проверка наличия "живой" особи в ячейке.

    # Принимает массив и номер ячейки в качестве входных аргументов.

    {

    GetCount "$1" $2 # Подсчитать кол-во "живых" соседей.

    local nhbd=$?


    if [ "$nhbd" -eq "$BIRTH" ] # "Живая".

    then

    return $ALIVE

    fi


    if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ]

    then # "Живая" если перед этим была "живая".

    return $ALIVE

    fi


    return $DEAD # По-умолчанию.


    }


    GetCount () # Подсчет "живых" соседей.

    # Необходимо 2 аргумента:

    # $1) переменная-массив

    # $2) cell номер ячейки

    {

    local cell_number=$2

    local array

    local top

    local center

    local bottom

    local r

    local row

    local i

    local t_top

    local t_cen

    local t_bot

    local count=0

    local ROW_NHBD=3


    array=( `echo "$1"` )


    let "top = $cell_number - $COLS - 1" # Номера соседних ячеек.

    let "center = $cell_number - 1"

    let "bottom = $cell_number + $COLS - 1"

    let "r = $cell_number / $ROWS"


    for ((i=0; i<$ROW_NHBD; i++)) # Просмотр слева-направо.

    do

    let "t_top = $top + $i"

    let "t_cen = $center + $i"

    let "t_bot = $bottom + $i"


    let "row = $r" # Пройти по соседям в средней строке.

    IsValid $t_cen $row # Координаты корректны?

    if [ $? -eq "$TRUE" ]

    then

    if [ ${array[$t_cen]} = "$ALIVE1" ] # "Живая"?

    then # Да!

    let "count += 1" # Нарастить счетчик.

    fi

    fi


    let "row = $r - 1" # По верхней строке.

    IsValid $t_top $row

    if [ $? -eq "$TRUE" ]

    then

    if [ ${array[$t_top]} = "$ALIVE1" ]

    then

    let "count += 1"

    fi

    fi


    let "row = $r + 1" # По нижней строке.

    IsValid $t_bot $row

    if [ $? -eq "$TRUE" ]

    then

    if [ ${array[$t_bot]} = "$ALIVE1" ]

    then

    let "count += 1"

    fi

    fi


    done


    if [ ${array[$cell_number]} = "$ALIVE1" ]

    then

    let "count -= 1" # Убедиться, что сама проверяемая ячейка

    fi #+ не была подсчитана.


    return $count


    }


    next_gen () # Обновить массив, в котором содержится информация о новом "поколении".

    {


    local array

    local i=0


    array=( `echo "$1"` ) # Преобразовать в массив.


    while [ "$i" -lt "$cells" ]

    do

    IsAlive "$1" $i ${array[$i]} # "Живая"?

    if [ $? -eq "$ALIVE" ]

    then # Если "живая", то

    array[$i]=. #+ записать точку.

    else

    array[$i]="_" # Иначе -- символ подчеркивания

    fi #+ (который позднее заменится на пробел).

    let "i += 1"

    done


    # let "generation += 1" # Увеличить счетчик поколений.


    # Подготовка переменных, для передачи в функцию "display".

    avar=`echo ${array[@]}` # Преобразовать массив в строку.

    display "$avar" # Вывести его.

    echo; echo

    echo "Поколение $generation -- живых особей $alive"


    if [ "$alive" -eq 0 ]

    then

    echo

    echo "Преждеверменное завершение: не осталось ни одной живой особи!"

    exit $NONE_ALIVE # Нет смысла продолжать

    fi #+ если не осталось ни одной живой особи


    }


    # =========================================================


    # main ()


    # Загрузить начальное поколение из файла.

    initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\

    sed -e 's/\./\. /g' -e 's/_/_ /g'` )

    # Удалить строки, начинающиеся с символа '#' -- комментарии.

    # Удалить строки перевода строки и вставить пробелы между элементами.


    clear # Очистка экрана.


    echo # Заголовок

    echo "======================="

    echo " $GENERATIONS поколений"

    echo " в"

    echo " игре \" ЖИЗНЬ\""

    echo "======================="


    # -------- Вывести первое поколение. --------

    Gen0=`echo ${initial[@]}`

    display "$Gen0" # Тлько вывод.

    echo; echo

    echo "Поколение $generation -- живых особей $alive"

    # -------------------------------------------


    let "generation += 1" # Нарастить счетчик поколений.

    echo


    # ------- Вывести второе поколение. -------

    Cur=`echo ${initial[@]}`

    next_gen "$Cur" # Обновить и вывести.

    # ------------------------------------------


    let "generation += 1" # Нарастить счетчик поколений.


    # ------ Основной цикл игры ------

    while [ "$generation" -le "$GENERATIONS" ]

    do

    Cur="$avar"

    next_gen "$Cur"

    let "generation += 1"

    done

    # ==============================================================


    echo


    exit 0


    # --------------------------------------------------------------

    # Этот сценарий имеет недоработку.

    # Граничные ячейки сверху, снизу и сбоков остаются пустыми.

    # Упражнение: Доработайте сценарий таким образом, чтобы ,

    # + левая и правая стороны как бы "соприкасались",

    # + так же и верхняя и нижняя стороны.

    Пример A-12. Файл с первым поколением для игры "Жизнь"

    # Это файл-пример, содержащий "поколение 0", для сценария "life.sh".

    # --------------------------------------------------------------

    # Игровое поле имеет размер 10 x 10, точкой обозначается "живая" особь,

    #+ символом подчеркивания -- пустая ячейка. Мы не можем использовать пробелы,

    #+ для обозначения пустых ячеек, из-за особенностей строения массивов в Bash.

    # [Упражнение для читателей: объясните, почему?.]

    #

    # Строки, начинающиеся с символа '#' считаются комментариями, сценарий их игнорирует.

    __.__..___

    ___._.____

    ____.___..

    _._______.

    ____._____

    ..__...___

    ____._____

    ___...____

    __.._..___

    _..___..__

    +++

    Следующие два сценария предоставил Mark Moraes, из университета в Торонто. См. файл "Moraes-COPYRIGHT", который содержит указание на авторские права.

    Пример A-13. behead: Удаление заголовков из электронных писем и новостей

    #! /bin/sh

    # Удаление заголовков из электронных писем и новостей т.е. до первой

    # пустой строки

    # Mark Moraes, Университет в Торонто


    # ==> Такие комментарии добавлены автором документа.


    if [ $# -eq 0 ]; then

    # ==> Если входной аргумент не задан (файл), то выводить результат на stdin.

    sed -e '1,/^$/d' -e '/^[ ]*$/d'

    # --> Удалить пустые строки и все строки предшествующие им

    else

    # ==> Если аргумент командной строки задан, то использовать его как имя файла.

    for i do

    sed -e '1,/^$/d' -e '/^[ ]*$/d' $i

    # --> То же, что и выше.

    done

    fi


    # ==> Упражнение: Добавьте проверку на наличие ошибок.

    # ==>

    # ==> Обратите внимание -- как похожи маленькие сценарии sed, за исключением передачи аргумента.

    # ==> Можно ли его оформит в виде функции? Почему да или почему нет?

    Пример A-14. ftpget: Скачивание файлов по ftp

    #! /bin/sh

    # $Id: ftpget,v 1.2 91/05/07 21:15:43 moraes Exp $

    # Сценарий устанавливает анонимное соединение с ftp-сервером.

    # Простой и быстрый - написан как дополнение к ftplist

    # -h -- удаленный сервер (по-умолчанию prep.ai.mit.edu)

    # -d -- каталог на сервере - вы можете указать последовательность из нескольких ключей -d

    # Если вы используете относительные пути,

    # будьте внимательны при задании последовательности.

    # (по-умолчанию -- каталог пользователя ftp)

    # -v -- "многословный" режим, будет показывать все ответы ftp-сервера

    # -f -- file[:localfile] скачивает удаленный file и записывает под именем localfile

    # -m -- шаблон для mget. Не забудьте взять в кавычки!

    # -c -- локальный каталог

    # Например,

    # ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh \

    # -d ../pub/R3/fixes -c ~/fixes -m 'fix*'

    # Эта команда загрузит файл xplaces.shar из ~ftp/contrib с expo.lcs.mit.edu

    # и сохранит под именем xplaces.sh в текущем каталоге, затем заберет все исправления (fixes)

    # из ~ftp/pub/R3/fixes и поместит их в каталог ~/fixes.

    # Очевидно, что последовательность ключей и аргументов очень важна, поскольку

    # она определяет последовательность операций, выполняемых с удаленным ftp-сервером

    #

    # Mark Moraes (moraes@csri.toronto.edu), Feb 1, 1989

    #


    # ==> Эти комментарии добавлены автором документа.


    # PATH=/local/bin:/usr/ucb:/usr/bin:/bin

    # export PATH

    # ==> Первые две строки в оригинальном сценарии вероятно излишни.


    TMPFILE=/tmp/ftp.$$

    # ==> Создан временный файл


    SITE=`domainname`.toronto.edu

    # ==> 'domainname' подобен 'hostname'


    usage="Порядок использования: $0 [-h удаленный_сервер] [-d удаленный_каталог]... [-f удаленный_файл:локальный_файл]... \

    [-c локальный_каталог] [-m шаблон_имен_файлов] [-v]"

    ftpflags="-i -n"

    verbflag=

    set -f # разрешить подстановку имен файлов (globbing) для опции -m

    set x `getopt vh:d:c:m:f: $*`

    if [ $? != 0 ]; then

    echo $usage

    exit 65

    fi

    shift

    trap 'rm -f ${TMPFILE} ; exit' 0 1 2 3 15

    echo "user anonymous ${USER-gnu}@${SITE} > ${TMPFILE}"

    # ==> Добавлены кавычки (рекомендуется).

    echo binary >> ${TMPFILE}

    for i in $* # ==> Разбор командной строки.

    do

    case $i in

    -v) verbflag=-v; echo hash >> ${TMPFILE}; shift;;

    -h) remhost=$2; shift 2;;

    -d) echo cd $2 >> ${TMPFILE};

    if [ x${verbflag} != x ]; then

    echo pwd >> ${TMPFILE};

    fi;

    shift 2;;

    -c) echo lcd $2 >> ${TMPFILE}; shift 2;;

    -m) echo mget "$2" >> ${TMPFILE}; shift 2;;

    -f) f1=`expr "$2" : "\([^:]*\).*"`; f2=`expr "$2" : "[^:]*:\(.*\)"`;

    echo get ${f1} ${f2} >> ${TMPFILE}; shift 2;;

    --) shift; break;;

    esac

    done

    if [ $# -ne 0 ]; then

    echo $usage

    exit 65 # ==> В оригинале было "exit 2", изменено в соответствии со стандартами.

    fi

    if [ x${verbflag} != x ]; then

    ftpflags="${ftpflags} -v"

    fi

    if [ x${remhost} = x ]; then

    remhost=prep.ai.mit.edu

    # ==> Здесь можете указать свой ftp-сервер по-умолчанию.

    fi

    echo quit >> ${TMPFILE}

    # ==> Все команды сохранены во временном файле.


    ftp ${ftpflags} ${remhost} < ${TMPFILE}

    # ==> Теперь обработать пакетный файл.


    rm -f ${TMPFILE}

    # ==> В заключение, удалить временный файл (можно скопировать его в системный журнал).


    # ==> Упражнения:

    # ==> ----------

    # ==> 1) Добавьте обработку ошибок.

    # ==> 2) Добавьте уведомление звуковым сигналом.

    Пример A-15. Указание на авторские права

    Следующее соглащение об авторских правах относится к двум, включенным в книгу,

    сценариям от Mark Moraes: "behead.sh" и "ftpget.sh"


    /*

    * Copyright University of Toronto 1988, 1989.

    * Автор: Mark Moraes

    *

    * Автор дает право на использование этого программного обеспечения

    * его изменение и рапространение со следующими ограничениями:

    *

    * 1. Автор и Университет Торонто не отвечают

    * за последствия использования этого программного обеспечения,

    * какими ужасными бы они ни были,

    * даже если они вызваны ошибками в нем.

    *

    * 2. Указание на происхождение программного обеспечения не должно подвергаться изменениям,

    * явно или по оплошности. Так как некоторые пользователи обращаются к исходным текстам,

    * они обязательно должны быть включены в документацию.

    *

    * 3. Измененная версия должна содержать явное упоминание об этом и не должна

    * выдаваться за оригинал. Так как некоторые пользователи обращаются к исходным текстам,

    * они обязательно должны быть включены в документацию.

    *

    * 4. Это соглашение не может удаляться и/или изменяться.

    */

    +

    Antek Sawicki предоставил следующий сценарий, который демонстрирует операцию подстановки параметров, обсуждавшуюся в Section 9.3.

    Пример A-16. password: Генератор случайного 8-ми символьного пароля

    #!/bin/bash

    # Для старых систем может потребоваться указать #!/bin/bash2.

    #

    # Генератор случайных паролей для bash 2.x

    # Автор: Antek Sawicki <tenox@tenox.tc>,

    # который великодушно позволил использовать его в данном документе.

    #

    # ==> Комментарии, добавленные автором документа ==>


    MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

    LENGTH="8"

    # ==> 'LENGTH' можно увеличить, для генерации более длинных паролей.


    while [ "${n:=1}" -le "$LENGTH" ]

    # ==> Напоминаю, что ":=" -- это оператор "подстановки значения по-умолчанию".

    # ==> Таким образом, если 'n' не инициализирована, то в нее заносится 1.

    do

    PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}"

    # ==> Хитро, хитро....


    # ==> Начнем с самых внутренних скобок...

    # ==> ${#MATRIX} -- возвращает длину массива MATRIX.


    # ==> $RANDOM%${#MATRIX} -- возвращает случайное число

    # ==> в диапазоне 1 .. ДЛИНА_МАССИВА(MATRIX) - 1.


    # ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1}

    # ==> возвращает символ из MATRIX, из случайной позиции (найденной выше).

    # ==> См. подстановку параметров {var:pos:len} в Разделе 3.3.1

    # ==> и примеры в этом разделе.


    # ==> PASS=... -- добавление символа к строке PASS, полученной на предыдущих итерациях.


    # ==> Чтобы детальнее проследить ход работы цикла, раскомментируйте следующую строку

    # ==> echo "$PASS"

    # ==> Вы увидите, как на каждом проходе цикла,

    # ==> к строке PASS добавляется по одному символу.


    let n+=1

    # ==> Увеличить 'n' перед началом следующей итерации.

    done


    echo "$PASS" # ==> Или перенаправьте в файл, если пожелаете.


    exit 0

    +

    James R. Van Zandt предоставил следующий сценарий, который демонстрирует применение именованных каналов, по его словам, "на самом деле -- упражнение на применение кавычек и на экранирование".

    Пример A-17. fifo: Создание резервных копий с помощью именованных каналов

    #!/bin/bash

    # ==> Автор:James R. Van Zandt

    # ==> используется с его разрешения.


    # ==> Комментарии, добавленные автором документа.


    HERE=`uname -n` # ==> hostname

    THERE=bilbo

    echo "начало создания резервной копии на $THERE, за `date +%r`"

    # ==> `date +%r` возвращает время в 12-ти часовом формате, т.е. "08:08:34 PM".


    # убедиться в том, что /pipe -- это действительно канал, а не простой файл

    rm -rf /pipe

    mkfifo /pipe # ==> Создание "именованного канала", с именем "/pipe".


    # ==> 'su xyz' -- запускает команду от имени порльзователя "xyz".

    # ==> 'ssh' -- вызов secure shell (вход на удаленную систему).

    su xyz -c "ssh $THERE \"cat >/home/xyz/backup/${HERE}-daily.tar.gz\" < /pipe"&

    cd /

    tar -czf - bin boot dev etc home info lib man root sbin share usr var >/pipe

    # ==> Именованный канал /pipe, используется для передачи данных между процессами:

    # ==> 'tar/gzip' пишет в /pipe, а 'ssh' -- читает из /pipe.


    # ==> В результате будет получена резервная копия всех основных каталогов.


    # ==> В чем состоит преимущество именованного канала, в данной ситуации,

    # ==> перед неименованным каналом "|" ?

    # ==> Будет ли работать неименованный канал в данной ситуации?


    exit 0

    +

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

    Пример A-18. Генерация простых чисел, с использованием оператора деления по модулю (остаток от деления)

    #!/bin/bash

    # primes.sh: Генерация простых чисел, без использования массивов.

    # Автор: Stephane Chazelas.


    # Этот сценарий не использует класический алгоритм "Решето Эратосфена",

    #+ вместо него используется более понятный метод проверки каждого кандидата в простые числа

    #+ путем поиска делителей, с помощью оператора нахождения остатка от деления "%".


    LIMIT=1000 # Простые от 2 до 1000


    Primes()

    {

    (( n = $1 + 1 )) # Перейти к следующему числу.

    shift # Следующий параметр в списке.

    # echo "_n=$n i=$i_"


    if (( n == LIMIT ))

    then echo $*

    return

    fi


    for i; do # "i" устанавливается в "@", предыдущее значение $n.

    # echo "-n=$n i=$i-"

    (( i * i > n )) && break # Оптимизация.

    (( n % i )) && continue # Отсечь составное число с помощью оператора "%".

    Primes $n $@ # Рекурсивный вызов внутри цикла.

    return

    done


    Primes $n $@ $n # Рекурсивный вызов за пределами цикла.

    # Последовательное накопление позиционных параметров.

    # в "$@" накапливаются простые числа.

    }


    Primes 1


    exit 0


    # Раскомментарьте строки 16 и 24, это поможет понять суть происходящего.


    # Сравните скоростные характеристики этого сценария и сценария (ex68.sh),

    # реализующего алгоритм "Решето Эратосфена".


    # Упражнение: Попробуйте реализовать этот сценарий без использования рекурсии.

    # Это даст некоторый выигрыш в скорости.

    +

    Jordi Sanfeliu дал согласие на публикацию своего сценария tree.

    Пример A-19. tree: Вывод дерева каталогов

    #!/bin/sh

    # @(#) tree 1.1 30/11/95 by Jordi Sanfeliu

    # email: mikaku@fiwix.org

    #

    # Начальная версия: 1.0 30/11/95

    # Следующая версия: 1.1 24/02/97 Now, with symbolic links

    # Исправления : Ian Kjos, поддержка недоступных каталогов

    # email: beth13@mail.utexas.edu

    #

    # Tree -- средство просмотра дерева каталогов (очевидно :-) )

    #


    # ==> Используется в данном документе с разрешения автора сценария, Jordi Sanfeliu.

    # ==> Комментарии, добавленные автором документа.

    # ==> Добавлено "окавычивание" аргументов.


    search () {

    for dir in `echo *`

    # ==> `echo *` список всех файлов в текущем каталоге, без символов перевода строки.

    # ==> Тот же эффект дает for dir in *

    # ==> но "dir in `echo *`" не обрабатывет файлы, чьи имена содержат пробелы.

    do

    if [ -d "$dir" ] ; then # ==> Если это каталог (-d)...

    zz=0 # ==> Временная переменная, для сохранения уровня вложенности каталога.

    while [ $zz != $deep ] # Keep track of inner nested loop.

    do

    echo -n "| " # ==> Показать символ вертикальной связи,

    # ==> с 2 пробелами и без перевода строки.

    zz=`expr $zz + 1` # ==> Нарастить zz.

    done

    if [ -L "$dir" ] ; then # ==> Если символическая ссылка на каталог...

    echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'`

    # ==> Показать горизонтальный соединитель и имя связянного каталога, но...

    # ==> без указания даты/времени.

    else

    echo "+---$dir" # ==> Вывести горизонтальный соединитель...

    # ==> и название каталога.

    if cd "$dir" ; then # ==> Если можно войти в каталог...

    deep=`expr $deep + 1` # ==> Нарастить уровень вложенности.

    search # рекурсия ;-)

    numdirs=`expr $numdirs + 1` # ==> Нарастить счетчик каталогов.

    fi

    fi

    fi

    done

    cd .. # ==> Подняться на один уровень вверх.

    if [ "$deep" ] ; then # ==> Если depth = 0 (возвращает TRUE)...

    swfi=1 # ==> выставить признак окончания поиска.

    fi

    deep=`expr $deep - 1` # ==> Уменьшить уровень вложенности.

    }


    # - Main -

    if [ $# = 0 ] ; then

    cd `pwd` # ==> Если аргумент командной строки отсутствует, то используется текущий каталог.

    else

    cd $1 # ==> иначе перейти в заданный каталог.

    fi

    echo "Начальный каталог = `pwd`"

    swfi=0 # ==> Признак завершения поиска.

    deep=0 # ==> Уровень вложенности.

    numdirs=0

    zz=0


    while [ "$swfi" != 1 ] # Пока поиск не закончен...

    do

    search # ==> Вызвать функцию поиска.

    done

    echo "Всего каталогов = $numdirs"


    exit 0

    # ==> Попробуйте разобраться в том как этот сценарий работает.

    Noah Friedman дал разрешение на публикацию своей библиотеки функций для работы со строками, которая, по сути, воспроизводит некоторые библиотечные функции языка C.

    Пример A-20. Функции для работы со строками

    #!/bin/bash


    # string.bash --- эмуляция библиотеки функций string(3)

    # Автор: Noah Friedman <friedman@prep.ai.mit.edu>

    # ==> Используется с его разрешения.

    # Дата создания: 1992-07-01

    # Дата последней модификации: 1993-09-29

    # Public domain


    # Преобразование в синтаксис bash v2 выполнил Chet Ramey


    # Комментарий:

    # Код:


    #:docstring strcat:

    # Порядок использования: strcat s1 s2

    #

    # Strcat добавляет содержимое переменной s2 к переменной s1.

    #

    # Пример:

    # a="foo"

    # b="bar"

    # strcat a b

    # echo $a

    # => foobar

    #

    #:end docstring:


    ###;;;autoload

    function strcat ()

    {

    local s1_val s2_val


    s1_val=${!1} # косвенная ссылка

    s2_val=${!2}

    eval "$1"=\'"${s1_val}${s2_val}"\'

    # ==> eval $1='${s1_val}${s2_val}' во избежание проблем,

    # ==> если одна из переменных содержит одиночную кавычку.

    }


    #:docstring strncat:

    # Порядок использования: strncat s1 s2 $n

    #

    # Аналог strcat, но добавляет не более n символов из

    # переменной s2. Результат выводится на stdout.

    #

    # Пример:

    # a=foo

    # b=barbaz

    # strncat a b 3

    # echo $a

    # => foobar

    #

    #:end docstring:


    ###;;;autoload

    function strncat ()

    {

    local s1="$1"

    local s2="$2"

    local -i n="$3"

    local s1_val s2_val


    s1_val=${!s1} # ==> косвенная ссылка

    s2_val=${!s2}


    if [ ${#s2_val} -gt ${n} ]; then

    s2_val=${s2_val:0:$n} # ==> выделение подстроки

    fi


    eval "$s1"=\'"${s1_val}${s2_val}"\'

    # ==> eval $1='${s1_val}${s2_val}' во избежание проблем,

    # ==> если одна из переменных содержит одиночную кавычку.

    }


    #:docstring strcmp:

    # Порядок использования: strcmp $s1 $s2

    #

    # Strcmp сравнивает две строки и возвращает число меньше, равно

    # или больше нуля, в зависимости от результатов сравнения.

    #:end docstring:


    ###;;;autoload

    function strcmp ()

    {

    [ "$1" = "$2" ] && return 0


    [ "${1}" '<' "${2}" ] > /dev/null && return -1


    return 1

    }


    #:docstring strncmp:

    # Порядок использования: strncmp $s1 $s2 $n

    #

    # Подобна strcmp, но сравнивает не более n символов

    #:end docstring:


    ###;;;autoload

    function strncmp ()

    {

    if [ -z "${3}" -o "${3}" -le "0" ]; then

    return 0

    fi


    if [ ${3} -ge ${#1} -a ${3} -ge ${#2} ]; then

    strcmp "$1" "$2"

    return $?

    else

    s1=${1:0:$3}

    s2=${2:0:$3}

    strcmp $s1 $s2

    return $?

    fi

    }


    #:docstring strlen:

    # Порядок использования: strlen s

    #

    # возвращает количество символов в строке s.

    #:end docstring:


    ###;;;autoload

    function strlen ()

    {

    eval echo "\${#${1}}"

    # ==> Возвращает длину переменной,

    # ==> чье имя передается как аргумент.

    }


    #:docstring strspn:

    # Порядок использования: strspn $s1 $s2

    #

    # Strspn возвращает максимальную длину сегмента в строке s1,

    # который полностью состоит из символов строки s2.

    #:end docstring:


    ###;;;autoload

    function strspn ()

    {

    # Сброс содержимого переменной IFS позволяет обрабатывать пробелы как обычные символы.

    local IFS=

    local result="${1%%[!${2}]*}"


    echo ${#result}

    }


    #:docstring strcspn:

    # Порядок использования: strcspn $s1 $s2

    #

    # Strcspn возвращает максимальную длину сегмента в строке s1,

    # который полностью не содержит символы из строки s2.

    #:end docstring:


    ###;;;autoload

    function strcspn ()

    {

    # Сброс содержимого переменной IFS позволяет обрабатывать пробелы как обычные символы.

    local IFS=

    local result="${1%%[${2}]*}"


    echo ${#result}

    }


    #:docstring strstr:

    # Порядок использования: strstr s1 s2

    #

    # Strstr выводит подстроку первого вхождения строки s2

    # в строке s1, или ничего не выводит, если подстрока s2 в строке s1 не найдена.

    # Если s2 содержит строку нулевой длины, то strstr выводит строку s1.

    #:end docstring:


    ###;;;autoload

    function strstr ()

    {

    # Если s2 -- строка нулевой длины, то вывести строку s1

    [ ${#2} -eq 0 ] && { echo "$1" ; return 0; }


    # не выводить ничего, если s2 не найдена в s1

    case "$1" in

    *$2*) ;;

    *) return 1;;

    esac


    # использовать шаблон, для удаления всех несоответствий после s2 в s1

    first=${1/$2*/}


    # Затем удалить все несоответствия с начала строки

    echo "${1##$first}"

    }


    #:docstring strtok:

    # Порядок использования: strtok s1 s2

    #

    # Strtok рассматривает строку s1, как последовательность из 0, или более,

    # лексем (токенов), разделенных символами строки s2

    # При первом вызове (с непустым аргументом s1)

    # выводит первую лексему на stdout.

    # Функция запоминает свое положение в строке s1 от вызова к вызову,

    # так что последующие вызовы должны производиться с пустым первым аргументом,

    # чтобы продолжить выделение лексем из строки s1.

    # После вывода последней лексемы, все последующие вызовы будут выводить на stdout

    # пустое значение. Строка-разделитель может изменяться от вызова к вызову.

    #:end docstring:


    ###;;;autoload

    function strtok ()

    {

    :

    }


    #:docstring strtrunc:

    # Порядок использования: strtrunc $n $s1 {$s2} {$...}

    #

    # Используется многими функциями, такими как strncmp, чтобы отсечь "лишние" символы.

    # Выводит первые n символов в каждой из строк s1 s2 ... на stdout.

    #:end docstring:


    ###;;;autoload

    function strtrunc ()

    {

    n=$1 ; shift

    for z; do

    echo "${z:0:$n}"

    done

    }


    # provide string


    # string.bash конец библиотеки


    # ========================================================================== #

    # ==> Все, что находится ниже, добавлено автором документа.


    # ==> Чтобы этот сценарий можно было использовать как "библиотеку", необходимо

    # ==> удалить все, что находится ниже и "source" этот файл в вашем сценарии.


    # strcat

    string0=one

    string1=two

    echo

    echo "Проверка функции \"strcat\" :"

    echo "Изначально \"string0\" = $string0"

    echo "\"string1\" = $string1"

    strcat string0 string1

    echo "Теперь \"string0\" = $string0"

    echo


    # strlen

    echo

    echo "Проверка функции \"strlen\" :"

    str=123456789

    echo "\"str\" = $str"

    echo -n "Длина строки \"str\" = "

    strlen str

    echo


    # Упражнение:

    # ---------

    # Добавьте проверку остальных функций.


    exit 0

    Michael Zick предоставил очень сложный пример работы с массивами и утилитой md5sum, используемой для кодирования сведений о каталоге.

    От переводчика:

    К своему стыду вынужден признаться, что перевод комментариев оказался мне не "по зубам", поэтому оставляю этот сценарий без перевода.

    Пример A-21. Directory information

    #! /bin/bash

    # directory-info.sh

    # Parses and lists directory information.


    # NOTE: Change lines 273 and 353 per "README" file.


    # Michael Zick is the author of this script.

    # Used here with his permission.


    # Controls

    # If overridden by command arguments, they must be in the order:

    # Arg1: "Descriptor Directory"

    # Arg2: "Exclude Paths"

    # Arg3: "Exclude Directories"

    #

    # Environment Settings override Defaults.

    # Command arguments override Environment Settings.


    # Default location for content addressed file descriptors.

    MD5UCFS=${1:-${MD5UCFS:-'/tmpfs/ucfs'}}


    # Directory paths never to list or enter

    declare -a \

    EXCLUDE_PATHS=${2:-${EXCLUDE_PATHS:-'(/proc /dev /devfs /tmpfs)'}}


    # Directories never to list or enter

    declare -a \

    EXCLUDE_DIRS=${3:-${EXCLUDE_DIRS:-'(ucfs lost+found tmp wtmp)'}}


    # Files never to list or enter

    declare -a \

    EXCLUDE_FILES=${3:-${EXCLUDE_FILES:-'(core "Name with Spaces")'}}


    # Here document used as a comment block.

    : << LSfieldsDoc

    # # # # # List Filesystem Directory Information # # # # #

    #

    # ListDirectory "FileGlob" "Field-Array-Name"

    # or

    # ListDirectory -of "FileGlob" "Field-Array-Filename"

    # '-of' meaning 'output to filename'

    # # # # #


    String format description based on: ls (GNU fileutils) version 4.0.36


    Produces a line (or more) formatted:

    inode permissions hard-links owner group ...

    32736 -rw------- 1 mszick mszick


    size day month date hh:mm:ss year path

    2756608 Sun Apr 20 08:53:06 2003 /home/mszick/core


    Unless it is formatted:

    inode permissions hard-links owner group ...

    266705 crw-rw---- 1 root uucp


    major minor day month date hh:mm:ss year path

    4, 68 Sun Apr 20 09:27:33 2003 /dev/ttyS4

    NOTE: that pesky comma after the major number


    NOTE: the 'path' may be multiple fields:

    /home/mszick/core

    /proc/982/fd/0 -> /dev/null

    /proc/982/fd/1 -> /home/mszick/.xsession-errors

    /proc/982/fd/13 -> /tmp/tmpfZVVOCs (deleted)

    /proc/982/fd/7 -> /tmp/kde-mszick/ksycoca

    /proc/982/fd/8 -> socket:[11586]

    /proc/982/fd/9 -> pipe:[11588]


    If that isn't enough to keep your parser guessing,

    either or both of the path components may be relative:

    ../Built-Shared -> Built-Static

    ../linux-2.4.20.tar.bz2 -> ../../../SRCS/linux-2.4.20.tar.bz2


    The first character of the 11 (10?) character permissions field:

    's' Socket

    'd' Directory

    'b' Block device

    'c' Character device

    'l' Symbolic link

    NOTE: Hard links not marked - test for identical inode numbers

    on identical filesystems.

    All information about hard linked files are shared, except

    for the names and the name's location in the directory system.

    NOTE: A "Hard link" is known as a "File Alias" on some systems.

    '-' An undistingushed file


    Followed by three groups of letters for: User, Group, Others

    Character 1: '-' Not readable; 'r' Readable

    Character 2: '-' Not writable; 'w' Writable

    Character 3, User and Group: Combined execute and special

    '-' Not Executable, Not Special

    'x' Executable, Not Special

    's' Executable, Special

    'S' Not Executable, Special

    Character 3, Others: Combined execute and sticky (tacky?)

    '-' Not Executable, Not Tacky

    'x' Executable, Not Tacky

    't' Executable, Tacky

    'T' Not Executable, Tacky


    Followed by an access indicator

    Haven't tested this one, it may be the eleventh character

    or it may generate another field

    ' ' No alternate access

    '+' Alternate access

    LSfieldsDoc


    ListDirectory()

    {

    local -a T

    local -i of=0 # Default return in variable

    # OLD_IFS=$IFS # Using BASH default ' \t\n'


    case "$#" in

    3) case "$1" in

    -of) of=1 ; shift ;;

    * ) return 1 ;;

    esac ;;

    2) : ;; # Poor man's "continue"

    *) return 1 ;;

    esac


    # NOTE: the (ls) command is NOT quoted (")

    T=( $(ls --inode --ignore-backups --almost-all --directory \

    --full-time --color=none --time=status --sort=none \

    --format=long $1) )


    case $of in

    # Assign T back to the array whose name was passed as $2

    0) eval $2=\( \"\$\{T\[@\]\}\" \) ;;

    # Write T into filename passed as $2

    1) echo "${T[@]}" > "$2" ;;

    esac

    return 0

    }


    # # # # # Is that string a legal number? # # # # #

    #

    # IsNumber "Var"

    # # # # # There has to be a better way, sigh...


    IsNumber()

    {

    local -i int

    if [ $# -eq 0 ]

    then

    return 1

    else

    (let int=$1) 2>/dev/null

    return $? # Exit status of the let thread

    fi

    }


    # # # # # Index Filesystem Directory Information # # # # #

    #

    # IndexList "Field-Array-Name" "Index-Array-Name"

    # or

    # IndexList -if Field-Array-Filename Index-Array-Name

    # IndexList -of Field-Array-Name Index-Array-Filename

    # IndexList -if -of Field-Array-Filename Index-Array-Filename

    # # # # #


    : << IndexListDoc

    Walk an array of directory fields produced by ListDirectory


    Having suppressed the line breaks in an otherwise line oriented

    report, build an index to the array element which starts each line.


    Each line gets two index entries, the first element of each line

    (inode) and the element that holds the pathname of the file.


    The first index entry pair (Line-Number==0) are informational:

    Index-Array-Name[0] : Number of "Lines" indexed

    Index-Array-Name[1] : "Current Line" pointer into Index-Array-Name


    The following index pairs (if any) hold element indexes into

    the Field-Array-Name per:

    Index-Array-Name[Line-Number * 2] : The "inode" field element.

    NOTE: This distance may be either +11 or +12 elements.

    Index-Array-Name[(Line-Number * 2) + 1] : The "pathname" element.

    NOTE: This distance may be a variable number of elements.

    Next line index pair for Line-Number+1.

    IndexListDoc


    IndexList()

    {

    local -a LIST # Local of listname passed

    local -a -i INDEX=( 0 0 ) # Local of index to return

    local -i Lidx Lcnt

    local -i if=0 of=0 # Default to variable names


    case "$#" in # Simplistic option testing

    0) return 1 ;;

    1) return 1 ;;

    2) : ;; # Poor man's continue

    3) case "$1" in

    -if) if=1 ;;

    -of) of=1 ;;

    * ) return 1 ;;

    esac ; shift ;;

    4) if=1 ; of=1 ; shift ; shift ;;

    *) return 1

    esac


    # Make local copy of list

    case "$if" in

    0) eval LIST=\( \"\$\{$1\[@\]\}\" \) ;;

    1) LIST=( $(cat $1) ) ;;

    esac


    # Grok (grope?) the array

    Lcnt=${#LIST[@]}

    Lidx=0

    until (( Lidx >= Lcnt ))

    do

    if IsNumber ${LIST[$Lidx]}

    then

    local -i inode name

    local ft

    inode=Lidx

    local m=${LIST[$Lidx+2]} # Hard Links field

    ft=${LIST[$Lidx+1]:0:1} # Fast-Stat

    case $ft in

    b) ((Lidx+=12)) ;; # Block device

    c) ((Lidx+=12)) ;; # Character device

    *) ((Lidx+=11)) ;; # Anything else

    esac

    name=Lidx

    case $ft in

    -) ((Lidx+=1)) ;; # The easy one

    b) ((Lidx+=1)) ;; # Block device

    c) ((Lidx+=1)) ;; # Character device

    d) ((Lidx+=1)) ;; # The other easy one

    l) ((Lidx+=3)) ;; # At LEAST two more fields

    # A little more elegance here would handle pipes,

    #+ sockets, deleted files - later.

    *) until IsNumber ${LIST[$Lidx]} || ((Lidx >= Lcnt))

    do

    ((Lidx+=1))

    done

    ;; # Not required

    esac

    INDEX[${#INDEX[*]}]=$inode

    INDEX[${#INDEX[*]}]=$name

    INDEX[0]=${INDEX[0]}+1 # One more "line" found

    # echo "Line: ${INDEX[0]} Type: $ft Links: $m Inode: \

    # ${LIST[$inode]} Name: ${LIST[$name]}"


    else

    ((Lidx+=1))

    fi

    done

    case "$of" in

    0) eval $2=\( \"\$\{INDEX\[@\]\}\" \) ;;

    1) echo "${INDEX[@]}" > "$2" ;;

    esac

    return 0 # What could go wrong?

    }


    # # # # # Content Identify File # # # # #

    #

    # DigestFile Input-Array-Name Digest-Array-Name

    # or

    # DigestFile -if Input-FileName Digest-Array-Name

    # # # # #


    # Here document used as a comment block.

    : <<DigestFilesDoc


    The key (no pun intended) to a Unified Content File System (UCFS)

    is to distinguish the files in the system based on their content.

    Distinguishing files by their name is just, so, 20th Century.


    The content is distinguished by computing a checksum of that content.

    This version uses the md5sum program to generate a 128 bit checksum

    representative of the file's contents.

    There is a chance that two files having different content might

    generate the same checksum using md5sum (or any checksum). Should

    that become a problem, then the use of md5sum can be replace by a

    cyrptographic signature. But until then...


    The md5sum program is documented as outputting three fields (and it

    does), but when read it appears as two fields (array elements). This

    is caused by the lack of whitespace between the second and third field.

    So this function gropes the md5sum output and returns:

    [0] 32 character checksum in hexidecimal (UCFS filename)

    [1] Single character: ' ' text file, '*' binary file

    [2] Filesystem (20th Century Style) name

    Note: That name may be the character '-' indicating STDIN read.


    DigestFilesDoc


    DigestFile()

    {

    local if=0 # Default, variable name

    local -a T1 T2


    case "$#" in

    3) case "$1" in

    -if) if=1 ; shift ;;

    * ) return 1 ;;

    esac ;;

    2) : ;; # Poor man's "continue"

    *) return 1 ;;

    esac


    case $if in

    0) eval T1=\( \"\$\{$1\[@\]\}\" \)

    T2=( $(echo ${T1[@]} | md5sum -) )

    ;;

    1) T2=( $(md5sum $1) )

    ;;

    esac


    case ${#T2[@]} in

    0) return 1 ;;

    1) return 1 ;;

    2) case ${T2[1]:0:1} in # SanScrit-2.0.5

    \*) T2[${#T2[@]}]=${T2[1]:1}

    T2[1]=\*

    ;;

    *) T2[${#T2[@]}]=${T2[1]}

    T2[1]=" "

    ;;

    esac

    ;;

    3) : ;; # Assume it worked

    *) return 1 ;;

    esac


    local -i len=${#T2[0]}

    if [ $len -ne 32 ] ; then return 1 ; fi

    eval $2=\( \"\$\{T2\[@\]\}\" \)

    }


    # # # # # Locate File # # # # #

    #

    # LocateFile [-l] FileName Location-Array-Name

    # or

    # LocateFile [-l] -of FileName Location-Array-FileName

    # # # # #


    # A file location is Filesystem-id and inode-number


    # Here document used as a comment block.

    : <<StatFieldsDoc

    Based on stat, version 2.2

    stat -t and stat -lt fields

    [0] name

    [1] Total size

    File - number of bytes

    Symbolic link - string length of pathname

    [2] Number of (512 byte) blocks allocated

    [3] File type and Access rights (hex)

    [4] User ID of owner

    [5] Group ID of owner

    [6] Device number

    [7] Inode number

    [8] Number of hard links

    [9] Device type (if inode device) Major

    [10] Device type (if inode device) Minor

    [11] Time of last access

    May be disabled in 'mount' with noatime

    atime of files changed by exec, read, pipe, utime, mknod (mmap?)

    atime of directories changed by addition/deletion of files

    [12] Time of last modification

    mtime of files changed by write, truncate, utime, mknod

    mtime of directories changed by addtition/deletion of files

    [13] Time of last change

    ctime reflects time of changed inode information (owner, group

    permissions, link count

    -*-*- Per:

    Return code: 0

    Size of array: 14

    Contents of array

    Element 0: /home/mszick

    Element 1: 4096

    Element 2: 8

    Element 3: 41e8

    Element 4: 500

    Element 5: 500

    Element 6: 303

    Element 7: 32385

    Element 8: 22

    Element 9: 0

    Element 10: 0

    Element 11: 1051221030

    Element 12: 1051214068

    Element 13: 1051214068


    For a link in the form of linkname -> realname

    stat -t linkname returns the linkname (link) information

    stat -lt linkname returns the realname information


    stat -tf and stat -ltf fields

    [0] name

    [1] ID-0? # Maybe someday, but Linux stat structure

    [2] ID-0? # does not have either LABEL nor UUID

    # fields, currently information must come

    # from file-system specific utilities

    These will be munged into:

    [1] UUID if possible

    [2] Volume Label if possible

    Note: 'mount -l' does return the label and could return the UUID


    [3] Maximum length of filenames

    [4] Filesystem type

    [5] Total blocks in the filesystem

    [6] Free blocks

    [7] Free blocks for non-root user(s)

    [8] Block size of the filesystem

    [9] Total inodes

    [10] Free inodes


    -*-*- Per:

    Return code: 0

    Size of array: 11

    Contents of array

    Element 0: /home/mszick

    Element 1: 0

    Element 2: 0

    Element 3: 255

    Element 4: ef53

    Element 5: 2581445

    Element 6: 2277180

    Element 7: 2146050

    Element 8: 4096

    Element 9: 1311552

    Element 10: 1276425


    StatFieldsDoc


    # LocateFile [-l] FileName Location-Array-Name

    # LocateFile [-l] -of FileName Location-Array-FileName


    LocateFile()

    {

    local -a LOC LOC1 LOC2

    local lk="" of=0


    case "$#" in

    0) return 1 ;;

    1) return 1 ;;

    2) : ;;

    *) while (( "$#" > 2 ))

    do

    case "$1" in

    -l) lk=-1 ;;

    -of) of=1 ;;

    *) return 1 ;;

    esac

    shift

    done ;;

    esac


    # More Sanscrit-2.0.5

    # LOC1=( $(stat -t $lk $1) )

    # LOC2=( $(stat -tf $lk $1) )

    # Uncomment above two lines if system has "stat" command installed.

    LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11}

    ${LOC2[@]:1:2} ${LOC2[@]:4:1} )


    case "$of" in

    0) eval $2=\( \"\$\{LOC\[@\]\}\" \) ;;

    1) echo "${LOC[@]}" > "$2" ;;

    esac

    return 0

    # Which yields (if you are lucky, and have "stat" installed)

    # -*-*- Location Discriptor -*-*-

    # Return code: 0

    # Size of array: 15

    # Contents of array

    # Element 0: /home/mszick 20th Century name

    # Element 1: 41e8 Type and Permissions

    # Element 2: 500 User

    # Element 3: 500 Group

    # Element 4: 303 Device

    # Element 5: 32385 inode

    # Element 6: 22 Link count

    # Element 7: 0 Device Major

    # Element 8: 0 Device Minor

    # Element 9: 1051224608 Last Access

    # Element 10: 1051214068 Last Modify

    # Element 11: 1051214068 Last Status

    # Element 12: 0 UUID (to be)

    # Element 13: 0 Volume Label (to be)

    # Element 14: ef53 Filesystem type

    }


    # And then there was some test code


    ListArray() # ListArray Name

    {

    local -a Ta


    eval Ta=\( \"\$\{$1\[@\]\}\" \)

    echo

    echo "-*-*- List of Array -*-*-"

    echo "Size of array $1: ${#Ta[*]}"

    echo "Contents of array $1:"

    for (( i=0 ; i<${#Ta[*]} ; i++ ))

    do

    echo -e "\tElement $i: ${Ta[$i]}"

    done

    return 0

    }


    declare -a CUR_DIR

    # For small arrays

    ListDirectory "${PWD}" CUR_DIR

    ListArray CUR_DIR


    declare -a DIR_DIG

    DigestFile CUR_DIR DIR_DIG

    echo "The new \"name\" (checksum) for ${CUR_DIR[9]} is ${DIR_DIG[0]}"


    declare -a DIR_ENT

    # BIG_DIR # For really big arrays - use a temporary file in ramdisk

    # BIG-DIR # ListDirectory -of "${CUR_DIR[11]}/*" "/tmpfs/junk2"

    ListDirectory "${CUR_DIR[11]}/*" DIR_ENT


    declare -a DIR_IDX

    # BIG-DIR # IndexList -if "/tmpfs/junk2" DIR_IDX

    IndexList DIR_ENT DIR_IDX


    declare -a IDX_DIG

    # BIG-DIR # DIR_ENT=( $(cat /tmpfs/junk2) )

    # BIG-DIR # DigestFile -if /tmpfs/junk2 IDX_DIG

    DigestFile DIR_ENT IDX_DIG

    # Small (should) be able to parallize IndexList & DigestFile

    # Large (should) be able to parallize IndexList & DigestFile & the assignment

    echo "The \"name\" (checksum) for the contents of ${PWD} is ${IDX_DIG[0]}"


    declare -a FILE_LOC

    LocateFile ${PWD} FILE_LOC

    ListArray FILE_LOC


    exit 0

    Stephane Chazelas демонстрирует возможность объектно ориентированного подхода к программированию в Bash-сценариях.

    Пример A-22. Объектно ориентированная база данных

    #!/bin/bash

    # obj-oriented.sh: Объектно ориентрованный подход к программированию в сценариях.

    # Автор: Stephane Chazelas.


    person.new() # Очень похоже на объявление класса в C++.

    {

    local obj_name=$1 name=$2 firstname=$3 birthdate=$4


    eval "$obj_name.set_name() {

    eval \"$obj_name.get_name() {

    echo \$1

    }\"

    }"


    eval "$obj_name.set_firstname() {

    eval \"$obj_name.get_firstname() {

    echo \$1

    }\"

    }"


    eval "$obj_name.set_birthdate() {

    eval \"$obj_name.get_birthdate() {

    echo \$1

    }\"

    eval \"$obj_name.show_birthdate() {

    echo \$(date -d \"1/1/1970 0:0:\$1 GMT\")

    }\"

    eval \"$obj_name.get_age() {

    echo \$(( (\$(date +%s) - \$1) / 3600 / 24 / 365 ))

    }\"

    }"


    $obj_name.set_name $name

    $obj_name.set_firstname $firstname

    $obj_name.set_birthdate $birthdate

    }


    echo


    person.new self Bozeman Bozo 101272413

    # Создается экземпляр класса "person.new" (фактически -- вызов функции с аргументами).


    self.get_firstname # Bozo

    self.get_name # Bozeman

    self.get_age # 28

    self.get_birthdate # 101272413

    self.show_birthdate # Sat Mar 17 20:13:33 MST 1973


    echo


    # typeset -f

    # чтобы просмотреть перечень созданных функций.


    exit 0








     


    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Другие сайты | Наверх