Linux Tools: shells, ash #5 - ash syntax, functions

linux linux-tools shell ash

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

На помощь приходят функции

name () {
  command
  ...
  return [exitstatus]
}

Функции могут содержать всего одну команду и записываться без фигурных скобок

$ hello() echo 'Hello'
$ hello
Hello

Или несколько команд

$ hello() {
  echo 'Hello'
  echo 'World'
}
$ hello
Hello
World

Каждая функция работает как обычная команда-программа и возвращает exit-код. По умолчанию возвращается 0, но можно вернуть нужный exit-код c помощью return. При этом выполнение функции прекращается на return.

$ hello() {
echo 'Hello'
return 100
echo 'World'
}

# World не выводится
$ hello
Hello

# $? содержит exit-код предыдущей команды
$ echo $?
100

Параметры и переменные

В 3 статье серии мы уже немного говорили о переменных, сегодня разберемся с ними подробно.

Параметры (Parameters) могут позиционными и специальными, а параметры с именем называются переменными (Variables).

Позиционные параметры - Positional Parameters

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

В параметре $0 хранится путь к текущему скрипту или путь к shell, если запущен интерактивный режим

# параметр $0 самого shell хранит путь к shell-программе
$ echo $0
/usr/bin/ash

$ cat ./test.sh
echo '$0='$0
echo '$1='$1
echo '$2='$2

# передаем пару параметров скрипту test.sh
$ ash ./test.sh first second
$0=./test.sh
$1=first
$2=second

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

$ hello() { echo '$0='$0; echo "Hello, $1"; }

$ hello World
$0=/usr/bin/ash
Hello, World

Позиционные параметры в скриптах и функциях можно переопределять с помощью builtin-команды set (подробно синтаксис set разберем в отдельной статье про builtin-команды)

$ hello() {
echo "Hello, $1 and $2";
set Beavis Butthead;
echo "Hello, $1 and $2";
}

$ hello Rick Morty
Hello, Rick and Morty
Hello, Beavis and Butthead

Переменные - Variables

Shell-переменные можно использовать внутри скриптов для хранения значений

$ a=1
$ b=2
$ TEST=100500
$ echo "$a-$b-$TEST"
1-2-100500

Но эти параметры не передаются при вызовах команд в environment-переменные, можно делать вручную задавать environment-переменные для каждой команды

$ cat ./test.sh
echo '$TEST='$TEST

# переменная TEST будет пуста
$ ./test.sh
$TEST=

# передаем значение переменной TEST на момент вызова
$ TEST=1 ./test.sh
$TEST=1

Для того чтобы shell-переменные передавались в окружение (environment) всем выполняемым командам, нужно экспортировать переменную

$ export TEST=100500

$ ./test.sh
$TEST=100500

Переменные в функциях

В функциях видны все shell-переменные (и environment-переменные тоже) — их можно использовать внутри.

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

Локальные переменные в функциях

Если определенные переменные нужны только внутри функции можно воспользоваться builtin-функцией local.

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

# в shell своя переменная name
$ name=Pachino

$ hello () {
local name # в функции своя переменная name
echo 'local name='$name
name=DeNiro
echo 'local name='$name
}

$ hello
local name=Pachino
local name=DeNiro

# в shell переменная не поменялась
$ echo 'name='$name
name=Pachino

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

$ name=Pachino

$ hello () {
  local name
  name=DeNiro
  echo 'name='$name

  bang() { echo 'bang name='$name }

  bang
}


$ hello
name=DeNiro
# в функции bang переиспользуется локальная переменная name
# из функции hello
bang name=DeNiro

# в shell осталась своя переменная
$ echo $name
Pachino

Вместо имени переменной в local можно указать -

$ hello() {
local -
# ...
}

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

Потоки в функциях

Раз функция ведет себя как команда, значит внутри нее можно работать с потоками и использовать функции в пайплайнах

# передаем в cat данные из stdin-потока
$ hello { echo 'Hello'; cat <&0; }

$ echo 'World' | hello
Hello
World

stdout и stderr- потоки у функции также свои

$ hello() { echo 'Hello' >&1; echo 'World' >&2; }

$ hello
Hello
World

# перенаправляем stderr в /dev/null
$ hello 2>/dev/null
Hello

 

Итого

Сегодня узнали как создавать shell-функции и как использовать параметры, потоки и переменные внутри shell-функций.

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

 

Предыдущая статья: shells, ash #4 - ash syntax, complex commands, pipelines, conditions and loops

Следующая статья: shells, ash #6 - environment variables

 

Ссылки


Все статьи серии “Linux Tools”