Усовершенствованные методы
Сценарии оболочки могут быть мощными инструментами для записи программного обеспечения. Графические интерфейсы несмотря на это, они способны к выполнению почти любой задачи, которая могла быть выполнена с более традиционным языком. В этой главе описываются несколько методов, которые помогут Вам записать более сложное программное обеспечение с помощью сценариев оболочки.
Используя оценку, Встроенную для Структур данных, Массивов и Косвенности, описывает, как создать сложные структуры данных в сценариях оболочки.
Текстовое Форматирование Shell говорит, как сделать табличные разметки и использовать escape-последовательности ANSI для добавления цвета и стилей к терминальному выводу.
Захват Сигналов говорит как обработчикам сигнала записи в сценариях оболочки.
Неблокирование I/O и Синхронизация Циклов показывают один способ записать сложные интерактивные сценарии, такие как игры.
Фоновые задания и Управление заданиями объясняют, как сделать сложные задачи в фоновом режиме, в то время как Ваш сценарий продолжает выполняться, включая то, как выполнить некоторое основное параллельное вычисление. Это также объясняет, как получить коды результата из этих заданий после того, как они выходят.
Сценарии приложения С osascript описывают, как Ваш сценарий может взаимодействовать с использованием приложений OS X AppleScript.
Сценарии Интерактивных Инструментов Используя Дескрипторы файлов описывают, как можно сделать двунаправленные соединения к инструментам командной строки.
Сети Со Сценариями оболочки описывают, как использовать
nc
инструмент (иначе известный как netcat) для записи сценариев оболочки, использующих в своих интересах сокеты TCP/IP.
Используя оценку, Встроенную для Структур данных, Массивов и Косвенности
Одна из более недооцениваемых команд в сценариях оболочки 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 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-последовательности Непосредственно.
Возможность Terminfo | Escape-последовательность | Описание |
---|---|---|
Примечание: |
| Скрывает курсор. |
Примечание: |
| Показывает курсор. |
|
| Позиция курсора наборов к строке r, столбцу c. |
(никакой эквивалент) |
| Текущая позиция курсора отчетов, как будто введенный с клавиатуры (сообщил как |
|
| Сохраняет текущую позицию курсора и стиль. |
|
| Восстановления ранее сохранили позицию курсора и стиль. |
|
| Повышает курсор r строки. |
|
| Перемещает курсор вниз r строки. |
|
| Право курсора перемещений c столбцы. |
|
| Курсор перемещений оставил c столбцы. |
(никакой эквивалент) |
| Когда курсор достигает правого края экрана, отключает автоматическое обертывание строки. |
(никакой эквивалент) |
| Включает обертывание строки (на по умолчанию). |
(никакой эквивалент) |
| Включает целый экран, прокручивающий (на по умолчанию). |
(никакой эквивалент) |
| Включает прокрутку частичного экрана из строки S к строке E и перемещает курсор в вершину этой области. |
|
| Перемещает курсор вниз одной строкой. |
|
| Повышает курсор одной строкой. |
Припишите и окрасьте 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-последовательностей тот стиль текста управления.
Возможность Terminfo | Escape-последовательность | Описание |
---|---|---|
Сброс атрибутов | ||
|
| Сброс все атрибуты к их значениям по умолчанию. |
Установка атрибутов | ||
|
| Включает «полужирный» дисплей. Этот код и код № 2 ( |
|
| Включает «тусклый» дисплей. Этот код и код № 1 ( |
Примечание: В |
| Включает «выдающийся» дисплей. Не поддерживаемый в Терминале. |
|
| Включает подчеркнутый дисплей. |
Примечание: |
| <мигание>. |
(Никакой эквивалент.) |
| Быстрое мигание или перечеркивание. (Не поддерживаемый в Терминале; поведение, противоречивое в другом месте.) |
|
| Включает инвертированный (обратный) дисплей. |
Примечание: |
| Включает скрытый (фон на фоне) дисплей. |
| Неиспользованный. | |
Коды | Коды выбора шрифта. Неподдерживаемый в большинстве терминальных приложений, включая Терминал. | |
Очистка атрибутов | ||
(Никакой эквивалент.) |
| Гарнитура «Fraktur». Неподдерживаемый почти универсально, и Терминал не исключение. |
| Неиспользованный. | |
Примечание: Технически, эта возможность, как предполагается, заканчивает выдающийся режим, но это перегружается для отключения полужирного яркого/тусклого режима также. |
| Отключает «яркий» или «тусклый» дисплей. Это отключает любой код |
|
| Отключает «выдающийся» дисплей. Не поддерживаемый в Терминале. |
|
| Отключает подчеркнутый дисплей. |
(Никакой эквивалент. Использовать |
| </мигание>. Также отключает медленное мигание или перечеркивание ( |
| Неиспользованный. | |
(Никакой эквивалент. Использовать |
| Отключает инвертированный (обратный) дисплей. |
(Никакой эквивалент. Использовать |
| Отключает скрытый (фон на фоне) дисплей. |
| Неиспользованный. |
Таблица 11-3 содержит список возможностей и escape-последовательностей, управляющих цветами текста и цветами фона.
Возможность Terminfo | Escape-последовательность | Описание |
---|---|---|
Основные цвета | ||
|
| Выбирает основной цвет к черному цвету. |
|
| Выбирает основной цвет к красному. |
|
| Выбирает основной цвет к зеленому. |
|
| Выбирает основной цвет к желтому. |
|
| Выбирает основной цвет к синему. |
|
| Выбирает основной цвет к пурпурному. |
|
| Выбирает основной цвет к голубому цвету. |
|
| Выбирает основной цвет белому. |
| Неиспользованный. | |
|
| Выбирает основной цвет к значению по умолчанию. |
Цвета фона | ||
|
| Выбирает цвет фона к черному цвету. |
|
| Выбирает цвет фона к красному. |
|
| Выбирает цвет фона к зеленому. |
|
| Выбирает цвет фона к желтому. |
|
| Выбирает цвет фона к синему. |
|
| Выбирает цвет фона к пурпурному. |
|
| Выбирает цвет фона к голубому цвету. |
|
| Выбирает цвет фона белому. |
| Неиспользованный. | |
|
| Выбирает цвет фона к значению по умолчанию. |
Другие 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-последовательностей, выполняющих другие разные задачи, такие как управление курсором, манипулирование позицией табуляции и очистка экрана или частей этого.
Возможность Terminfo | Escape-последовательность | Описание |
---|---|---|
Примечание: Это сбрасывает еще много вещей, чем |
| Сбрасывает цвета фона и основные цвета к их значениям по умолчанию, очищает экран и перемещает курсор в исходное положение. |
|
| Очищается в нижнюю часть экрана с помощью текущего фонового цвета. |
(никакой эквивалент) |
| Очищается к вершине экрана с помощью текущего фонового цвета. |
|
| Очищает экран к текущему фоновому цвету. На некоторых терминалах курсор сбрасывается к исходному положению. |
Очистка текущей строки | ||
|
| Очищается до конца текущей строки. |
|
| Очищается к началу текущей строки. |
(никакой эквивалент) |
| Очищает текущую строку. |
|
| Горизонтальная вкладка набора в позиции курсора. |
(никакой эквивалент) |
| Установите вертикальную вкладку в текущей строке. (Не поддерживаемый в Терминале.) |
Коды | Избыточные коды, эквивалентные кодам | |
(никакой эквивалент) |
| Очистите горизонтальную вкладку в позиции курсора. |
(никакой эквивалент) | ^ [[1 г | Очистите вертикальную вкладку в текущей строке. (Не поддерживаемый в Терминале.) |
(никакой эквивалент) | ^ [[2 г | Очистите горизонтальные и вертикальные позиции табуляции для текущей строки только. (Не поддерживаемый в Терминале.) |
| ^ [[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
сеанс или другой канал передачи):
-icanon
флаг отключает канонизацию ввода. Например, если Вы нажимаете (в порядке) клавиши, Удаляете, и Возврат, обычно Ваш сценарий оболочки получает пустую строку. С отключенной канонизацией Ваше приложение вместо этого видит три байта: буква A, управляющий символ, представляющий клавишу Delete и символ новой строки, представляющий клавишу Return.-isig
флаг отключает автоматическую генерацию сигналов на основе вводимого символа. Путем указания этого флага можно захватить произвольные управляющие символы, включая символы, которые иначе остановили бы, приостановили бы или возобновили бы выполнение (Ctrl-C, например). Поскольку отключение этих сигналов делает его тяжелее для остановки выполнения сценария оболочки, необходимо обычно избегать использования этого флага, если Вы не намереваетесь получить эти управляющие символы как часть нормального функционирования. Если просто необходимо выполнить код очистки, когда эти клавиши нажаты, необходимо захватить получающиеся сигналы вместо этого, как описано в Захвате Сигналов.-cbreak
отметьте устанавливает некоторые разумные значения по умолчанию для интерактивного использования оболочки.
В зависимости от какого Вы делаете, можно также счесть полезным передать -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
заставляет задание начинать выполняться в фоновом режиме.
Вплоть до этой точки все сценарии вовлекли единственный процесс, работающий в передний план. Действительно, большинство сценариев оболочки работает этим способом. Если сценарий оболочки порождает голодную процессора задачу, иногда, тем не менее, параллелизм может улучшить производительность, особенно. Поэтому этот раздел описывает программируемые способы использовать в своих интересах фоновые задания в сценариях оболочки.
Для запуска процесса, работающего в фоновом режиме, добавьте амперсанд в конце оператора. Например:
Это запустит спящий процесс, работающий в фоновом режиме, и сразу возвратит Вас командной строке. Десять секунд спустя команда закончит выполняться, и в следующий раз, когда Вы поражаете возврат после этого, Вы будете видеть его статус выхода. В зависимости от Вашей оболочки это будет выглядеть примерно так:
[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 — в частности, что Вы не можете всегда однозначно определять определенный документ с именем, если два открытых файла имеют то же имя. При записи сценариев необходимо тщательно избежать открывать два файла с тем же именем с помощью того же приложения.
Это демонстрирует, как сослаться на документ его именем. Результаты
documents
сообщение является переходным; изменение номеров документов как новые окна открыто, и закрываются старые окна. Таким образом необходимо обычно адресовать документы с помощью их имен вместо того, чтобы использовать номера документов, если Вы не очень осторожны.
Заключительный пример показывает, как управлять изображениями с помощью сценариев оболочки и 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 суммирует операции, которые можно выполнить на дескрипторах файлов. Следующий раздел содержит полный рабочий пример.
Оператор | Эквивалентный код C |
---|---|
n |
|
n |
|
n |
|
n n |
Примечание: Несмотря на то, что эти операторы ведут себя тождественно для удобочитаемости, необходимо использовать |
n n |
|
Используя именованные каналы и дескрипторы файлов для создания круговых каналов
Существует только еще одна проблема. sed
буферы команд его ввод по умолчанию. Это может вызвать проблемы при использовании его как фильтр. Таким образом необходимо сказать sed
команда для не буферизации ее ввода путем указания -l
флаг (или -u
флаг для GNU sed
).
Следующее перечисление демонстрирует эти методы. Это работает sed
, тогда отправляет две строки в него, затем читает назад две отфильтрованных строки, затем отправляет третью строку, затем читает третью отфильтрованную строку назад, затем закрывает каналы.
Перечисление 11-14 Используя FIFOs для создания круговых каналов
Сети со сценариями оболочки
Путем построения на понятиях в Использовании Именованных каналов и Дескрипторов файлов для Создания Круговых Каналов можно легко записать сценарии, связывающиеся по Интернету с помощью 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 |