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

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

Подстановка переменных

Важно понимать разницу между именем переменной и значением этой переменной, и не забывать о ней. Например: variable -- это символическое имя переменной
в то время как $variable -- это уже ссылка на значение переменной ( на область памяти, содержащую значение переменной с именем variable ). Имена переменных, без префикса $, могут использоваться в процессе объявления переменной, то есть когда переменной присваивается определенное значение, при удалении значения переменной, при экспорте и в случаях -- когда переменная является названием сигнала.
Присвоить значение переменной можно с помощью оператора =:

  1. var=1
  2. var1="test"

различными вариантами с использованием оператора read:

  1. read var

или в заголовке цикла:

  1. for var in 1 2 3

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

  1. #!/bin/bash
  2.  
  3. var="hello"
  4. echo "$var world"

выведет:

freebsd /# ./test.sh
hello world

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

  1. #!/bin/bash
  2.  
  3. var="hello"
  4. echo '$var world'

выведет:

freebsd /# ./test.sh
$var world
Форма записи $var, является упрощенным вариантом ${var}. Запись с фигурными скобками ${var}, является более строгим вариантом, ее можно применять там, где обычный вариант вызывает синтаксическую ошибку.

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

  1. #!/bin/bash
  2.  
  3. a=375
  4. hello=$a

# Использование пробелов, перед, или после, знака =, не допустимо
# При записи VAR =value, интерпретатор попытается выполнить команду VAR с параметром =value.
# Если попробовать присвоить значение переменной таким образом VAR= value, будет произведена попытка назначить переменной окружения VAR пустое
# значение, после чего, выполнить команду value.

  1. echo hello    # Здесь просто будет выведена строка "hello"
  2.  
  3. echo $hello 
  4. echo ${hello} # Идентично предыдущей строке.
  5.  
  6. echo "$hello"
  7. echo "${hello}"
  8.  
  9. echo

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

  1. #!/bin/sh
  2.  
  3. hello="A B  C   D"
  4. echo $hello   
  5. echo "$hello" 
  6. echo '$hello'

freebsd /# ./test.sh
A B C D
A B  C   D
$hello

Объявление неинициализированной переменной, фактически то-же самое что и сброс переменной

  1. #!/bin/sh
  2.  
  3. # Неинициализированная переменная
  4. var=
  5. echo "var = $var"
  6.  
  7. # Присвоение значения переменной
  8. var=100
  9. echo "var = $var"
  10.  
  11. # Сброс значения переменной
  12. unset var
  13. echo "var = $var"

freebsd /# ./test.sh
var =
var = 100
var =

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

  1. echo "$uninitialized"                                # пустое значение
  2. let "uninitialized += 5"                             # к пустому значению прибавить 5
  3. echo "$uninitialized"                                # результат 5

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

  1. var1=variable1  var2=variable2  var3=variable3
  2. echo "var1=$var1   var2=$var2  var3=$var3"

Со старыми версиями "sh" могут возникнуть проблемы .

Присваивание значений переменным

Операцией присвоения значения переменной в bash является символ равно, "=", в зависимости от контекста использования так-же является оператором сравнения "равно".

Простое присваивание

  1. #!/bin/bash
  2.  
  3. # Символ  '$' не используется 
  4.  
  5. a=879
  6. echo "Значение переменной \"a\" = $a"
  7.  
  8. # Присваивание значения с помощью ключевого слова 'let'
  9. let a=16+5
  10. echo "Значение переменной \"a\" = $a."
  11.  
  12. # Неявное присваивание в заголовке цикла 'for' 
  13. echo -n "Значения переменной \"a\" в цикле: "
  14. for a in 7 8 9 11
  15. do
  16.   echo -n "$a "
  17. done
  18.  
  19. # Одна из разновидностей присваивания с использованием инструкции 'read' 
  20. echo -n "Введите значение переменной \"a\" "
  21. read a
  22. echo "Значение переменной \"a\" теперь стало равным: $a."
  23.  
  24. exit 0

Простое и замаскированное присваивание

  1. #!/bin/bash
  2.  
  3. a=23
  4. echo $a
  5. b=$a
  6. echo $b

# Подстановка команд, немного более сложный вариант.

  1. a=`echo Hello!`   # Переменной 'a' присваивается результат выполнения команды 'echo'
  2. echo $a

Обратите внимание на восклицательный знак (!) в подстанавливаемой команде, из командной строки такой вариант работать не будет, поскольку здесь используется механизм истории команд bash. Но в сценариях использование истории команд запрещено

  1. a=`ls -l`         # Переменной 'a' присваивается результат выполнения команды 'ls -l'
  2. echo $a           # Если не используются кавычки, будут удалены лишние пробелы и пустые строки
  3. echo
  4. echo "$a"         # При использовании кавычек, все пробелы и пустые строки будут сохранены
  5.  
  6. exit 0

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

  1. #!/bin/sh
  2.  
  3. R=$(uname)
  4. echo $R

f
reebsd /# ./test.sh
FreeBSD

Переменные Bash не имеют определенного типа

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

Целое число или строка

  1. #!/bin/bash
  2.  
  3. # Переменной присваивается целое число
  4. a=2334
  5. let "a += 1"
  6. echo "a = $a "
  7. # Переменная все еще является целым числом 2335
  8.  
  9. # Замена "23" на "BB"
  10. b=${a/23/BB}
  11.  
  12. # Происходит трансформация числа в строку.
  13. echo "b = $b"            # b = BB35
  14. declare -i b             # Явное указание типа ничего не изменит
  15. echo "b = $b"            # b = BB35
  16.  
  17. let "b += 1"             # BB35 + 1 =
  18. echo "b = $b"            # b = 1
  19.  
  20. c=BB34
  21. echo "c = $c"            # c = BB34
  22. d=${c/BB/23}             # замена "BB" на "23".
  23.  
  24. # Переменная $d становится целочисленной.
  25. echo "d = $d"            # d = 2334
  26. let "d += 1"             # 2334 + 1 =
  27. echo "d = $d"            # d = 2335
  28.  
  29. # Как ведут себя  "пустые" переменные
  30. e=""
  31. echo "e = $e"            # e =
  32. let "e += 1"             # В арифметических операциях возможно использование "пустых" переменных
  33. echo "e = $e"            # e = 1
  34. # "Пустая" переменная становится целочисленной.
  35.  
  36. # Поведение неинициализированных переменных
  37. echo "f = $f"            # f =
  38. let "f += 1"             # Арифметические операции разрешены
  39. echo "f = $f"            # f = 1
  40. echo                     # Неинициализированная переменная трансформируется в целочисленную.

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

Специальные типы переменных

локальные переменные

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

  1. #!/usr/local/bin/bash
  2.  
  3. a=111
  4. { a=555; }
  5. echo "a = $a"

freebsd /# ./test1.sh
a = 555

переменные окружения

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

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

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

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

 freebsd /# eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
freebsd /# bash$ du
freebsd /# /usr/bin/du: Argument list too long

Если сценарий меняет переменные окружения, они должны быть экспортированы, т.е передаться окружению, локальному по отношению к сценарию. Делается это с помощью команды export.

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

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

Позиционные параметры, это аргументы, получаемые сценарием при запуске из командной строки — $0, $1, $2, $3..., где $0 — это название файла сценария, $1 — первый аргумент, $2 — второй, $3 — третий и так далее. Аргументы, следующие за $9, должны быть заключены в фигурные скобки, например: ${10}, ${11}, ${12}.

Специальные переменные $* и $@ содержат все поступившие в сценарий позиционные параметры.

  1. #!/bin/bash
  2.  
  3. # Данный сценарий должен вызываться как минимум с 10 аргументами
  4. # ./scriptname 1 2 3 4 5 6 7 8 9 10
  5. MINPARAMS=10
  6.  
  7. echo
  8.  
  9. echo "Имя сценария: \"$0\"."
  10. # Для текущего каталога добавит ./
  11. echo "Имя файла сценария: \"`basename $0`\"."
  12. # Добавит путь к имени файла (см. 'basename')
  13.  
  14. echo
  15.  
  16. if [ -n "$1" ]              # Проверяемая переменная заключена в двойные кавычки.
  17. then
  18.  echo "Параметр #1: $1"     # здесь кавычки для нужны для экранирования символа #
  19. fi
  20.  
  21. if [ -n "$2" ]
  22. then
  23.  echo "Параметр #2: $2"
  24. fi
  25.  
  26. if [ -n "$3" ]
  27. then
  28.  echo "Параметр #3: $3"
  29. fi
  30.  
  31. # ...
  32.  
  33. if [ -n "${10}" ]  # Параметры, следующие за $9 должны заключаться в фигурные скобки
  34. then
  35.  echo "Параметр #10: ${10}"
  36. fi
  37.  
  38. echo "-----------------------------------"
  39. echo "Все аргументы командной строки: "$*""
  40.  
  41. if [ $# -lt "$MINPARAMS" ]
  42. then
  43.   echo
  44.   echo "Количество аргументов командной строки должно быть не менее $MINPARAMS !"
  45. fi
  46.  
  47. echo
  48.  
  49. exit 0

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

  1. args=$#           # Количество переданных аргументов.
  2. lastarg=${!args}  # Обратите внимание: lastarg=${!$#} неприменимо.

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

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

  1. variable1_=$1_ 
  2. # Это предотвратит появление ошибок, даже при отсутствии входного аргумента.
  3.  
  4. critical_argument01=$variable1_
  5.  
  6. # Дополнительные символы всегда можно "убрать" позднее.
  7. # Это может быть сделано примерно так:
  8. variable1=${variable1_/_/}   # Побочный эффект возникает только если имя переменной $variable1_ будет начинаться с символа "_".
  9. # Здесь используется один из вариантов подстановки параметров. Отсутствие шаблона замены приводит к удалению.
  10.  
  11. # Более простой способ заключается в обычной проверке передаваемых аргументов.
  12. if [ -z $1 ]
  13. then
  14.   exit $POS_PARAMS_MISSING
  15. fi

Сценарий использующий программу whois для определения имени домена

  1. #!/bin/bash
  2.  
  3. # Команда 'whois domain-name' определяет имя домена на одном из 3 серверов:
  4. #                    ripe.net, cw.net, radb.net
  5.  
  6. # Разместите этот скрипт под именем 'wh' в каталоге /usr/local/bin
  7.  
  8. # Требуемые символические ссылки:
  9. # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
  10. # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
  11. # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
  12.  
  13. if [ -z "$1" ]
  14. then
  15.   echo "Использование: `basename $0` [domain-name]"
  16.   exit 65
  17. fi
  18.  
  19. case `basename $0` in
  20. # Проверка имени скрипта и, соответственно, имени сервера
  21.     "wh"     ) whois $1@whois.ripe.net;;
  22.     "wh-ripe") whois $1@whois.ripe.net;;
  23.     "wh-radb") whois $1@whois.radb.net;;
  24.     "wh-cw"  ) whois $1@whois.cw.net;;
  25.     *        ) echo "Использования: `basename $0` [domain-name]";;
  26. esac
  27.  
  28. exit 0

Команда shift сдвигает позиционные параметры удаляя первый из них.

$1 <--- $2, $2 <--- $3, $3 <--- $4, и т.д.

При этом аргумент $0 (имя сценария) остается без изменений. Если вашему сценарию передается большое количество аргументов из командной строки, команда shift позволяет получить доступ к аргументам, с порядковым номером больше 9, без использования фигурных скобок.

Использование команды shift

В данном случае команда 'shift используется для перебора всех аргументов командной строки.

  1. #!/bin/bash
  2.  
  3. until [ -z "$1" ]  # Пока все параметры не будут разобраны
  4. do
  5.   echo -n "$1 "
  6.   shift
  7. done
  8.  
  9. echo              
  10.  
  11. exit 0

Так-же, команда shift, может применяться и к входным аргументам функций.

freebsd /# ./test1.sh 1 2 3 a b c
1 2 3 a b c