4. СРЕДА SHELL (ПЕРЕМЕННЫЕ И ПАРАМЕТРЫ)

На языке shell можно писать командные файлы и с помощью команды "chmod" делать их выполняемыми. После этого они ни чем не отличаются от прочих команд ОС UNIX.

4.1. shell-переменные

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

Значение shell-переменной - строка символов.

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

Однако интересно наблюдать то, как высококлассные программисты, освоившись с "правилами игры" shell, пишут на нем программы во много раз быстрее, чем на Си, но, что особенно интересно, в ряде случаев эти программы работают даже быстрее, чем реализованные на Си. (Но это уже случаи "высшего пилотажа").

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

Для присваивания значений переменным может использоваться оператор присваивания "=".

    var_1=13 - "13" - это не число, а строка из двух цифр.
    var_2="ОС UNIX" - здесь двойные кавычки (" ") необходимы, так как в строке есть пробел.
ВАЖНО: Обратим внимание на то, что, как переменная, так и ее значение должны быть записаны без пробелов относительно символа "=". Кстати, как видно из примеров, первым словом в командной строке может стоять не только имя команды, но и присваивание значения переменной. Об этом как раз и говорит наличие в беспробельной строке символов наличие (незаэкранированного) символа "=".

Возможны и иные способы присваивания значений shell-переменным. Так например запись,

    DAT=`date`

приводит к тому, что сначала выполняется команда "date" (обратные кавычки говорят о том, что сначала должна быть выполнена заключенная в них команда), а результат ее выполнения, вместо выдачи на стандартный выход, приписывается в качестве значения переменной, в данном случае "DAT".

Можно присвоить значение переменной и с помощью команды "read", которая обеспечивает прием значения переменной с (клавиатуры) дисплея в диалоговом режиме. Обычно команде "read" в командном файле предшествует команда "echo", которая позволяет предварительно выдать какое-то сообщение на экран. Например:

    echo -n "Введите трехзначное число:"
    read x

При выполнении этого фрагмента командного файла, после вывода на экран сообщения

    Введите трехзначное число:

интерпретатор остановится и будет ждать ввода значения с клавиатуры. Если вы ввели, скажем, "753" то это и станет значением переменной "x".

Одна команда "read" может прочитать (присвоить) значения сразу для нескольких переменных. Если переменных в "read" больше, чем их введено (через пробелы), оставшимся присваивается пустая строка. Если передаваемых значений больше, чем переменных в команде "read", то лишние игнорируются.

ПРЕДУПРЕЖДЕНИЕ. На самом деле интерпретатор для продолжения работы ждет лишь нажатия клавиши . Введенное вами число воспринимается им не как число, а как последовательность символов(!). Интерпретатор не проверяет, что вы ввели. Поэтому в качестве значения переменной может оказаться любая введенная абракадабра или просто нажатие , как значение пустой строки. (Для обеспечения проверки формата ввода следует написать свою команду).

При обращении к shell-переменной необходимо перед именем ставить символ "$". Так команды

    echo $var_2
    echo var_2

выдадут на экран

    ОС UNIX
    var_2

И еще один пример. Фрагмент командного файла:

    echo "var_2 = $var_2"

выдаст на экран

    var_2 = ОС UNIX

В команде "echo" первое использование "var_2" - это просто текст, а второе ("$var_2") - это значение соответствующей переменной.

То что здесь присутствуют пробелы между именем переменной и символом присваивания, а также между символом присваивания и значением, так это потому, что здесь мы имеем дело лишь с текстом, куда подставлены значения переменных. Там, где действительно выполняется присваивание, пробелы в этих местах НЕДОПУСТИМЫ. Присваивание, скажем, w= означает присваивание переменной "w" пустой строки. Но и пустую строку лучше присваивать аккуратно, например w="".

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

Пусть a=/mnt/lab/asu/
тогда

    cat /mnt/lab/asu/prim

и

    cat ${a}prim

равноценны (т.е. "cat" выдаст на экран содержимое одного и того же файла).

Если также предположить, что в системе есть переменная "prim" и "prim=dir" , то команда

    echo ${a}$prim

выдаст на экран

    /mnt/lab/asu/dir

4.2. Экранирование

Рассмотрим более подробно приемы экранирования, используемые в shell. В качестве средств экранирования используются двойные кавычки (" "), одинарные кавычки (' ') и бэк-слэш (\).

Из примеров очевидно их действие:

Можно в одной строке записывать несколько приcваиваний.

    x=22 y=33 z=$x
    A="$x" B='$x' C=\$x
    D="$x + $y + $z"  E='$x + $y + $z'  F=$x\ +\ $y\ +\ $z

(присваивание G=$x + $y не было бы выполнено из-за пробелов)
Тогда

    echo A = $A   B = $B   C = $C
    echo D = $D   E = $E   F = $F
    eval echo evaluated A = $A
    eval echo evaluated B = $B
    eval echo evaluated C = $C

Выдадут на экран

    A = 22 B = $x C = $x
    D = 22 + 33 + 22 E = $x + $y + $z F = 22 + 33 + 22
    evaluated A = 22
    evaluated B = 22
    evaluated C = 22
ВНИМАНИЕ. В трех последних случаях использована своеобразная команда "eval" (от evaluate - означивать), которая в подставленной в нее (в качестве аргумента) команде выполняет означивание переменных (если таковые имеются). В результате значение "A" остается прежним, поскольку "A" имеет значение "22". А переменные "B" и "C" имеют значение "$x". За счет означивания, которое было выполнено командой "eval" - evaluated "B" и "C" дают значения "22".

Еще один пример на "eval".

Пусть

    w=\$v v=\$u u=5

В результате выполнения команд

	       echo $w
	  eval echo $w
    eval eval echo $w

на экран будет выведено

    $v
    $u
    5

Приведем еще примеры, связанные с экранированием перевода строки. Пусть переменной "string" присвоено значение "массива" 2x3:

abc
def

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

string="abc
def"

Тогда три варианта записи переменной в команде "echo"

echo  $string
echo '$string'
echo "$string"

дадут соответственно три различных результата:

abc def
$string
abc
def

а последовательность команд

echo "строка первая
строка вторая" > f1
echo 'строка первая
строка вторая' > f2
cat f1 f2

даст выдаст последовательно одинаковые файлы f1 и f2:

строка первая
строка вторая
строка первая
строка вторая

Заметим также, что бэк-слэш (\) не только экранирует следующий за ним символ, что позволяет использовать специальные символы просто как символы, представляющие сами себя (он может экранировать и сам себя - \\), но в командном файле бэк-слэш позволяет об'единять строки в одну (экранировать конец строки).

Например, приводившийся ранее пример командной строки:

    cat f1 | grep -h result | sort | cat -b > f2

может быть записан в командном файле, скажем, как

    cat f1 | grep -h  \
    result | sort | cat -b > f2

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

    cat f1         |
    grep -h result |
    sort           |
    cat -b > f2

4.3. Манипуляции с shell-переменными

Несмотря на то, что shell-переменные в общем случае воспринимаются как строки, т. е. "35" - это не число, а строка из двух символов "3" и "5", в раде случаев они могут интерпретироваться иначе, например, как целые числа.

Разнообразные возможности имеет команда "expr".

Проиллюстрируем некоторые на примерах:

Выполнение командного файла:

    x=7 y=2
    a=`expr $x + $y`   ; echo a=$a
    a=`expr $a + 1`    ; echo a=$a
    b=`expr $y - $x`   ; echo b=$b
    c=`expr $x '*' $y` ; echo c=$c
    d=`expr $x / $y`   ; echo d=$d
    e=`expr $x % $y`   ; echo e=$e

выдаст на экран

    a=9
    a=10
    b=-5
    c=14
    d=3
    e=1
ВНИМАНИЕ. Операция умножения ("*") обязательно должна быть заэкранирована, поскольку в shell этот значок воспринимается, как спецсимвол, означающий, что на это место может быть подставлена любая последовательность символов.
Следует обратить также внимание на обязательные пробелы, отделяющие переменные и знаки операций.

С командой "expr" возможны не только (целочисленные) арифметические операции, но и строковые:

    A=`expr 'cocktail' : 'cock'`  ; echo $A
    B=`expr 'cocktail' : 'tail'`  ; echo $B
    C=`expr 'cocktail' : 'cook'`  ; echo $C
    D=`expr 'cock' : 'cocktail'`  ; echo $D

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

    4
    0
    0
    0

И наконец, об условной замене переменных.

Если переменные, скажем "х", "y", "z", не определены, то при обращении к переменным

${x-new} в качестве значения "x" будет выдано "new",
${y=new} в качестве значения "у" будет присвоено "new",
${z?new} в качестве значения "z" будет выдано "z: new" и

соответствующая процедура прекращается.

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

А в следующем случае наоборот, пусть переменная "v" имеет какое-то значение, тогда

${z+new} в качестве значения "z" будет выдано "new", а если не было присвоено значение, то пустая строка.

4.4. Экспорт переменных

В ОС UNIX существует понятие процесса. Процесс возникает тогда, когда запускается на выполнение какая-либо команда (расчет).

Например, при наборе на клавиатуре "р <Enter>" порождается процесс расчета "р". В свою очередь "р" может породить другие процессы. Допустим, что "р" вызывает расчеты "р1" и "р2", которые последовательно порождают соответствующие процессы.

У каждого процесса есть своя среда - множество доступных ему переменных. Например, до запуска расчета "р" уже существовала среда, в которой уже были определены некоторые переменные (о стандартных переменных речь пойдет несколько позже). Запуск "р" порождает новую среду; уже в ней будут порождены расчеты "р1" и "р2".

Переменные локальны в рамках процесса, в котором они объявлены, т.е. где им присвоены значения (описание переменных отсутствует - они все одного типа). Для того, чтобы они были доступны и другим порождаемым процессам, надо передать их явным образом. Для этого используется встроенная команда "export".

Пример.

Пусть расчет (командный файл) "p", имеющий вид:

    # расчет p
    echo Расчет p
    varX=0 varY=1
    echo varX=$varX varY=$varY
    export varY
    p1  # вызов расчета p1
    p2  # вызов расчета p2
    echo Снова расчет p: varX=$varX varY=$varY

вызывает командные файлы "p1" и "p2", имеющие вид:

    # расчет p1
    echo Расчет p1
    echo varX=$varX varY=$varY
    varX=a varY=b
    echo varX=$varX varY=$varY
    export varX
    # расчет p2
    echo Расчет p2
    echo varX=$varX varY=$varY
    varX=A varY=B
    echo varX=$varX varY=$varY
    export varY

На экран будут выданы следующая информация:

    Расчет p
    varX=0 varY=1
    Расчет p1
    varX= varY=1
    varX=a varY=b
    Расчет p2
    varX= varY=1
    varX=A varY=B
    Снова расчет p: varX=0 varY=1

Из примера видно, что значения переменных экспортируются только в вызываемые расчеты (и не передаются "вверх" и "вбок"). Экспортировать переменные можно и командой "set" с флагом "-a".

НА ВСЯКИЙ СЛУЧАЙ заметим, что на передачу значений переменных никакого влияния не оказывает "физическое" взаимное расположение (файлов) расчетов в файловой системе.

4.5. Параметры

В командный файл могут быть переданы параметры. В shell используются позиционные параметры (т.е. существенна очередность их следования). В командном файле соответствующие параметрам переменные (аналогично shell-переменным) начинаются с символа "$", а далее следует одна из цифр от 0 до 9:

Пусть расчет "examp-1" вызывается с параметрами "cock" и "tail". Эти параметры попадают в новую среду под стандартными именами "1" и "2". В (стандартной) переменной с именем "0" будет храниться имя вызванного расчета.

При обращении к параметрам перед цифрой ставится символ доллара "$" (как и при обращении к переменным):

$0 соответствует имени данного командного файла;
$1 первый по порядку параметр;
$2 второй параметр и т.д.

Пусть командный файл с именем "examp-1" имеет вид

     echo Это расчет $0:
     sort $2 >> $1
     cat $1

а файлы "cock" и "tail" содержат соответственно

cock:

Это отсортированный файл:

tail:
     1
     3
     2

Тогда после вызова команды

     examp-1 cock tail

на экране будет

    Это расчет examp-1:
    Это отсортированный файл:
    1
    2
    3

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

Рассмотрим ее действие на примере.

Пусть командный файл "many" вызывается с 13-ю параметрами

    many 10 20 30 40 50 60 70 80 90 100 110 120 130

И имеет вид

    ###
    # many: Передача большого числа параметров.
    echo "$0: Много параметров"
    echo " Общее число параметров = $#
    Исходное состояние: $1 $5 $9 "
    shift
    echo "1 сдвиг: первый=$1 пятый=$5 девятый=$9"
    shift 2
    echo "1 + 2 = 3 сдвига: первый=$1 пятый=$5 девятый=$9"
    perem=`expr $1 + $2 + $3`
    echo $perem

В результате первого применения команды "shift" второй параметр расчета вызывается как $1, третий параметр вызывается как $2, ... десятый параметр, который был исходно недоступен, вызывается как $9. Но стал недоступным первый параметр!

После выполнения этого расчета на экране будет:

    many: Много параметров
    Общее число параметров = 13
    Исходное состояние: 10 50 90
    1 сдвиг: первый=20 пятый=60 девятый=100
    1 + 2 = 3 сдвиг: первый=40 пятый=80 девятый=120
    150

Своеобразный подход к параметрам дает команда "set".

Например, фрагмент расчета

    set a b с
    echo первый=$1 второй=$2 третий=$3

выдаст на экран

    первый=a второй=b третий=c

т.е. команда "set" устанавливает значения параметров. Это бывает очень удобно. Например, команда "date" выдает на экран текущую дату, скажем, "Mon May 01 12:15:10 2000", состоящую из пяти слов, тогда

     set `date`
     echo $1 $3 $5

выдаст на экран

     Mon 01 2000

Команда "set" позволяет также осуществлять контроль выполнения программы, например:

set -v на терминал выводятся строки, читаемые shell.
Set +v отменяет предыдущий режим.
Set -x на терминал выводятся команды перед выполнением.
Set +x отменяет предыдущий режим.

Команда "set" без параметров выводит на терминал состояние программной среды (см далее).

4.6. Подстановки shell-интерпретатора

Перед началом непосредственной интерпретации и выполнением команд, содержащихся в командных файлах, shell выполняет различные виды подстановок:

  1. ПОДСТАНОВКА РЕЗУЛЬТАТОВ. Выполняются все команды, заключенные в обратные кавычки, и на их место подставляется результат.
  2. ПОДСТАНОВКА ЗНАЧЕНИЙ ПАРАМЕТРОВ И ПЕРЕМЕННЫХ. То есть слова, начинающиеся на "$", заменяются соответствующими значениями переменных и параметров.
  3. ИНТЕРПРЕТАЦИЯ ПРОБЕЛОВ. Заэкранированные пробелы игнорируются.
  4. ГЕНЕРАЦИЯ ИМЕН ФАЙЛОВ. Проверяются слова на наличие в них спецсимволов ("*", "?","[]") и выполняются соответствующие генерации.

4.7. Программная среда

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

Если вы наберете команду "set" без параметров, то на экран будет выдана информация о ряде стандартных переменных, созданных при входе в систему (и передаваемых далее всем вашим новым процессам "по наследству"), а также переменных, созданных и экспортируемых вашими процессами.

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

Вот лишь часть того, что выдала мне команда "set":

    HOME=/home/sae
    PATH=/usr/local/bin:/usr/bin:/bin:.:/usr/bin/X11:
    IFS=
    LOGNAME=sae
    MAIL=/var/spool/mail/sae
    PWD=/home/sae/STUDY/SHELL
    PS1=${PWD}:" "
    PS2=>
    SHELL=/bin/bash
    TERM=linux
    TERMCAP=console|con80x25|dumb|linux:li#25:co#80::
    UID=501
    perem=stroka
    x=5

Прокомментируем эти присваивания значений переменным.

HOME=/home/sae это имя домашнего директория, в котором пользователь (в данном случае я) оказывается после входа в систему. То есть, правильно набрав имя и пароль, я окажусь в директории "/home/sae".
PATH=/bin:/usr/bin:.:/usr/local/bin:/usr/bin/X11 эта переменная задает последовательность файлов (ТРОПУ), которые просматривает "shell" в поисках команды. Имена файлов разделяются здесь двоеточиями. Последовательность просмотра соответствует очередности следования имен в тропе. НО ПЕРВОНАЧАЛЬНО поиск происходит среди так называемых встроенных команд. В число встроенных команд входят наиболее часто используемые команды, например "echo", "cd", "pwd", "date". После этого система просматривает директорий "/bin", в котором могут находиться команды "sh", "cp", "mv", "ls" и т.п. Затем директорий "/usr/bin" с командами "cat", "сс", "expr", "nroff", "man" и многими другими. Далее поиск происходит в текущем директории (".", или другое обозначение "пусто", т.е.""), где скорее всего находятся написанные вами команды (расчеты).

После набора командной строки и нажатия <Enter> "shell" (после выполнения необходимых подстановок) распознает имя, соответствующее команде и осуществляет ее поиск в директориях, перечисленных в тропе. Если команда размещена вне этих директориев, она не будет найдена. Если присутствует несколько команд с одинаковым именем, то вызвана будет та, которая расположена в директории, просматриваемом первым.

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

IFS= (Внутренний Разделитель Полей) перечисляет символы, которые служат для разделения слов (полей). Таковыми являются "пробел", "табуляция" и "перевод строки", поэтому здесь слева от присваивания ничего не видно и занято две строки.
LOGNAME=sae имя входа ("имя" пользователя).
MAIL=/var/spool/mail/sae имя файла, в который поступает (электронная) почта.
PWD=/home/sae/STUDY/SHELL имя текущего директория
PS1=${PWD}:" " вид промтера. В данном случае в промптере будет выдаваться имя текущего директория двоеточие и пробел. То есть здесь будет "/home/sae/STUDY/SHELL: ".
PS2=> этот промтер (здесь ">") используется как приглашение к продолжению ввода (в очередной строке) незаконченной команды. Например, наберите открывающую скобку "(" и после нажатия <Enter> в следующей строке вы увидите этот промптер. Если пока не знаете, что дальше делать, наберите закрывающую скобку ")" и он исчезнет.
SHELL=/bin/bash эта переменная указывает оболочку, которую использует пользователь. В данном случае используется не (стандартный) shell ("sh"), а "продвинутая" версия .
TERM=linux указание типа терминала. -"bash", написанная тем же автором (Bourne-Again SHell)

TERMCAP=console|con80x25|dumb|linux:li#25:co#80::

(TERMinal CAPacity) это (очень сильно) обрезанная строка задания параметров терминала.

UID=501 идентификатор пользователя (мой "501").
perem=stroka

x=5

переменные, которые ввел пользователь.

Исходная среда устанавливается автоматически при входе в систему с использованием файлов типа "/etc/rc" и "/etc/.profile".

ВАЖНОЕ ЗАМЕЧАНИЕ. Один из способов просто изменит среду (например, тропу поиска команд, вид промтера, вид оболочки, цвет экрана и т.п.) можно, разместив эту информацию в своем домашнем директории в специализированном файле ".profile" (${HOME}/.profile), присвоив нужные значения переменным среды. То есть вызвать это файл в редактор и написать, что пожелаете). Тогда при каждом вашем входе в систему этот файл будет автоматически выполняться и устанавливать новую среду. Этот файл должен ОБЯЗАТЕЛЬНО размещаться в вашем ДОМАШНЕМ директории (директории входа).

Если вы внесли изменения в ".profile", то для переноса этих изменений в среду необходимо выполнить этот файл. Для этого можно выйти и заново войти в систему, а можно воспользоваться (специально для этого случая созданной) командой "." без выхода из системы, т.е.

    . .profile

Следует иметь в виду, что имена файлов, начинающиеся с точки, вообще имеют особый статус. Так, они не выдаются на экран простой командой "ls" - необходимо вызывать эту команду с флагом "-a". Кстати, и не уничтожаются огульно командой "rm *".

Дописать новый свой директорий "my" в тропу команд можно, записав в ".profile", например

    PATH=${PATH}:/home/sae/my

или

    PATH=${PATH}:${HOME}/my

Как правило, устанавливаемые переменные среды следует экспортировать. Например,

    export TERM PATH REDKEYS MAIL

Кроме определения переменных в ".profile" можно выполнить команды, например команда

     stty -lcase

установит терминал в режим "большие и маленькие буквы"; а команда

     cat заставка

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

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

? значение, возвращенное последней командой;
$ номер процесса;
! номер фонового процесса;
# число позиционных параметров, передаваемых в shell;
* перечень параметров, как одна строка;
@ перечень параметров, как совокупность слов;
- флаги, передаваемые в shell.

При обращении к этим переменным (т.е при использовании их в командном файле - shell-программе) следует впереди ставить "$".

Пример. Вызов расчета

    specific par1 par2 par3

имеющего вид

    ###
    # specific: Специальные параметры (переменные)
    echo $0 - имя расчета
    echo $? - код завершения
    echo $$ - идентификатор последнего процесса
    echo $! - идентификатор последнего фонового процесса
    echo
    echo $* - значения параметров, как строки
    echo $@ - значения параметров, как слов
    echo
    set -au
    echo $- - режимы работы интерпретатора

Выдаст на экран

    specific - имя расчета
    0 - код завершения
    499 - идентификатор последнего процесса
    98 - идентификатор последнего фонового процесса
    par1 par2 par3 - значения параметров, как строки
    par1 par2 par3 - значения параметров, как слов
    au - режимы работы интерпретатора

Код "0" соответствует нормальному завершению процесса.

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

"echo" без параметров выводит пустую строку.

Различия $* и $@ состоит в том, что первая переменная может быть представлена как

    "par1  par2  par3"

а вторая как

    "par1" "par2" "par3"

Пример, иллюстрирующий различия "$*" и "$@" будет рассмотрен в связи с оператором "for".

Для иллюстрации мы установили командой "set" режимы интерпретатора ("a" - все последующие переменные экспортируются; "u" - отсутствие параметра считать ошибкой), что и отразилось в специальной переменной "$-".

[Назад] [Содержание] [Вперед]