Подпрограммы, определение объема и определение источника

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

В Оболочке Bourne существует два основных способа приблизиться к подпрограммам. Первое посредством выполнения вне инструментов (который может включать сценарий, выполняющий себя рекурсивно). Это было описано кратко в Основных Операторах Управления. Однако существуют другие методы для получения информации о коде результата из внешних сценариев. Они описаны в Работе с Кодами Результата. Можно также заставить выполнение из одной команды быть условным выражением на код результата, возвращенный другой командой, как описано в Объединении в цепочку Выполнения.

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

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

Можно счесть полезным включать один весь сценарий оболочки в другом. Этот предмет покрыт Включением Одного Сценария оболочки В Другом (Определение источника).

Наконец, можно счесть полезным выполнить внешние сценарии в фоновом режиме и проверить их состояние в более позднее время. Можно узнать об этом в Фоновых заданиях и Управлении заданиями.

Основы подпрограммы

Подпрограммы в Оболочке Bourne смотрят очень как функции C без списка аргументов. Вы вызываете эти подпрограммы точно так же, как Вы выполняете программу, и подпрограммы могут использоваться где угодно, что можно использовать исполнимую программу.

Вот простой пример, распечатывающий «Аргумент 1: Это - аргумент» использование подпрограммы оболочки:

#!/bin/sh
 
mysub()
{
        echo "Arg 1: $1"
}
 
mysub "This is an arg"

Так же, как параметры сценария оболочки сохранены в переменных оболочки, названных 1$, 2$, и т.д., так также являются параметрами для окружения подпрограмм. Фактически, большинством способов, подпрограммы оболочки ведут себя точно как выполнение внешнего сценария. Одно место, где они ведут себя по-другому, находится в переменном определении объема. Посмотрите, что Переменная Определяет объем для получения дополнительной информации.

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

#!/bin/sh
 
mysub()
{
        return 3
}
 
mysub "This is an arg"
echo "Subroutine returned $?"

Анонимные подпрограммы

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

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

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

tar -cf - file1 file2 file3 ...
tar -xf -

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

Таким образом Вы должны выполнить две команды на правой стороне канала: a cd команда для изменения каталогов прежде, чем извлечь архив и tar сама команда. Можно сделать это с анонимной подпрограммой.

Вот простой пример:

tar -cf - file1 file2 file3 | \
    { cd "/destination" ; tar -xf - ; }

Заметьте точку с запятой перед близкой изогнутой фигурной скобкой. Эта точка с запятой требуется. Также заметьте пространство после вводной изогнутой фигурной скобки. Это пространство также требуется. Упущение любого из этих результатов по синтаксической ошибке.

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

Например:

tar -cf - file1 file2 file3 | \
    { if cd "/destination" ; then tar -xf - ; fi; }

Эта версия выполнится cd команда, затем выполните второе tar команда, только если cd команда была успешна.

Переменное определение объема

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

К счастью, переменные не должны оставаться глобальной переменной.

Объявление локальной переменной

Для объявления переменной локальной переменной к данной подпрограмме используйте local оператор.

#!/bin/sh
 
mysub()
{
        local MYVAR
        MYVAR=3
        echo "SUBROUTINE: MYVAR IS $MYVAR";
}
 
MYVAR=4
echo "MYVAR INITIALLY $MYVAR"
mysub "This is an arg"
echo "MYVAR STILL $MYVAR"

Этот сценарий скажет Вам, что начальное значение равняется 4, значение было изменено на 3 в подпрограмме и остается 4, когда возвращается подпрограмма. Был он не для a local объявление MYVAR в подпрограмме, последующем изменении к MYVAR распространил бы назад к основной части сценария.

Во многом как export оператор, local оператор может использоваться в начале оператора присваивания также. Например, предыдущая подпрограмма, возможно, содержала следующую строку вместо этого:

local MYVAR=3

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

Если это вызовы подпрограммы само рекурсивно, новая копия MYVAR создается для каждого вызова к этой подпрограмме, приводящей к стеку вызовов во многом как локальные переменные в C или других языках.

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

Используя глобальные переменные в подпрограммах

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

  • Изменения в переменных, ранее объявленных как local в текущем стеке вызовов. Это описано далее в Объявлении Локальной переменной.

  • Изменения, внесенные в подпрограммах, вызывают посредством встроенного выполнения.

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

Следующий сценарий демонстрирует эти понятия:

#!/bin/sh
 
# Demonstrates scoping rules.
 
changevalue()
{
    NAME="$1"
 
    eval "$NAME=\"\$(expr \"\$$NAME\" \"+\" \"1\")\""
    eval echo "\$$NAME"
}
 
localchange()
{
    local X=17
 
    printf "Local variable X: $X + 1 is: "
    changevalue X
    echo "which is also $X"
}
 
A=3
printf "$A + 1 is "
changevalue A
echo "which is also $A"
 
B=3
printf "$B + 1 is "
RESULT="$(changevalue B)"
echo $RESULT
echo "which is NOT $B"
 
localchange
echo "X in a global context is \"$X\""

Заметьте это когда changevalue вызывается непосредственно, изменения, которые это вносит в глобальные переменные, распространены назад к основной организации сценария. Когда это вызывают с помощью встроенного выполнения, изменения потеряны.

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

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

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

  • Вызывающая сторона может перенаправить вывод подпрограммы к файлу и впоследствии использовать встроенное выполнение с cat команда для копирования вывода подпрограммы в переменную.

Оба метода функционально эквивалентны.

Включая один сценарий оболочки в другом (определение источника)

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

Для определения источника одного сценария от другого Вы используете . встроенный.

Например, создайте файл, содержащий подпрограмму mysub от Переменного Определения объема. Вызовите его mysub.sh. Для использования этой подпрограммы в другом сценарии можно сделать следующее:

#!/bin/sh
MYVAR=4
 
# The next line sources the external script.
. /path/to/mysub.sh
 
echo "MYVAR INITIALLY $MYVAR"
mysub "This is an arg"
echo "MYVAR STILL $MYVAR"

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

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

# This form is less compatible.
source /path/to/mysub.sh

source встроенный более популярно среди бывших программистов оболочки C, в то время как период (.) версия более популярна среди пуристов Оболочки Bourne. Версию периода считают переносимой.

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

#!/bin/sh
# Save as sourcetest1.sh
MYVAR=3
. sourcetest2.sh
echo "MYVAR IS $MYVAR"
#!/bin/sh
# Save as sourcetest2.sh
MYVAR=4

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

Нахождение абсолютного пути текущего сценария

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

Переменная оболочки $0 содержит имя, переданное в на командной строке. Если сценарий выполнялся с абсолютным путем, это - все, в чем Вы нуждаетесь. Однако, если сценарий находится в каталоге, содержавшемся в PATH переменная окружения, это может содержать не что иное как имя сценария.

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

SCRIPT="$(which $0)"

Ваш сценарий может тогда выполнить себя как это:

"$SCRIPT" arguments go here

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

SCRIPT="$(which $0)"
if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; then
    SCRIPT="$PWD/$SCRIPT"
fi

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