Среда SHELL - переменные и параметры

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

SHELL-переменные

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

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

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

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

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

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

var_1=13

"13" - это не число, а строка из двух цифр.

var_2="ОС UNIX"

Здесь двойные кавычки (" ") необходимы, так как в строке есть пробел.

Важно:
Обратим внимание на то, что, как переменная, так и ее значение должны быть записаны без пробелов относительно символа "=". Кстати, как видно из примеров, первым словом в командной строке может стоять не только имя команды, но и присваивание значения переменной. Об этом как раз и говорит наличие в беспробельной строке символов наличие (незаэкранированного) символа "=".

DAT=`date`

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

Можно присвоить значение переменной и с помощью команды "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

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

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

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

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

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" и экранирование в shell

Команда "eval" в shell используется для обработки и выполнения команд, записанных в переменных. Это позволяет динамически генерировать и выполнять команды в процессе выполнения скрипта.

Рассмотрим пример:

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

После выполнения следующих команд:

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

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

$v
$u
5

Давайте разберем еще один пример, связанный с экранированием перевода строки. Предположим, переменной "string" присвоено значение:

string="abc def"

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

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

приведут к следующим результатам:

abc def
$string
abc def

Если вы используете бэк-слэш (\) перед символом, он будет интерпретироваться буквально, а не как специальный символ. Например:

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

дает следующий вывод:

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

Бэк-слэш также позволяет объединять строки в одну, экранируя конец строки. Это может быть полезно при написании скриптов для улучшения читаемости кода.

Так, приведенный ранее пример командной строки:

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

может быть записан в командном файле следующим образом:

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

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

Манипуляции с 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

Рассмотрим также условную замену переменных. Если переменные, например "x", "y" и "z", не определены, то при обращении к этим переменным:

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

после этого соответствующая процедура завершится.

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

В определенных случаях, когда переменная "v" уже имеет присвоенное значение, следует обратить внимание на следующее:

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

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

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

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

Каждый процесс имеет свою собственную среду - это набор переменных, доступных ему. До запуска программы "р" уже была среда с определенным набором переменных. При запуске "р" создается новая среда, в которой будут выполняться "р1" и "р2".

Переменные являются локальными в рамках своего процесса. Чтобы сделать их доступными для других процессов, их необходимо экспортировать с помощью команды "export".

Рассмотрим пример:

# Программа 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
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

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

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

Параметры

В командные файлы можно передавать параметры. В 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

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

Рассмотрим действие команды "shift" на конкретном примере.

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

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

Содержание командного файла "many" выглядит так:

###
# 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 и так далее. После этой команды первый параметр становится недоступным.

Результат выполнения этого командного файла будет следующим:

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

Команда "set" позволяет устанавливать значения для позиционных параметров. Например:

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

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

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

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

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

Команда "set" без параметров выводит текущее состояние программной среды.

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

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

  1. Подстановка результатов. Команды, заключенные в обратные кавычки, выполняются, и их результаты подставляются на их место.
  2. Подстановка значений параметров и переменных. Слова, начинающиеся на "$", заменяются соответствующими значениями.
  3. Интерпретация пробелов. Экранированные пробелы не учитываются.
  4. Генерация имен файлов. Слова, содержащие специальные символы ("*", "?", "[]"), обрабатываются согласно этим символам.

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

Каждый процесс работает в своей среде. 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Это путь к домашнему каталогу пользователя. После успешного входа в систему пользователь будет автоматически перенаправлен в этот каталог.
PATH=/bin:/usr/bin:.:/usr/local/bin:/usr/bin/X11Переменная PATH определяет местоположение исполняемых файлов. Shell будет искать команды в указанных директориях. Поиск начинается с первой директории и продолжается до последней. Если команда найдена, она будет выполнена.
IFS=Переменная IFS (Internal Field Separator) определяет разделители, используемые для разбиения строки на слова. По умолчанию это пробел, табуляция и перевод строки.
LOGNAME=saeИмя пользователя, который вошел в систему.
MAIL=/var/spool/mail/saeДиректория, где хранится электронная почта пользователя.
PWD=/home/sae/STUDY/SHELLТекущая рабочая директория пользователя.
PS1=${PWD}:" "Основной приглашение командной строки. Здесь он будет отображать текущую рабочую директорию, за которой следует двоеточие и пробел.
PS2=>Вторичный приглашение командной строки, который отображается, когда необходимо продолжение ввода.
SHELL=/bin/bashОболочка, используемая пользователем. В данном случае это "bash" - расширенная версия стандартного shell.
TERM=linuxТип используемого терминала. TERMCAP содержит параметры для этого терминала.
UID=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.

Когда вы обращаетесь к позиционным параметрам в командном файле или shell-программе, перед цифровым обозначением параметра следует ставить символ "$".

Рассмотрим пример. При вызове команды:

specific par1 par2 par3

И содержанием командного файла:

# specific: Специальные параметры (переменные)
echo $0 - имя расчета
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" считает отсутствие параметра ошибкой. Эти режимы отображаются в специальной переменной "$-".