Усовершенствованные методы

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

Используя оценку, Встроенную для Структур данных, Массивов и Косвенности

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

Например, следующий сценарий присваивает значение 3 к переменной X и затем распечатывает значение:

#!/bin/sh
eval X=3
echo $X

Для таких простых примеров, eval встроенный является лишним. Однако поведение eval когда необходимо создать или выбрать имена переменной программно, встроенный становится намного более интересным. Например, следующий сценарий также присваивает значение 3 к переменной X:

#!/bin/sh
 
VARIABLE="X"
eval $VARIABLE=3
echo $X

Когда eval встроенный оценивает его параметры, это делает так на двух шагах. В первом шаге переменные заменяются их значениями. В предыдущем примере, букве X вставляется вместо $VARIABLE. Таким образом результатом первого шага является следующая строка:

X=3

На втором шаге, eval встроенный выполняет оператор, сгенерированный первым шагом, таким образом присваивая значение 3 к переменной X. Как дополнительное доказательство, echo оператор в конце сценария распечатывает значение 3.

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

Сложный пример: установка и печать значений произвольных переменных

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

#!/bin/sh
echo "Enter variable name and value separated by a space"
read VARIABLE VALUE
echo Assigning the value $VALUE to variable $VARIABLE
eval $VARIABLE=$VALUE
 
# print the value
eval echo "$"$VARIABLE
 
# export the value
eval export $VARIABLE
 
# print the exported variables.
export

Выполните этот сценарий и введите что-то как MYVAR 33. Сценарий присваивает значение 33 к переменной MYVAR (или безотносительно имени переменной Вы вошли).

Необходимо заметить, что команда эха имеет дополнительный знак доллара ($) в кавычках. В первый раз eval встроенные синтаксические анализы строка, заключенный в кавычки знак доллара упрощен до просто знака доллара. Вы могли также окружить этот знак доллара одинарными кавычками или заключить его в кавычки с наклонной чертой влево, как описано в Заключении в кавычки Специальных символов. Результатом является то же.

Таким образом, оператор:

eval echo "$"$VARIABLE

оценивает к:

echo $MYVAR

Практический Пример: Используя оценку для Моделирования Массива

В Переменных Shell и Печати, Вы изучили, как считать переменные из стандартного ввода. Это было ограничено до некоторой степени неспособностью считать неизвестное число вводимых пользователями значений.

Сценарий ниже решает это использование задач eval путем создания серии переменных для содержания значений моделируемого массива.

#!/bin/sh
 
COUNTER=0
VALUE="-1"
echo "Enter a series of lines of test.  Enter a blank line to end."
 
while [ "x$VALUE" != "x" ] ; do
        read VALUE
        eval ARRAY_$COUNTER=$VALUE
        eval export ARRAY_$COUNTER
        COUNTER=$(expr $COUNTER '+' 1) # More on this in Paint by Numbers
done
COUNTER=$(expr $COUNTER '-' 1) # Subtract one for the blank value at the end.
 
# print the exported variables.
COUNTERB=0;
 
echo "Printing values."
while [ $COUNTERB -lt $COUNTER ] ; do
        echo "ARRAY[$COUNTERB] = $(eval echo "$"ARRAY_$COUNTERB)"
        COUNTERB=$(expr $COUNTERB '+' 1) # More on this in Paint by Numbers
done

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

#!/bin/sh
 
COUNTER=0
VALUE="-1"
echo "Enter a series of lines of numbers separated by spaces."
 
read LIST
IFS=" "
for VALUE in $LIST ; do
        eval ARRAY_$COUNTER=$VALUE
        eval export ARRAY_$COUNTER
        COUNTER=$(expr $COUNTER '+' 1) # More on this in Paint by Numbers
done
 
# print the exported variables.
COUNTERB=0;
 
echo "Printing values."
while [ $COUNTERB -lt $COUNTER ] ; do
        echo "ARRAY[$COUNTERB] = $(eval echo '$'ARRAY_$COUNTERB)"
        COUNTERB=$(expr $COUNTERB '+' 1) # More on this in Paint by Numbers
done

Пример структуры данных: связанные списки

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

Точно так же можно использовать eval встроенный для обеспечения уровня абстракции, подобного указателям в C.

Например, следующий сценарий вручную создает связанный список с тремя элементами, затем обходит список:

#!/bin/sh
 
VAR1_VALUE="7"
VAR1_NEXT="VAR2"
 
VAR2_VALUE="11"
VAR2_NEXT="VAR3"
 
VAR3_VALUE="42"
 
HEAD="VAR1"
POS=$HEAD
while [ "x$POS" != "x" ] ; do
        echo "POS: $POS"
        VALUE="$(eval echo '$'$POS'_VALUE')"
        echo "VALUE: $VALUE"
        POS="$(eval echo '$'$POS'_NEXT')"
done

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

Мощный пример: деревья двоичного поиска

Работа с Деревьями двоичного поиска в Начальных точках предоставляет готовой к использованию библиотеке дерева двоичного поиска, записанной как сценарий Оболочки Bourne.

Захват сигналов

Никакое обсуждение усовершенствованного программирования не было бы завершено без объяснения сигнальной обработки. В основанных на UNIX и подобных UNIX операционных системах сигналы обеспечивают примитив средние значения межпроцессного взаимодействия. Сценарий или другой процесс могут отправить сигнал в другой процесс любым использованием kill команда или путем вызова kill функция в программе C. На получение, процесс получения или выходы, игнорирует сигнал или выполняет сигнальную подпрограмму обработчика выбора автора.

Сигналы наиболее часто используются для завершения выполнения процесса дружественным способом, признавая тому процессу возможность очистить, прежде чем это выйдет. Однако они могут также использоваться для других целей. Например, когда окно терминала изменяется в размере, любая рабочая оболочка в том окне получает a SIGWINCH (изменение окна) сигнал. Обычно, этот сигнал проигнорирован, но если программа заботится об изменениях размера окна, это может захватить тот сигнал и обработать его специализированным способом. За исключением SIGKILL сигнал, любой сигнал может быть захвачен и обработан путем вызывания функции C signal.

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

Синтаксис trap следующие:

trap subroutine signal [ signal ... ]

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

Например, если Вы хотите захватить SIGWINCH (изменение окна) сигнал, Вы могли записать следующее утверждение:

trap sigwinch_handler SIGWINCH

После того, как Вы сделаете это заявление, оболочка вызывает подпрограмму sigwinch_handler каждый раз, когда это получает a SIGWINCH сигнал. Сценарий в Перечислении 11-1 распечатывает фразу “Измененный размер окна “. каждый раз, когда Вы корректируете размер своего окна терминала.

Перечисление 11-1  , Устанавливающее сигнальное прерывание обработчика

#!/bin/sh
 
fixrows()
{
        echo "Window size changed."
}
 
echo "Adjust the size of your window now."
trap fixrows SIGWINCH
 
COUNT=0
while [ $COUNT -lt 60 ] ; do
        COUNT=$(($COUNT + 1))
        sleep 1
done

Иногда, вместо того, чтобы захватить сигнал, можно хотеть проигнорировать сигнал полностью. Чтобы сделать это, укажите пустую строку для имени подпрограммы. Например, код в Перечислении 11-2 игнорирует сигнал «прерывания», сгенерированный при нажатии Control-C:

Перечисление 11-2  , Игнорирующее сигнал

#!/bin/sh
trap "" SIGINT
 
echo "This program will sleep for 10 seconds and cannot be killed with"
echo "control-c."
sleep 10

Наконец, сигналы могут использоваться в качестве примитивной формы коммуникации межсценария. Следующие два сценария работают парой. Для наблюдения этого в действии сначала сохраните сценарий в Перечислении 11-3 как ipc1.sh и сценарий в Перечислении 11-4 как ipc2.sh.

Перечисление 11-3  ipc1.sh: Пример межпроцессного взаимодействия сценария, часть 1 2

#!/bin/sh
 
## Save this as ipc1.sh
 
./ipc2.sh &
 
PID=$!
 
sleep 1 # Give it time to launch.
 
kill -HUP $PID

Перечисление 11-4  ipc2.sh: Пример межпроцессного взаимодействия сценария, часть 2 2

#!/bin/sh
 
## Save this as ipc2.sh
 
hup_handler()
{
        echo "SIGHUP RECEIVED."
        exit 0
}
 
trap hup_handler SIGHUP
 
while true ; do
        sleep 1
done

Теперь выполненный ipc1.sh. Это запускает сценарий ipc2.sh в фоновом режиме, использует специальную переменную оболочки $! получить процесс ID последнего фонового процесса (ipc2.sh в этом случае), затем отправляет ему зависание (SIGHUP) сигнальное использование kill.

Поскольку второй сценарий, ipc2.sh, захваченный сигнал зависания, его оболочка тогда вызывает подпрограмму обработчика, hup_handler. Эта подпрограмма распечатывает слова “SIGHUP RECEIVED “. и выходы.

Текстовое форматирование Shell

Один мощный метод, когда запись сценариев оболочки должна использовать в своих интересах функции эмуляции терминала Вашего терминального приложения (является ли это Терминальным, xterm или некоторое другое приложение) вывести на экран отформатированное содержание.

Можно использовать printf команда для простого создания колоночных разметок без любых специальных приемов. Для более визуально захватывающего представления можно добавить цвет или текст, форматирующий, такой как полужирный шрифт или подчеркнутый дисплей с помощью ANSI (VT100/VT220) escape-последовательности.

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

Используя printf Команду для Табличного Расположения

Во многом как C и другие языки, большинство операционных систем, поддерживающих сценарии оболочки также, обеспечивает версию командной строки printf. Эта команда отличается от C printf функция многими способами. Эти различия включают следующее:

  • %c директива не выполняет целое число к преобразованию символов. Единственный способ преобразовать целое число в символ с версией оболочки состоит в том, чтобы сначала преобразовать целое число в восьмеричный и затем распечатать его при помощи восьмеричного значения как переключатель. Например, printf "\144" распечатывает строчную букву d.

  • Поддержка версии командной строки намного меньший набор заполнителей. Например, %p (указатели) не существуют в версии оболочки.

  • Версия командной строки не имеет понятия чисел двойной точности или длинных. Несмотря на то, что флаги с этими модификаторами позволяются (%lld, например), модификаторы проигнорированы. Таким образом нет никакого различия между %d, %ld, и %lld.

  • Большие целые числа могут быть усеченными к 32-разрядным значениям со знаком.

  • Двойная точность значения с плавающей точкой может быть сокращена до значений одинарной точности.

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

Во многом как printf оператор на других языках, сценарии оболочки printf синтаксис следующие:

printf "format string" argument ...

Как C printf функция, командная строка printf строка формата содержит некоторую комбинацию текста, переключатели (\n и \t, например), и заполнители (%d, например).

Самая важная функция printf поскольку табличные разметки являются дополнительной функцией. Между знаком процента и буквой типа, можно поместить число для указания ширины, к которой должно быть дополнено поле. Для заполнителя с плавающей точкой (%f), можно дополнительно указать two числа, разделенные десятичной точкой. Крайнее левое значение указывает общую ширину поля, в то время как самое правое значение указывает число десятичных разрядов, которые должны быть включены. Например, можно распечатать пи к трем цифрам точности в 8 символьных широких полях путем ввода printf "%8.3f" 3.14159265.

В дополнение к ширине дополнения можно добавить определенные префиксы перед шириной поля для указания специальных дополнительных требований. Они:

  • Знак «минус» (-) — указывает, что поле нужно оставить выровненным по ширине. (Поля выровнены по правому краю по умолчанию.)

  • Знак «плюс» (+) — указывает, что знак должен предварительно ожидаться к числовому параметру, даже если он имеет положительное значение.

  • Пространство — указывает, что пространство должно быть добавлено к числовому параметру вместо знака, если значение положительно. (Знак «плюс» имеет приоритет по пространству.)

  • Нуль (0) — указывает, что числовые параметры должны быть дополнены продвижением, обнуляет вместо пробелов. (Знак «минус» имеет приоритет по нулю.)

Например, если Вы хотите составить таблицу на четыре столбца имени, адреса, телефонного номера и GPA, Вы могли бы записать оператор как это:

Перечисление 11-5  Колоночное использование печати printf

#/bin/sh
 
NAME="John Doe"
ADDRESS="1 Fictitious Rd, Bucksnort, TN"
PHONE="(555) 555-5555"
GPA="3.885"
printf "%20s | %30s | %14s | %5s\n" "Name" "Address" "Phone Number" "GPA"
printf "%20s | %30s | %14s | %5.2f\n" "$NAME" "$ADDRESS" "$PHONE" "$GPA"

printf оператор дополняет поля в аккуратные столбцы и усекает GPA к двум десятичным разрядам, оставляя комнату для трех дополнительных символов (сама десятичная точка, те помещают, и ведущее пространство). Необходимо заметить, что дополнительные параметры все окружаются кавычками. Если Вы не сделаете этого, то Вы получите неправильное поведение из-за пробелов в параметрах.

Следующая выборка показывает форматирование чисел:

#!/bin/sh
 
GPA="3.885"
 
printf "%f | whatever\n" "$GPA"
printf "%20f | whatever\n" "$GPA"
printf "%+20f | whatever\n" "$GPA"
printf "%+020f | whatever\n" "$GPA"
printf "%-20f | whatever\n" "$GPA"
printf "%- 20f | whatever\n" "$GPA"

Это распечатывает следующий вывод:

3.885000 | whatever
            3.885000 | whatever
           +3.885000 | whatever
+000000000003.885000 | whatever
3.885000             | whatever
 3.885000            | whatever

Большинство тех же параметров форматирования применяется к %s и %d (включая, удивительно, дополнение нуля аргументов строки). Для получения дополнительной информации см. страницу руководства для printf.

Усечение строк

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

STRING="whatever you want it to be"
TRUNCSTRING="`echo "$STRING" | sed 's/^\(.......\).*$/\1/'`"
echo "$TRUNCSTRING"

Как альтернатива, можно использовать подпрограмму более общего назначения, такую как та в Перечислении 11-6, усекающем строку к произвольной длине путем роста регулярного выражения.

Перечисление 11-6  , Усекающее текст к ширине столбца

trunc_field()
{
    local STR=$1
    local CHARS=$2
    local EXP=""
    local COUNT=0
    while [ $COUNT -lt $CHARS ] ; do
        EXP="$EXP."
        COUNT=`expr $COUNT + 1`
    done
    echo $STR | sed "s/^\($EXP\).*$/\1/"
}

printf "%10s | something\n" "`trunc_field "$TEXT" 20`"

Конечно, можно сделать это намного быстрее или кэширующий эти строки или заменяющий большую часть подпрограммы одной строкой Perl:

echo "$STR" | perl -e "$/=undef; print substr(<STDIN>, 0, $CHARS);"

Наконец, если Вы готовы записать код, который является чрезвычайно непереносимым (использование синтаксиса, даже не работающего в ZSH), можно использовать СПЕЦИФИЧНОЕ ДЛЯ BASH расширение подстроки:

echo "${STR:0:8}"

Можно узнать о подобных операциях в странице руководства для bash в соответствии с “заголовком” Расширения Параметра. Как правило, однако, необходимо избежать таких специфичных для оболочки приемов.

Используя escape-последовательности ANSI

Можно использовать escape-последовательности ANSI, чтобы добавить цвет или форматирующий к тексту, выведенному на экран в терминале, изменить местоположение курсора, установить позиции табуляции, ясные части дисплея, поведение прокрутки изменения и т.д. Этот раздел включает частичный список многих обычно используемых escape-последовательностей, вместе с примерами того, как использовать их.

Существует два способа генерировать escape-последовательности: прямая печать и использование terminfo базы данных. Печать последовательностей непосредственно имеет значительные преимущества производительности, но является менее переносимой, потому что она предполагает, что все терминалы ANSI/VT100/VT220-compliant. Хороший компромисс должен объединить эти два подхода путем кэширования значений, сгенерированных с terminfo командой такой как tput в начале Вашего сценария и затем печати значений непосредственно в другом месте в сценарии.

Генерация Escape-последовательностей с помощью terminfo Базы данных

Генерация escape-последовательностей с terminfo базой данных является относительно прямой, как только Вы знаете что терминальные возможности запросить. Можно найти несколько таблиц, содержащих информацию о возможности, вместе со стандартными значениями ANSI/VT220 для каждой возможности, в Таблицах Escape-последовательности ANSI. (Обратите внимание на то, что не все escape-последовательности ANSI имеют эквивалентные terminfo возможности, и наоборот.)

Как только Вы знаете, какую возможность запросить (вместе с любыми дополнительными параметрами, что необходимо указать), можно использовать tput команда, чтобы вывести escape-последовательность (или получить вывод tput в переменную, таким образом, можно использовать его позже). Например, можно очистить экран со следующей командой:

tput cl

Некоторые terminfo записи базы данных содержат заполнителей для числовых значений, таких как информация о столбце и строка. Самый простой способ использовать их состоит в том, чтобы указать те числовые значения на командной строке при вызове tput. Однако для производительности, это может быть быстрее для замены значениями самостоятельно. Например, возможность cup устанавливает позицию курсора в строку и значение столбца. Следующие наборы команд позиция для расположения в ряд 3, столбец 7:

tput cup 3 7

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

tput cup | less

Путем передачи по каналу данных к less, Вы видите точно что tput инструмент обеспечивает, и можно искать параметры в странице руководства для terminfo. Этот определенный пример распечатывает следующую строку:

^[[%i%p1%d;%p2%dH

%i нотация означает, что первые два (и только первые два) значения являются одним большим, чем Вы могли бы иначе ожидать. (Для терминалов ANSI, столбцов и числа строк от 1, а не от 0). %p1%d средние значения, чтобы продвинуть параметр 1 на штабель и затем сразу распечатать его. Параметр %p2%d эквивалент для параметра 2.

Как Вы видите от даже этого относительно простого примера, язык, используемый для terminfo, довольно сложен. Таким образом, в то время как может быть приемлемо выполнить замену на простые терминалы, такие как VT100 самостоятельно, можно все еще торговать производительностью для мобильности. В целом, лучше позволять tput выполните замены от Вашего имени.

Генерация escape-последовательностей непосредственно

Использовать escape-последовательность ANSI без использования tput, необходимо сначала быть в состоянии распечатать символ ESC из сценария. Существует три способа сделать это:

  • Использовать printf распечатать escape-последовательность. В строке, \e переключитесь распечатывает символ ESC. Это - самый простой способ распечатать escape-последовательности.

    Например, следующий отрывок показывает, как распечатать последовательность сброса (^[c):

    printf "\ec" # resets the screen
  • Встройте символ ESC в свой сценарий. Метод выполнения этого значительно различается от одного редактора другому. В большинстве основанных на тексте редакторов и на самой командной строке, Вы делаете это путем нажатия Control-V, сопровождаемого клавишей Esc. Несмотря на то, что это - самый быстрый способ распечатать escape-последовательность, он имеет недостаток создания Вашего сценария тяжелее для редактирования.

    Например, Вы могли бы записать отрывок как этот:

    echo "^[c" # Read the note below!!!
  • Использовать printf сохранить символ ESC в переменную. Это - рекомендуемый метод, потому что он почти с такой скоростью, как встраивает символ ESC, но не делает код трудно, чтобы считать и отредактировать.

    Например, следующий код отправляет терминальную команду сброса (^[c):

    #!/bin/sh
     
    ESC=`printf "\e"`       # store an escape character
                            # into the variable ESC
    echo "$ESC""c"          # Echo a terminal reset command.

Поскольку терминальная команда сброса является одной только из ряда escape-последовательностей, не запускающихся с левой квадратной скобки, стоит указать на два набора меток двойной кавычки после переменной в вышеупомянутом примере. Без тех оболочка пытается распечатать значение переменной ESCc, который не существует.

Таблицы escape-последовательности ANSI

Существует четыре основных категории кодов Escape:

  • Подпрограммы манипулирования курсором (описанный в Таблице 11-1) позволяют Вам перемещать курсор на экране, показывать или скрывать курсор и предельную прокрутку к только части экрана.

  • Последовательности манипулирования атрибутом (описанный в Атрибуте и Цветных Escape-последовательностях) позволяют Вам устанавливать или атрибуты открытого текста, такие как подчеркивание, полужирный дисплей и обратный дисплей.

  • Цветные последовательности манипулирования (описанный в Атрибуте и Цветных Escape-последовательностях) позволяют Вам изменять основной цвет и цвет фона текста.

  • Другие коды Escape (описанный в Таблице 11-4) поддержка, очищающаяся экран, очищая части экрана, сбрасывая терминал, и устанавливая позиции табуляции.

Курсор и прокрутка escape-последовательностей манипулирования

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

Можно получить текущее число строк и столбцов на экране путем исследования значений переменных оболочки LINES и COLUMNS. Таким образом координаты экрана располагаются от (1, 1) к ($LINES, $COLUMNS). В большинстве современных Оболочек Bourne, значениях для LINES и COLUMNS когда размер окна изменяется, автоматически обновляются. Это - истина и для BASH и для оболочек ZSH.

Если Вы хотите быть особенно умными, можно также захватить SIGWINCH сигнализируйте и обновите понятие своего сценария строк и столбцов, когда оно произойдет. Посмотрите Сигналы Захвата для получения дополнительной информации.

Как только Вы знаете число строк и столбцов на Вашем экране, можно переместить курсор с escape-последовательностями, перечисленными в Таблице 11-1. Например, для установки позиции курсора для расположения в ряд 4, столбец 5, Вы могли дать следующую команду:

printf "\e[4;5H"

Для другого, более быстрые способы распечатать escape-последовательности, посмотрите Генерирующиеся Escape-последовательности Непосредственно.

Табличный 11-1  Курсор и прокрутка escape-последовательностей манипулирования

Возможность Terminfo

Escape-последовательность

Описание

tivis

Примечание: terminfo запись для Терминала не поддерживает эту опцию.

^[[?25l

Скрывает курсор.

tvvis

Примечание: terminfo запись для Терминала не поддерживает эту опцию.

^[[?25h

Показывает курсор.

cup r c

^[[r;cH

Позиция курсора наборов к строке r, столбцу c.

(никакой эквивалент)

^[[6n

Текущая позиция курсора отчетов, как будто введенный с клавиатуры (сообщил как ^[[r;cR).Примечание: это не практично для получения этой информации в сценарии оболочки.

sc

^[7

Сохраняет текущую позицию курсора и стиль.

rc

^[8

Восстановления ранее сохранили позицию курсора и стиль.

cuu r

^[[rA

Повышает курсор r строки.

cud r

^[[rB

Перемещает курсор вниз r строки.

cuf c

^[[cC

Право курсора перемещений c столбцы.

cub c

^[[cD

Курсор перемещений оставил c столбцы.

(никакой эквивалент)

^[[7h

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

(никакой эквивалент)

^[[7l

Включает обертывание строки (на по умолчанию).

(никакой эквивалент)

^[[r

Включает целый экран, прокручивающий (на по умолчанию).

(никакой эквивалент)

^[[S;Er

Включает прокрутку частичного экрана из строки S к строке E и перемещает курсор в вершину этой области.

do

^[D

Перемещает курсор вниз одной строкой.

up

^[M

Повышает курсор одной строкой.

Припишите и окрасьте escape-последовательности

Припишите и окрасьте, escape-последовательности позволяют Вам изменять атрибуты или цвет для текста, который Вы еще не составили. Никакая escape-последовательность (прокрутка несмотря на это) не изменяет ничего, что было уже нарисовано на экране. Escape-последовательности применяются только к последующему тексту.

Например, для рисования красного символа «W» сначала отправьте escape-последовательность для выбирания основного цвета к красному (^[[31m), затем распечатайте символ «W», затем отправьте последовательность сброса атрибута (^[[m), при желании.

Атрибут и цветные коды Escape могут быть объединены с другим атрибутом и цветными кодами Escape в форме ^[[#;#;#;...#m. Например, можно объединить escape-последовательности ^[[1m (полужирный) и ^[[32m зеленый текст) в последовательность ^[[1;32m. Перечисление 11-8 распечатывает знакомую фразу в многократных цветах.

Перечисление 11-8  Используя цвет ANSI

#!/bin/sh
 
printf '\e[41mH\e[42me\e[43ml\e[44;32ml\e[45mo\e[m \e[46;33m'
printf 'W\e[47;30mo\e[40;37mr\e[49;39ml\e[41md\e[42m!\e[m\n'

Таблица 11-2 содержит список возможностей и escape-последовательностей тот стиль текста управления.

Табличные 11-2  escape-последовательности Атрибута

Возможность Terminfo

Escape-последовательность

Описание

Сброс атрибутов

me

^[[m или ^[[0m

Сброс все атрибуты к их значениям по умолчанию.

Установка атрибутов

bold

^[[1m

Включает «полужирный» дисплей. Этот код и код № 2 (dim) являются взаимоисключающими.

dim

^[[2m

Включает «тусклый» дисплей. Этот код и код № 1 (bold) являются взаимоисключающими. Не поддерживаемый в Терминале.

so

Примечание: В terminfo запись базы данных для Терминала, этот атрибут отображается на инверсии, потому что не поддерживается «выдающийся» режим VT100.

^[[3m

Включает «выдающийся» дисплей. Не поддерживаемый в Терминале.

us

^[[4m

Включает подчеркнутый дисплей.

blink

Примечание: terminfo запись для Терминала не поддерживает эту опцию.

^[[5m

<мигание>.

(Никакой эквивалент.)

^[[6m

Быстрое мигание или перечеркивание. (Не поддерживаемый в Терминале; поведение, противоречивое в другом месте.)

mr

^[[7m

Включает инвертированный (обратный) дисплей.

invis

Примечание: terminfo запись для Терминала не поддерживает эту опцию.

^[[8m

Включает скрытый (фон на фоне) дисплей.

^[[9m

Неиспользованный.

Коды 10m19m

Коды выбора шрифта. Неподдерживаемый в большинстве терминальных приложений, включая Терминал.

Очистка атрибутов

(Никакой эквивалент.)

^[[20m

Гарнитура «Fraktur». Неподдерживаемый почти универсально, и Терминал не исключение.

^[[21m

Неиспользованный.

se

Примечание: Технически, эта возможность, как предполагается, заканчивает выдающийся режим, но это перегружается для отключения полужирного яркого/тусклого режима также.

^[[22m

Отключает «яркий» или «тусклый» дисплей. Это отключает любой код 1m или 2m.

se

^[[23m

Отключает «выдающийся» дисплей. Не поддерживаемый в Терминале.

ue

^[[24m

Отключает подчеркнутый дисплей.

(Никакой эквивалент. Использовать me отключить все атрибуты вместо этого.)

^[[25m

</мигание>. Также отключает медленное мигание или перечеркивание (6m) на терминалах та поддержка тот атрибут.

^[[26m

Неиспользованный.

(Никакой эквивалент. Использовать me отключить все атрибуты вместо этого.)

^[[27m

Отключает инвертированный (обратный) дисплей.

(Никакой эквивалент. Использовать me отключить все атрибуты вместо этого.)

^[[28m

Отключает скрытый (фон на фоне) дисплей.

^[[29m

Неиспользованный.

Таблица 11-3 содержит список возможностей и escape-последовательностей, управляющих цветами текста и цветами фона.

Табличные 11-3  escape-последовательности Цвета

Возможность Terminfo

Escape-последовательность

Описание

Основные цвета

setaf 0

^[[30m

Выбирает основной цвет к черному цвету.

setaf 1

^[[31m

Выбирает основной цвет к красному.

setaf 2

^[[32m

Выбирает основной цвет к зеленому.

setaf 3

^[[33m

Выбирает основной цвет к желтому.

setaf 4

^[[34m

Выбирает основной цвет к синему.

setaf 5

^[[35m

Выбирает основной цвет к пурпурному.

setaf 6

^[[36m

Выбирает основной цвет к голубому цвету.

setaf 7

^[[37m

Выбирает основной цвет белому.

^[[38m

Неиспользованный.

setaf 9

^[[39m

Выбирает основной цвет к значению по умолчанию.

Цвета фона

setab 0

^[[40m

Выбирает цвет фона к черному цвету.

setab 1

^[[41m

Выбирает цвет фона к красному.

setab 2

^[[42m

Выбирает цвет фона к зеленому.

setab 3

^[[43m

Выбирает цвет фона к желтому.

setab 4

^[[44m

Выбирает цвет фона к синему.

setab 5

^[[45m

Выбирает цвет фона к пурпурному.

setab 6

^[[46m

Выбирает цвет фона к голубому цвету.

setab 7

^[[47m

Выбирает цвет фона белому.

^[[48m

Неиспользованный.

setab 9

^[[49m

Выбирает цвет фона к значению по умолчанию.

Другие escape-последовательности

В дополнение к обеспечению текстового форматирования escape-последовательности ANSI предоставляют возможность, чтобы сбросить терминал, очистить экран (или части этого), очистить строку (или части этого), и установить или очистить позиции табуляции.

Например, чтобы очистить все существующие позиции табуляции и установить единственную позицию табуляции в столбце 20, Вы могли использовать отрывок, показывают в Перечислении 11-9.

Перечисление 11-9  , Устанавливающее позиции табуляции

#!/bin/sh
echo # Start on a new line
printf "\e[19C" # move right 19 columns to column 20
printf "\e[3g" # clear all tab stops
printf "\e[W" # set a new tab stop
printf "\e[19D" # move back to the left
printf "Tab test\tThis starts at column 20."

Таблица 11-4 содержит список возможностей и escape-последовательностей, выполняющих другие разные задачи, такие как управление курсором, манипулирование позицией табуляции и очистка экрана или частей этого.

Таблица 11-4  Другие коды Escape

Возможность Terminfo

Escape-последовательность

Описание

Сброс терминала

reset

Примечание: Это сбрасывает еще много вещей, чем ^[c. Это не также технически ни одна возможность, а скорее связь rs1, rs2, и rs3.

^[c

Сбрасывает цвета фона и основные цвета к их значениям по умолчанию, очищает экран и перемещает курсор в исходное положение.

Очистка экрана

cd

^[[J или ^[[0J

Очищается в нижнюю часть экрана с помощью текущего фонового цвета.

(никакой эквивалент)

^[[1J

Очищается к вершине экрана с помощью текущего фонового цвета.

cl

^[[2J

Очищает экран к текущему фоновому цвету. На некоторых терминалах курсор сбрасывается к исходному положению.

Очистка текущей строки

ce

^[[K или ^[[0K

Очищается до конца текущей строки.

cb— Не поддерживаемый в terminfo запись для Терминала.

^[[1K

Очищается к началу текущей строки.

(никакой эквивалент)

^[[2K

Очищает текущую строку.

Позиции табуляции

hts

^[[W или ^[[0W

Горизонтальная вкладка набора в позиции курсора.

(никакой эквивалент)

^[[1W

Установите вертикальную вкладку в текущей строке. (Не поддерживаемый в Терминале.)

Коды 2W6W

Избыточные коды, эквивалентные кодам 0g3g.

(никакой эквивалент)

^[[g или ^[[0g

Очистите горизонтальную вкладку в позиции курсора.

(никакой эквивалент)

^ [[1 г

Очистите вертикальную вкладку в текущей строке. (Не поддерживаемый в Терминале.)

(никакой эквивалент)

^ [[2 г

Очистите горизонтальные и вертикальные позиции табуляции для текущей строки только. (Не поддерживаемый в Терминале.)

tbc

^ [[3 г

Очистите все горизонтальные вкладки.

Для получения дополнительной информации

Таблицы в этой главе обеспечивают только некоторые более обычно используемые escape-последовательности и terminfo возможности. Можно найти исчерпывающий список escape-последовательностей ANSI в http://www .inwap.com/pdp10/ansicode.txt и исчерпывающий список terminfo возможности в странице руководства для terminfo.

Перед использованием возможностей или escape-последовательностей не в этой главе, однако, необходимо знать, что большая часть терминального программного обеспечения (включая Терминал в OS X) не поддерживает полный набор escape-последовательностей ANSI или terminfo возможностей.

Неблокирование I/O

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

Во-первых, предупреждение: неблокирование I/O не возможно в чистом сценарии оболочки. Это требует использования внешнего инструмента, устанавливающего терминал в неблокирование. Установка терминала к неблокированию может серьезно перепутать оболочку, таким образом, Вы не должны смешивать неблокирование I/O и блокирование I/O в той же программе.

С тем протестом можно выполнить неблокирование I/O путем записи маленькому помощнику C, такому как этот:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
 
int main(int argc, char *argv[])
{
    int ch;
    int flags = fcntl(STDIN_FILENO, F_GETFL);
    if (flags == -1) return -1; // error
 
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
 
    ch = fgetc(stdin);
    if (ch == EOF) return -1;
    if (ch == -1) return -1;
    printf("%c", ch);
    return 0;
}

Если Вы компилируете этот инструмент и называете его getch, можно тогда использовать его для выполнения ввода терминала неблокирования, как показано в следующем примере:

#!/bin/bash
 
stty -icanon -isig
while true ; do
        echo -n "Enter a character: "
        CHAR=`./getch`
        if [ "x$CHAR" = "x" ] ; then
                echo "NO DATA";
        else
                if [ "x$CHAR" = "xq" ] ; then
                        stty -cbreak
                        exit
                fi
                echo "DATA: $CHAR";
        fi
        sleep 1;
done
 
# never reached
stty -cbreak

Этот сценарий не распечатывает “ДАННЫХ” или “DATA: [некоторый символ]” в зависимости от того, нажали ли Вы ключ в прошлую секунду. (Для остановки сценария нажмите клавишу Q.) Используя тот же метод, можно записать довольно сложные сценарии оболочки, которые могут обнаружить нажатия клавиш при выполнении других задач. Например, Вы могли бы записать игру вони ping, проверяющей на нажатие клавиши в начале каждого цикла рисования шара и если это обнаруживает один, перемещает весло пользователя несколькими пикселями.

Этот сценарий также иллюстрирует другой полезный метод: отключение буферизации ввода. stty команда изменяет три настройки на терминале управления (файл устройств, представляющий текущее Окно терминала, консоль, ssh сеанс или другой канал передачи):

В зависимости от какого Вы делаете, можно также счесть полезным передать -echo флаг. Этот флаг отключает автоматическое эхо введенных символов на экран. При получении символов для полноэкранной игры, например, повторение введенных символов на экран имеет тенденцию иметь катастрофические последствия, в зависимости от того, насколько неудачный синхронизация пользователя при нажатии клавиши.

В зависимости от какого другие флаги Вы передаете, можно хотеть сбросить терминал более полно в конце путем выдачи команды stty sane. В OS X этот флаг идентичен -cbreak, но в Linux и некоторых других операционных системах, sane флаг является надмножеством -cbreak флаг.

Синхронизация циклов

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

Базовая конструкция для такого цикла состоит из двух частей: калибровочная подпрограмма и цикл задержки. Калибровочная подпрограмма должна выполнить приблизительно те же инструкции как цикл задержки для известного числа итераций.

Природа инструкций в цикле задержки в основном неважна. Они могут быть любыми инструкциями, которые Ваша программа должна выполнить при ожидании желаемого количества времени для протекания. Однако общий метод должен выполнить неблокирование I/O во время цикла задержки и затем обработать любые полученные символы.

Например, Перечисление 11-10 показывает очень простой цикл синхронизации, читающий байт и инициировавший некоторые простые операторы эха (в зависимости от того, какая клавиша нажата), одновременно повторяя оператор на экран о столь же в секунду.

Перечисление 11-10  простое второй цикл синхронизации

#!/bin/sh
 
ONE_SECOND=1000
 
read_test()
{
    COUNT=0
    local ONE_SECOND=1000                       # ensure this never trips!
    while [ $COUNT -lt 200 ] ; do
        CHAR=`./getch`
        if [ $1 = "rot" ] ; then
                CHAR=","
        fi
        case "$CHAR" in
                ( "q" | "Q" )
                        CONT=0;
                        GAMEOVER=1
                ;;
                ( "" )
                        # Silently ignore empty input.
                ;;
                ( * )
                        echo "Unknown key $CHAR"
                ;;
        esac
        COUNT=`expr $COUNT '+' 1`
        while [ $COUNT -ge $ONE_SECOND ] ; do
                COUNT=`expr $COUNT - $ONE_SECOND`
                MODE="clear";
                draw_cur $ROT;
                VPOS=`expr $VPOS '+' 1`
                MODE="apple";
                draw_cur $ROT
        done
    done
}
 
calibrate_timers()
{
    2>/tmp/readtesttime time $0 -readtest
    local READ_DUR=`grep real /tmp/readtesttime | sed 's/real.*//' | tr -d ' '`
    # echo "READ_DUR: $READ_DUR"
 
    local READ_SINGLE=`echo "scale=20; ($READ_DUR / 200)" | bc`
    ONE_SECOND=`echo "scale=0; 1.0  / $READ_SINGLE" | bc`
 
    # echo "READ_SINGLE: $READ_SINGLE";
    # exit
 
    echo "One second is about $ONE_SECOND cycles."
}
 
if [ "x$1" = "x-readtest" ] ; then
        read_test
        exit
fi
 
echo "Calibrating.  Please wait."
calibrate_timers
 
echo "Done calibrating.  You should see a message about once per second.  Press 'q' to quit."
stty -icanon -isig
 
GAMEOVER=0
COUNT=0
# Start the game loop.
while [ $GAMEOVER -eq 0 ] ; do
        # echo -n "Enter a character: "
        CHAR=`./getch`
        case "$CHAR" in
                ( "q" | "Q" )
                        CONT=0;
                        GAMEOVER=1
                ;;
                ( "" )
                        # Silently ignore empty input.
                ;;
                ( * )
                        echo "Unknown key $CHAR"
                ;;
        esac
        COUNT=`expr $COUNT '+' 1`
        while [ $COUNT -ge $ONE_SECOND ] ; do
                COUNT=`expr $COUNT - $ONE_SECOND`
                echo "One second elapsed (give or take)."
        done
done
 
stty sane

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

Если Вы делите время для медленной работы продолжительностью единственной операции чтения (READ_SINGLE), можно различить приблизительный штраф за перемещение с помощью итераций основного программного цикла как стоимость единицы. Затем при выполнении одной из тех операций позже Вы просто добавляете, что штраф оценивает счетчику основного цикла, таким образом гарантируя, что «Вторые прошедшие” сообщения быстро догонят (приблизительно), где они должны быть.

Можно приблизить это далее при помощи большего числа в счетчике цикла для достижения большей точности. Например, Вы могли бы постепенно увеличить свой счетчик цикла 100 вместо 1. Это даст намного более точное приближение числа циклов, украденных медленной работой.

Фоновые задания и управление заданиями

Для удобства конечного пользователя в эпоху текстовых терминалов перед появлением инструментов как screen, оболочка C содержит функции управления заданиями, позволяющие Вам запускать процесс в фоновом режиме, затем уходить и работать над другими вещами, принося эти фоновые задачи в передний план, приостанавливая приоритетные задачи завершить их позже, и продолжая эти приостановленные задачи как фоновые задачи.

За эти годы, много современных вариантов Оболочки Bourne включая bash и zsh добавила подобная поддержка. Подробные данные использования этих команд из командной строки выходят за рамки этого документа, но короче говоря, управление-Z приостанавливает приоритетный процесс, fg приносит приостановленное или фоновое задание к переднему плану, и bg заставляет задание начинать выполняться в фоновом режиме.

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

Для запуска процесса, работающего в фоновом режиме, добавьте амперсанд в конце оператора. Например:

sleep 10 &

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

[1]+  Done                    sleep 10

Это указывает, что команда сна завершила выполнение. Связанная функция wait встроенный. Эта команда заставляет оболочку ожидать указанного фонового задания для завершения. Если никакое задание не будет указано, то это будет ожидать, пока все фоновые задания не закончились.

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

#!/bin/bash
 
delayprint()
{
    local TIME;
    TIME=$1
    echo "Sleeping for $TIME seconds."
    sleep $TIME
    echo "Done sleeping for $TIME seconds."
}
 
delayprint 3 &
delayprint 5 &
delayprint 7 &
wait

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

Следующий пример немного более сложен. Это показывает два различных метода для ожидания заданий. Необходимо обычно использовать процесс ID при ожидании дочернего процесса. Можно получить процесс ID последней команды с помощью $! переменная оболочки.

Если, однако, необходимо проверить задание с помощью jobs встроенный, необходимо использовать задание ID. Это может быть несколько неуклюже для получения задания ID, потому что механизм управления заданиями в большинстве вариантов Оболочки Bourne был разработан прежде всего для интерактивного использования, а не программируемого использования. К счастью, существует немного вещей, которые не может фиксировать правильно написанное регулярное выражение.

#!/bin/bash
 
jobidfromstring()
{
        local STRING;
        local RET;
 
        STRING=$1;
        RET="$(echo $STRING | sed 's/^[^0-9]*//' | sed 's/[^0-9].*$//')"
 
        echo $RET;
}
 
delayprint()
{
        local TIME;
        TIME=$1
        echo "Sleeping for $TIME seconds."
        sleep $TIME
        echo "Done sleeping for $TIME seconds."
}
 
# Use the job ID for this one.
delayprint 3 &
DP3=`jobidfromstring $(jobs %%)`
 
# Use the process ID this time.
delayprint 5 &
DP5=$!
 
delayprint 7 &
DP7=`jobidfromstring $(jobs %%)`
 
echo "Waiting for job $DP3";
wait %$DP3
 
echo "Waiting for process ID $DP5";
# No percent because it is a process ID
wait $DP5
 
echo "Waiting for job $DP7";
wait %$DP7
 
echo "Done."

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

В случае, однако, используется второй знак процента. %% задание является одним из многого специального задания «числа», которые обеспечивает оболочка. Это говорит jobs встроенный к выходной информации о последней команде, выполнявшейся в фоновом режиме. Результат этого jobs команда является строкой состояния как один показанный ранее. Эта строка передается как серия параметров jobidfromstring подпрограмма, тогда распечатывающая задание ID отдельно. Вывод этой подпрограммы, в свою очередь, сохранен в любого переменная DP3 или DP7.

Этот пример также демонстрирует, как ожидать задания на основе процесса ID с помощью специальной переменной оболочки, $!, который содержит процесс ID последней выполняемой команды. Это значение сохранено в переменную DP5. Процесс IDs обычно предпочитается по заданию IDs при использовании jobs команда в сценариях (в противоположность вводимому в руку использованию jobs команда).

Наконец, сценарий заканчивается серией вызовов к wait встроенный. Эти команды говорят оболочке ожидать дочернего процесса для выхода. То, когда дочерний процесс выходит, оболочка пожинает процесс, хранит его статус выхода в $? переменная и возвраты управляют к сценарию..

Как jobs команда, wait встроенный может устроиться на работу ID или обработать ID. Если Вы указываете задание или обрабатываете ID, оболочка не возвращает управление сценарию до указанного задания или обрабатывает выходы. Если никакой процесс или задание ID не указаны, wait встроенные возвраты, как только выходит первый дочерний элемент.

Задание ID состоит из знака процента, сопровождаемого числом задания (полученный от любого переменная DP3 или DP7). Процесс ID является просто самим числом.

Заключительный пример показывает, как выполнить ограниченное количество параллельных заданий, в которых порядок завершения задания не важен.

#!/bin/bash
 
MAXJOBS=3
 
spawnjob()
{
    echo $1 | bash
}
 
clearToSpawn()
{
    local JOBCOUNT="$(jobs -r | grep -c .)"
    if [ $JOBCOUNT -lt $MAXJOBS ] ; then
        echo 1;
        return 1;
    fi
 
    echo 0;
    return 0;
}
 
JOBLIST=""
 
COMMANDLIST='ls
echo "sleep 3"; sleep 3; echo "sleep 3 done"
echo "sleep 10"; sleep 10 ; echo "sleep 10 done"
echo "sleep 1"; sleep 1; echo "sleep 1 done"
echo "sleep 5"; sleep 5; echo "sleep 5 done"
echo "sleep 7"; sleep 7; echo "sleep 7 done"
echo "sleep 2"; sleep 2; echo "sleep 2 done"
'
 
IFS="
"
 
for COMMAND in $COMMANDLIST ; do
    while [ `clearToSpawn` -ne 1 ] ; do
        sleep 1
    done
    spawnjob $COMMAND &
    LASTJOB=$!
    JOBLIST="$JOBLIST $LASTJOB"
done
 
IFS=" "
 
for JOB in $JOBLIST ; do
    wait $JOB
    echo "Job $JOB exited with status $?"
done
 
echo "Done."

Большая часть кода здесь является прямой. Стоит отметить, однако, это в подпрограмме clearToSpawn, -r флаг должен быть передан jobs встроенный для ограничения вывода в настоящее время рабочими заданиями. Без этого флага, jobs встроенный иначе возвратил бы список, включавший завершенные задания, таким образом проводя подсчет рабочих неправильных заданий.

-c флаг к grep причины это для возврата числа согласующих отрезков длинной линии, а не самих строк, и период заставляет его соответствовать на любых непустых строках (те, которые содержат по крайней мере один символ). Таким образом, JOBCOUNT переменная содержит число в настоящее время рабочих заданий, которое является, в свою очередь, по сравнению со значением MAXJOBS определить, является ли надлежащим запустить другое задание или нет.

Сценарии приложения С osascript

OS X обеспечивает среду сценариев мощного приложения под названием AppleScript. С AppleScript можно запустить приложение, сказать запущенному приложению выполнять различные задачи, запрашивать запущенное приложение в различных способах, и т.д. Программисты сценария оболочки могут использовать это питание через osascript инструмент.

osascript инструмент выполняет программу в указанном языке и распечатывает результаты через стандартный вывод. Если никакой программный файл не указан, это читает программу из стандартного ввода.

Первый пример является довольно прямым. Это открывает файл poem.txt в каталоге выше каталога, где расположен сценарий:

Перечисление 11-11  , Открывающее использование файла AppleScript и osascript: 07_osascript_simple.sh

#!/bin/sh
 
POEM="$PWD/../poem.txt"
 
cat << EOF | osascript -l AppleScript
launch application "TextEdit"
tell application "TextEdit"
        open "$POEM"
end tell
EOF

Необходимо заметить что путь к файлу poem.txt указан как абсолютный путь здесь. Это крайне важно при работе с osascript. Поскольку текущий рабочий каталог запущенного приложения всегда является корнем файловой системы ( / каталог), а не рабочий каталог сценария оболочки, сценарий должен передать абсолютный путь AppleScript, а не пути относительно рабочего каталога сценария.

Следующий пример показывает, как запросить приложение. В этом случае это запускает TextEdit, открывает два файла, просит у TextEdit список открытых документов и использования, перечисляющего, чтобы помочь ему попросить, чтобы TextEdit возвратил первый абзац текста в документе, соответствующем poem.txt файл.

Перечисление 11-12  , Работающее с использованием файла AppleScript и osascript: 08_osascript_para.sh

#!/bin/sh
 
# Get an absolute path for the poem.txt file.
POEM="$PWD/../poem.txt"
 
# Get an absolute path for the script file.
SCRIPT="$(which $0)"
if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; then
    SCRIPT="$PWD/$SCRIPT"
fi
 
# Launch TextEdit and open both the poem and script files.
cat << EOF | osascript -l AppleScript > /dev/null
launch application "TextEdit"
tell application "TextEdit"
    open "$POEM"
end tell
 
set myDocument to result
return number of myDocument
EOF
 
cat << EOF | osascript -l AppleScript > /dev/null
launch application "TextEdit"
tell application "TextEdit"
        open "$SCRIPT"
end tell
 
set myDocument to result
return number of myDocument
EOF
 
 
# Tell the shell not to mangle newline characters, tabs, or whitespace.
IFS=""
 
# Ask TextEdit for a list of open documents.  From this, we can
# obtain a document number that corresponds with the poem.txt file.
# This query returns a newline-deliminted list of open files. Each
# line contains the file number, followed by a tab, followed by the
# filename
DOCUMENTS="$(cat << EOF | osascript -l AppleScript
 
    tell application "TextEdit"
        documents
    end tell
 
    set myList to result         -- Store the result of "documents" message into variable "myList"
    set myCount to count myList  -- Store the number of items in myList into myCount
    set myRet to ""              -- Create an empty string variable called "myRet"
 
    (* Loop through the myList array and build up a string in the myRet variable
       containing one line per entry in the form:
 
        number tab_character name
      *)
    repeat with myPos from 1 to myCount
        set myRet to myRet & myPos & "\t" & name of item myPos of myList & "\n"
    end repeat
    return myRet
EOF
)"
 
# Determine the document number that corresponds with the poem.txt
# file.
DOCNUMBER="$(echo $DOCUMENTS | grep '[[:space:]]poem\.txt' | grep -v ' poem\.txt' | head -n 1 | sed 's/\([0-9][0-9]*.\).*/\1/')"
SECOND_DOCNUMBER="$(echo $DOCUMENTS | grep '[[:space:]]poem\.txt' | grep -v ' poem\.txt' | tail -n 1 | sed 's/\([0-9][0-9]*.\).*/\1/')"
 
if [ $DOCNUMBER -ne $SECOND_DOCNUMBER ] ; then
    echo "WARNING: You have more than one file named poem.txt open.  Using the" 1>&2
    echo "most recently opened file." 1>&2
    echo "DOCNUMBER $DOCNUMBER != $SECOND_DOCNUMBER"
fi
 
echo "DOCNUMBER: $DOCNUMBER"
 
if [ "x$DOCNUMBER" != "x" ] ; then
    # Query poem.txt by number
    FIRSTPARAGRAPH="$(cat << EOF | osascript -l AppleScript
        tell application "TextEdit"
            paragraph 1 of document $DOCNUMBER
        end tell
EOF
    )"
    echo "The first paragraph of poem.txt is:"
    echo "$FIRSTPARAGRAPH"
fi
 
# Query poem.txt by name
FIRSTPARAGRAPH="$(cat << EOF | osascript -l AppleScript
        tell application "TextEdit"
            paragraph 1 of document "poem.txt"
        end tell
EOF
)"
echo "The first paragraph of poem.txt is:"
echo "$FIRSTPARAGRAPH"

Этот сценарий иллюстрирует три очень важных понятия.

Заключительный пример показывает, как управлять изображениями с помощью сценариев оболочки и AppleScript. Это масштабирует изображение, чтобы быть максимально близко к 320x480 или 480x320 (в зависимости от ориентации изображения).

Перечисление 11-13  , Изменяющее размеры изображения с помощью Событий Изображения и osascript: 09_osascript_images.sh

#!/bin/sh
 
# Get an absolute path for the poem.txt file.
 
MAXLONG=480
MAXSHORT=320
 
URL="http://images.apple.com/macpro/images/design_smartdesign_hero20080108.png"
FILE="$PWD/my design_smartdesign_hero20080108.png"
OUTFILE="$PWD/my design_smartdesign_hero20080108-mini.png"
 
if [ ! -f "$FILE" ] ; then
    curl "$URL" > "$FILE"
fi
 
# Tell the shell not to mangle newline characters, tabs, or whitespace.
IFS=""
 
# Obtain image information
DIM="$(cat << EOF | osascript -l AppleScript
tell application "Image Events"
    launch
    set this_image to open "$FILE"
    copy dimensions of this_image to {W, H}
    close this_image
end tell
return W & H
EOF
)"
 
W="$(echo "$DIM" | sed 's/ *, *.*//' )"
H="$(echo "$DIM" | sed 's/.* *, *//' )"
 
echo WIDTH: $W HEIGHT: $H
 
if [ $W -gt $H ] ; then
    LONG=$W
    SHORT=$H
else
    LONG=$H
    SHORT=$W
fi
 
# echo "LONG: $LONG SHORT: $SHORT"
# echo "MAXLONG: $MAXLONG MAXSHORT: $MAXSHORT"
 
NEWLONG=$LONG
NEWSHORT=$SHORT
# NEWSCALE=1
 
if [ $NEWLONG -gt $MAXLONG ] ; then
    # Long direction is too big.
    NEWLONG="$(echo "scale=20; $LONG * ($MAXLONG/$LONG)" | bc | sed 's/\..*//')";
    NEWSHORT="$(echo "scale=20; $SHORT * ($MAXLONG/$LONG)" | bc | sed 's/\..*//')";
    NEWSCALE="$(echo "scale=20; ($MAXLONG/$LONG)" | bc)";
fi
 
# echo "PART 1: NEWLONG: $NEWLONG NEWSHORT: $NEWSHORT"
 
if [ $NEWSHORT -gt $MAXSHORT ] ; then
    # Short direction is till too big.
    NEWLONG="$(echo "scale=20; $LONG * ($MAXSHORT/$SHORT)" | bc | sed 's/\..*//')";
    NEWSHORT="$(echo "scale=20; $SHORT * ($MAXSHORT/$SHORT)" | bc | sed 's/\..*//')";
    NEWSCALE="$(echo "scale=20; ($MAXSHORT/$SHORT)" | bc)";
fi
 
# echo "PART 2: NEWLONG: $NEWLONG NEWSHORT: $NEWSHORT"
 
if [ $W -gt $H ] ; then
    NEWWIDTH=$NEWLONG
    NEWHEIGHT=$NEWSHORT
else
    NEWHEIGHT=$NEWLONG
    NEWWIDTH=$NEWSHORT
fi
 
echo "DESIRED WIDTH: $NEWWIDTH NEW HEIGHT: $NEWHEIGHT (SCALE IS $NEWSCALE)"
 
cp "$FILE" "$OUTFILE"
 
DIM="$(cat << EOF | osascript -l AppleScript
tell application "Image Events"
    launch
    set this_image to open "$OUTFILE"
    scale this_image by factor $NEWSCALE
    save this_image with icon
    copy dimensions of this_image to {W, H}
    close this_image
end tell
return W & H
EOF
)"
 
GOTW="$(echo "$DIM" | sed 's/ *, *.*//' )"
GOTH="$(echo "$DIM" | sed 's/.* *, *//' )"
 
echo "NEW WIDTH: $GOTW NEW HEIGHT: $GOTH"
 

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

Для получения дополнительной информации об управлении изображениями с Событиями Изображения, посмотрите http://www .apple.com/applescript/imageevents/. Можно также найти много других примеров AppleScript в http://www .apple.com/applescript/examples.html.

Сценарии интерактивных инструментов Используя дескрипторы файлов

Большую часть времени необходимо использовать expect сценарии или программы C для управления интерактивными инструментами. Однако иногда возможно, хотя иногда трудный, написать сценарий таких интерактивных инструментов (если их вывод основан на строке). Этот раздел объясняет методы, которые Вы используете.

Создание именованных каналов

Прежде чем можно будет связаться с инструментом непрерывным способом туда и обратно, необходимо создать пару FIFOs (короткий для метода «первым пришел - первым вышел», иначе известного как именованные каналы) использование mkfifo команда. Например, для создания вызванных именованных каналов /tmp/infifo и /tmp/outfifo, Вы дали бы следующие команды:

mkfifo /tmp/infifo
mkfifo /tmp/outfifo

Видеть это в действии с помощью sed команда как фильтр, введите следующие команды:

mkfifo /tmp/outfifo
sed 's/a/b/' < /tmp/outfifo &
echo "This is a test" > /tmp/outfifo

Заметьте это sed выходы после получения данных и печати This is b test на экран. echo команда открывает вывод FIFO, пишут данные и закрывают FIFO. Как только это закрывает FIFO, sed команда получает a SIGPIPE сигнализируйте и (обычно) завершается. Чтобы использовать инструмент командной строки в качестве фильтра и сохранить передающие данные к нему, необходимо удостовериться, что Вы не закрываете FIFO, пока Вы не закончены с помощью фильтра. Для достижения этого необходимо использовать дескрипторы файлов, как описано в следующем разделе.

Открытие дескрипторов файлов для чтения и записи

Как объяснено в Создании Именованных каналов, отправляя данные в именованный канал с инструментами командной строки заставляет команду завершаться после первого сообщения. Для предотвращения этого необходимо открыть дескриптор файла в оболочке для обеспечения непрерывного доступа к именованному каналу.

Можно открыть дескриптор файла для записи в вывод FIFO следующим образом:

exec 8> /tmp/outfifo

Эта команда открывает дескриптор файла 8 и перенаправляет его к файлу/tmp/outfifo.

Точно так же можно открыть дескриптор для чтения как это:

exec 9<> /tmp/infifo

Можно записать данные в открытый дескриптор как это:

# Write a string to descriptor 8
echo "This is a test." >&8

Можно считать строку из открытого дескриптора как это:

# Read a line from descriptor 9 and store the result in variable MYLINE
read MYLINE <&9

Когда Вы закончили писать данные в фильтр, необходимо закрыть каналы и удалить файлы FIFO следующим образом:

exec 8>&-
exec 9<&-
rm /tmp/infifo
rm /tmp/outfifo

Таблица 11-5 суммирует операции, которые можно выполнить на дескрипторах файлов. Следующий раздел содержит полный рабочий пример.

Таблица 11-5  операторы дескриптора файла Shell

Оператор

Эквивалентный код C

n<> "filename"

fd = open("имя файла", O_RDWR|O_CREAT);

dup2(fd, n);

close(fd);

n> "filename"

fd = open("имя файла", O_WRONLY|O_CREAT|O_TRUNC);

dup2(fd, n);

close(fd);

n>> "filename"

fd = open("имя файла", O_WRONLY|O_APPEND|O_CREAT);

dup2(fd, n);

close(fd);

n<&o

n>&o

dup2(o, n);

Примечание: Несмотря на то, что эти операторы ведут себя тождественно для удобочитаемости, необходимо использовать <& оператор для только для чтения или дескрипторов чтения-записи и >& для дескрипторов только для записи.

n<&-

n<&-

close(n);

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

Существует только еще одна проблема. sed буферы команд его ввод по умолчанию. Это может вызвать проблемы при использовании его как фильтр. Таким образом необходимо сказать sed команда для не буферизации ее ввода путем указания -l флаг (или -u флаг для GNU sed).

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

Перечисление 11-14  Используя FIFOs для создания круговых каналов

#!/bin/sh
 
# Create two FIFOs (named pipes)
INFIFO="/tmp/infifo.$$"
OUTFIFO="/tmp/outfifo.$$"
mkfifo "$INFIFO"
mkfifo "$OUTFIFO"
 
# OS X and recent *BSD sed uses -l for line-buffered mode.
BUFFER_FLAG="-l"
 
# GNU sed uses -u for "unbuffered" mode (really line-buffered).
if [ "x$(sed --version 2>&1 | grep GNU)" != "x" ] ; then
    BUFFER_FLAG="-u"
fi
 
# Set up a sed substitution input from the input fifo otput to
sed $BUFFER_FLAG 's/a test/not a test/' < $INFIFO > $OUTFIFO &
PID=$!
 
# Open a file descriptor (#8) to write to the input FIFO
exec 8> $INFIFO
 
# Open a file descriptor (#9) to read from the output FIFO.
exec 9<> $OUTFIFO
 
# Send two lines of text to the running copy of sed.
echo "This is a test." >&8
echo "This is maybe a test." >&8
 
# Read the first two lines from sed's output.
read A <&9
echo "Result 1: $A"
read A <&9
echo "Result 2: $A"
 
# Send another line of text to the running copy of sed.
echo "This is also a test." >&8
 
# Read it back.
read A <&9
echo "Result 3: $A"
 
# Show that sed is still running.
ps -p $PID
 
# Close the pipes to terminate sed.
exec 8>&-
exec 9<&-
 
# Show that sed is no longer running.
ps -p $PID
 
# Clean up the FIFO files in /tmp
rm "$INFIFO"
rm "$OUTFIFO"
 

Сети со сценариями оболочки

Путем построения на понятиях в Использовании Именованных каналов и Дескрипторов файлов для Создания Круговых Каналов можно легко записать сценарии, связывающиеся по Интернету с помощью TCP/IP с помощью netcat утилиты, nc. Эта утилита обычно доступна в различных формах на различных платформах, и доступные флаги варьируются несколько от платформы до платформы.

Следующее перечисление показывает, как записать очень простому демону на основе netcat, работающего переносимо. Это слушает на порту 4242. Когда клиент соединяется, это читает строку текста, затем отправляет клиенту ту же строку, только назад. Это повторяет этот процесс, пока клиент не закрывает соединение.

Перечисление 11-15  простой демон на основе netcat

#!/bin/sh
 
INFIFO="/tmp/infifo.$$"
OUTFIFO="/tmp/outfifo.$$"
 
# /*! Cleans up the FIFOs and kills the netcat helper. */
cleanup_daemon()
{
    rm -f "$INFIFO" "$OUTFIFO"
 
    if [ "$NCPID" != "" ] ; then
        kill -TERM "$NCPID"
    fi
 
    exit
}
 
# /*! @abstract Attempts to reconnect after a sigpipe. */
reconnect()
{
        PSOUT="$(ps -p $NCPID | tail -n +2 | tr -d '\n')"
        if [ "$PSOUT" = "" ] ; then
                cleanup_shttpd
        fi
        closeConnection 8 "$INFIFO"
}
 
trap cleanup_daemon SIGHUP
trap cleanup_daemon SIGTERM
trap reconnect SIGPIPE
trap cleanup_daemon SIGABRT
trap cleanup_daemon SIGTSTP
# trap cleanup_daemon SIGCHLD
trap cleanup_daemon SIGSEGV
trap cleanup_daemon SIGBUS
trap cleanup_daemon SIGQUIT
trap cleanup_daemon SIGINT
 
mkfifo "$INFIFO"
mkfifo "$OUTFIFO"
 
# /*! Reverses a string. */
reverseit()
{
    STRING="$1"
 
    REPLY=""
 
    while [ "$STRING" != "" ] ; do
        FIRST="$(echo "$STRING" | cut -c '1')"
        STRING="$(echo "$STRING" | cut -c '2-')"
        REPLY="$FIRST$REPLY"
    done
 
    echo "$REPLY"
}
 
while true ; do
    CONNECTED=1
    nc -l 4242 < $INFIFO > $OUTFIFO &
    NCPID=$!
 
    exec 8> $INFIFO
    exec 9<> $OUTFIFO
 
    while [ $CONNECTED = 1 ]  ; do
            read -u9 -t1 REQUEST
 
        if [ $? = 0 ] ; then
            # Read didn't time out.
            reverseit "$REQUEST" >&8
            echo "GOT REQUEST $REQUEST"
        fi
 
        CONNECTED="$(jobs -r | grep -c .)"
    done
done
 

Этот демон разработан, чтобы быть переносимым, который ограничивает флаги, которые это может использовать. В результате это может только обработать единственный клиент в любой момент времени с минимумом одного второго периода между попытками подключения. Это - самый простой способ использовать netcat утилиту. Для более сложного примера посмотрите Основанный на Shell веб-сервер.

Можно также использовать netcat в качестве сетевого клиента почти таким же способом. Вы могли бы отправить запрос к веб-серверу, почтовому серверу или другому демону. Конечно, Вы - обычно более обеспеченные использующие существующие клиенты такой как curl или sendmail, но когда это не возможно, netcat предоставляет решение.

Следующие подключения перечисления к демону, показанному в Перечислении 11-15, вводе запросов от пользователя, отправляют ввод удаленному демону, читают результат и распечатывают его к стандартному выводу.

Перечисление 11-16  простой клиент на основе netcat

#!/bin/sh
 
INFIFO="/tmp/infifo.$$"
OUTFIFO="/tmp/outfifo.$$"
 
INFIFO="/tmp/infifo.$$"
OUTFIFO="/tmp/outfifo.$$"
 
# /*! Cleans up the FIFOs and kills the netcat helper. */
cleanup_client()
{
    rm -f "$INFIFO" "$OUTFIFO"
 
    if [ "$NCPID" != "" ] ; then
        kill -TERM "$NCPID"
    fi
 
    exit
}
 
# /*! @abstract Attempts to reconnect after a sigpipe. */
reconnect()
{
        PSOUT="$(ps -p $NCPID | tail -n +2 | tr -d '\n')"
        if [ "$PSOUT" = "" ] ; then
                cleanup_shttpd
        fi
        closeConnection 8 "$INFIFO"
}
 
trap cleanup_client SIGHUP
trap cleanup_client SIGTERM
trap reconnect SIGPIPE
trap cleanup_client SIGABRT
trap cleanup_client SIGTSTP
trap cleanup_client SIGCHLD
trap cleanup_client SIGSEGV
trap cleanup_client SIGBUS
trap cleanup_client SIGQUIT
trap cleanup_client SIGINT
 
mkfifo "$INFIFO"
mkfifo "$OUTFIFO"
 
nc localhost 4242 < $INFIFO > $OUTFIFO &
NCPID=$!
 
exec 8> $INFIFO
exec 9<> $OUTFIFO
 
while true ; do
    printf "String to reverse -> "
        read STRING
    echo "$STRING" >&8
    read -u9 REVERSED
    echo "$REVERSED"
done