Глава 9. К вопросу о переменных

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

9.1. Внутренние переменные

Встроенные переменные
$BASH

путь к исполняемому файлу Bash

bash$ echo $BASH
/bin/bash

              

$BASH_VERSINFO[n]

это массив, состоящий из 6 элементов, и содержащий информацию о версии Bash. Очень похожа на переменную $BASH_VERSION, описываемую ниже.

# Информация о версии Bash:

for n in 0 1 2 3 4 5
do
  echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done

# BASH_VERSINFO[0] = 2                      # Major version no.
# BASH_VERSINFO[1] = 05                     # Minor version no.
# BASH_VERSINFO[2] = 8                      # Patch level.
# BASH_VERSINFO[3] = 1                      # Build version.
# BASH_VERSINFO[4] = release                # Release status.
# BASH_VERSINFO[5] = i386-redhat-linux-gnu  # Architecture
                                            # (same as $MACHTYPE).

              

$BASH_VERSION

версия Bash, установленного в системе

bash$ echo $BASH_VERSION
2.04.12(1)-release
             

              

tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.
             

              

Проверка переменной $BASH_VERSION -- неплохой метод проверки типа командной оболочки, под которой исполняется скрипт. Переменная $SHELL не всегда дает правильный ответ.

$DIRSTACK

содержимое вершины стека каталогов (который управляется командами pushd и popd)

Эта переменная соответствует команде dirs, за исключением того, что dirs показывает полное содержимое всего стека каталогов.

$EDITOR

заданный по-умолчанию редактор, вызываемый скриптом, обычно vi или emacs.

$EUID

"эффективный" идентификационный номер пользователя (Effective User ID)

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

Caution

Значение переменной $EUID необязательно должно совпадать с содержимым переменной $UID.

$FUNCNAME

имя текущей функции

xyz23 ()
{
  echo "Исполняется функция $FUNCNAME."  # Исполняется функция xyz23.
}

xyz23

echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
                                   # Пустое (Null) значение за пределеми функций.

              

$GLOBIGNORE

Перечень шаблонных символов, которые будут проигнорированы при выполнении подстановки имен файлов (globbing) .

$GROUPS

группы, к которым принадлежит текущий пользователь

Это список групп (массив) идентификационных номеров групп для текущего пользователя, как эо записано в /etc/passwd.

root# echo $GROUPS
0


root# echo ${GROUPS[1]}
1


root# echo ${GROUPS[5]}
6
             

              

$HOME

домашний каталог пользователя, как правило это /home/username (см. Пример 9-13)

$HOSTNAME

Сетевое имя хоста устанавливается командой hostname во время исполнения инициализирующих сценариев на загрузке системы. Внутренняя переменная $HOSTNAME Bash получает свое значение посредством вызова функции gethostname(). См. так же Пример 9-13.

$HOSTTYPE

тип машины

Подобно $MACHTYPE, идентифицирует аппаратную архитектуру.

bash$ echo $HOSTTYPE
i686

            
$IFS

разделитель полей во вводимой строке (IFS -- Input Field Separator)

По-умолчанию -- пробельный символ (пробел, табуляция и перевод строки), но может быть изменен, например, для разбора строк, в которых отдельные поля разделены запятыми. Обратите внимание: при составлении содержимого переменной $*, Bash использует первый символ из $IFS для разделения аргументов. См. Пример 5-1.

bash$ echo $IFS | cat -vte
$


bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
w:x:y:z
             

              

Caution

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

Пример 9-1. $IFS и пробельные символы

#!/bin/bash
# При использовании $IFS, пробельные символы обрабатываются иначе, чем все остальные.

output_args_one_per_line()
{
  for arg
  do echo "[$arg]"
  done
}

echo; echo "IFS=\" \""
echo "-------"

IFS=" "
var=" a  b c   "
output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
#
# [a]
# [b]
# [c]


echo; echo "IFS=:"
echo "-----"

IFS=:
var=":a::b:c:::"               # То же самое, только пробелы зменены символом ":".
output_args_one_per_line $var
#
# []
# [a]
# []
# [b]
# [c]
# []
# []
# []

# То же самое происходит и с разделителем полей "FS" в awk.

# Спасибо Stephane Chazelas.

echo

exit 0

                      


(Спасибо S. C., за разъяснения и примеры.)

$LC_COLLATE

Чаще всего устанавливается в .bashrc или /etc/profile, эта переменная задает порядок сортировки символов, в операциях подстановки имен файлов и в поиске по шаблону. При неверной настройке переменной LC_COLLATE можно получить весьма неожиданные результаты.

Note

Начиная с версии 2.05, Bash, в операциях подстановки имен файлов, не делает различий между символами верхнего и нижнего регистров, в диапазонах символов в квадратных скобках. Например,, ls [A-M]* выведет как File1.txt, так и file1.txt. Возврат к общепринятому стандарту поведения шаблонов в квадратных скобках выполняется установкой переменной LC_COLLATE в значение C командой export LC_COLLATE=C в файле /etc/profile и/или ~/.bashrc.

$LC_CTYPE

Эта внутренняя переменная определяет кодировку символов. Используется в операциях подстановки и поиске по шаблону.

$LINENO

Номер строки исполняемого сценария. Эта переменная имеет смысл только внутри исполняемого сценария и чаще всего применяется в отладочных целях.

# *** BEGIN DEBUG BLOCK ***
last_cmd_arg=$_  # Запомнить.

echo "Строка $LINENO: переменная \"v1\" = $v1"
echo "Последний аргумент командной строки = $last_cmd_arg"
# *** END DEBUG BLOCK ***

              

$MACHTYPE

аппаратная архитектура

Идентификатор аппаратной архитектуры.

bash$ echo $MACHTYPE
i686

            
$OLDPWD

прежний рабочий каталог ("OLD-Print-Working-Directory")

$OSTYPE

тип операционной системы

bash$ echo $OSTYPE
linux

            
$PATH

путь поиска, как правило включает в себя каталоги /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, и т.д.

Когда командный интерпретатор получает команду, то он автоматически пытается отыскать соответствующий исполняемый файл в указанном списке каталогов (в переменной $PATH). Каталоги, в указанном списке, должны отделяться друг от друга двоеточиями. Обычно, переменная $PATH инициализируется в /etc/profile и/или в ~/.bashrc (см. Глава 26).

bash$ echo $PATH
/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

              

Инструкция PATH=${PATH}:/opt/bin добавляет каталог /opt/bin в конец текущего пути поиска. Иногда может оказаться целесообразным, внутри сценария, временно добавить какой-либо каталог к пути поиска. По завершении работы скрипта, эти изменения будут утеряны (вспомните о том, что невозможно изменить переменные окружения вызывающего процесса).

Note

Текущий "рабочий каталог", ./, обычно не включается в $PATH из соображений безопасности.

$PIPESTATUS

Код возврата канала (конвейера). Интересно, что это не то же самое, что код возврата последней исполненной команды.

bash$ echo $PIPESTATUS
0

bash$ ls -al | bogus_command
bash: bogus_command: command not found
bash$ echo $PIPESTATUS
141

bash$ ls -al | bogus_command
bash: bogus_command: command not found
bash$ echo $?
127
             

              

Caution

Переменная $PIPESTATUS может давать неверные значения при вызове из командной строки.

tcsh% bash

bash$ who | grep nobody | sort
bash$ echo ${PIPESTATUS[*]}
0
             

                      

Если поместить эти строки в сценарий и исполнить его, то будут выведены верные значения 0 1 0.

Спасибо Wayne Pollock за замечания и предоставленный пример.

$PPID

Переменная $PPID хранит PID (идентификатор) родительского процесса. [1]

Сравните с командой pidof.

$PS1

prompt, приглашение командной строки.

$PS2

Вторичное приглашение командной строки, выводится тогда, когда от пользователя ожидается дополнительный ввод. Отображается как ">".

$PS3

Третичное приглашение (prompt), выводится тогда, когда пользователь должен сделать выбор в операторе select (см. Пример 10-29).

$PS4

Приглашение (prompt) четвертого уровня, выводится в начале каждой строки вывода тогда, когда сценарий вызывается с ключом -x. Отображается как "+".

$PWD

рабочий (текущий) каталог

Аналог встроенной команды pwd.

#!/bin/bash

E_WRONG_DIRECTORY=73

clear # Очистка экрана.

TargetDirectory=/home/bozo/projects/GreatAmericanNovel

cd $TargetDirectory
echo "Удаление файлов в каталоге $TargetDirectory."

if [ "$PWD" != "$TargetDirectory" ]
then    # Защита от случайного удаления файлов не в том каталоге.
  echo "Неверный каталог!"
  echo "Переменная $PWD указывает на другой каталог!"
  exit $E_WRONG_DIRECTORY
fi

rm -rf *
rm .[A-Za-z0-9]*    # удалить "скрытые" файлы (начинающиеся с ".")
# rm -f .[^.]* ..?*   удалить файлы, чьи имена начинаются с нескольких точек.
# (shopt -s dotglob; rm -f *)   тоже работает верно.
# Спасибо S.C. за замечание.

# Имена файлов могут содержать любые символы из диапазона 0-255, за исключением "/".
# Оставляю вопрос удаления файлов с "необычными" символами для самостоятельного изучения.

# Здесь можно вставить дополнительные действия, по мере необходимости.

echo
echo "Конец."
echo "Файлы, из каталога $TargetDirectory, удалены."
echo


exit 0

              

$REPLY

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

#!/bin/bash

echo
echo -n "Ваше любимое растение? "
read

echo "Ваше любимое растение: $REPLY."
# REPLY хранит последнее значение, прочитанное командой "read" тогда, и только тогда
#+ когда команде "read" не передается имя переменной.

echo
echo -n "Ваш любимый фрукт? "
read fruit
echo "Ваш любимый фрукт $fruit."
echo "но..."
echo "Значение переменной \$REPLY осталось равным $REPLY."
# Переменная $REPLY не была перезаписана потому, что
# следующей команде "read", в качестве аргумента была передана переменная $fruit

echo

exit 0

              

$SECONDS

Время паботы сценария в секундах.

#!/bin/bash
# Автор: Mendel Cooper
# Дополнен переводчиком.
#

TIME_LIMIT=10
INTERVAL=1

echo
echo "Для прерывания работы сценария, ранее чем через $TIME_LIMIT секунд, нажмите Control-C."
echo

while [ "$SECONDS" -le "$TIME_LIMIT" ]
do
# Оригинальный вариант сценария содержал следующие строки
#  if [ "$SECONDS" -eq 1 ]
#  then
#    units=second
#  else
#    units=seconds
#  fi
#
# Однако, из-за того, что в русском языке для описания множественного числа
# существует большее число вариантов, чем в английском,
# переводчик позволил себе смелость несколько подправить сценарий
# (прошу ногами не бить! ;-) )
# === НАЧАЛО БЛОКА ИЗМЕНЕНИЙ, ВНЕСЕННЫХ ПЕРЕВОДЧИКОМ ===

  let "last_two_sym = $SECONDS - $SECONDS / 100 * 100" # десятки и единицы
  if [ "$last_two_sym" -ge 11 -a "$last_two_sym" -le 19 ]
  then
    units="секунд"               # для чисел, которые заканчиваются на "...надцать"
  else
    let "last_sym = $last_two_sym - $last_two_sym / 10 * 10"  # единицы
    case "$last_sym" in
      "1" )
        units="секунду"         # для чисел, заканчивающихся на 1
      ;;
      "2" | "3" | "4" )
        units="секунды"         # для чисел, заканчивающихся на 2, 3 и 4
      ;;
      * )
        units="секунд"          # для всех остальных (0, 5, 6, 7, 8, 9)
      ;;
    esac
  fi
# === КОНЕЦ БЛОКА ИЗМЕНЕНИЙ, ВНЕСЕННЫХ ПЕРЕВОДЧИКОМ ===

  echo "Сценарий отработал $SECONDS $units."
  #  В случае перегруженности системы, скрипт может перескакивать через отдельные
  #+  значения счетчика
  sleep $INTERVAL
done

echo -e "\a"  # Сигнал!

exit 0

              

$SHELLOPTS

список допустимых опций интерпретатора shell. Переменная доступна только для чтения.

bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
             

              

$SHLVL

Уровень вложенности shell. Если в командной строке

echo $SHLVL

              дает 1, то в сценарии значение этой переменной будет
              больше на 1, т.е. 2.

$TMOUT

Если переменная окружения $TMOUT содержит ненулевое значение, то интерпретатор будет ожидать ввод не более чем заданное число секунд, что, в первичном приглашении (см. описание PS1 выше), может привести к автоматическому завершению сеанса работы.

Note

К сожалению это возможно только во время ожидания ввода с консоли или в окне терминала. А как было бы здорово, если бы можно было использовать эту внутреннюю переменную, скажем в комбинации с командой read! Но в данном контексте эта переменная абсолютно не применима и потому фактически бесполезна в сценариях. (Есть сведения о том, что в ksh время ожидания ввода командой read можно ограничить.)

Организация ограничения времени ожидания ввода от пользователя в сценариях возможна, но это требут довольно сложных махинаций. Как один из вариантов, можно предложить организовать прерывание цикла ожидания по сигналу. Но это потребует написание функции обработки сигналов командой trap (см. Пример 29-5).

Пример 9-2. Ограничения времени ожидания ввода

#!/bin/bash
# timed-input.sh

# TMOUT=3            бесполезно в сценариях

TIMELIMIT=3  # Три секунды в данном случае, но может быть установлено и другое значение

PrintAnswer()
{
  if [ "$answer" = TIMEOUT ]
  then
    echo $answer
  else       # Чтобы не спутать разные варианты вывода.
    echo "Ваше любимое растение $answer"
    kill $!  # "Прибить" ненужную больше функцию TimerOn, запущенную в фоновом процессе.
             # $! -- PID последнего процесса, запущенного в фоне.
  fi

}



TimerOn()
{
  sleep $TIMELIMIT && kill -s 14 $$ &
  # Ждать 3 секунды, после чего выдать sigalarm сценарию.
}

Int14Vector()
{
  answer="TIMEOUT"
  PrintAnswer
  exit 14
}

trap Int14Vector 14   # переназначить процедуру обработки прерывания от таймера (14)

echo "Ваше любимое растение? "
TimerOn
read answer
PrintAnswer


#  По общему признанию, это не очень хороший способ ограничения времени ожидания,
#+ однако опция "-t"команды "read" упрощает задачу.
#  См. "t-out.sh", ниже.

#  Если вам нужно что-то более элегантное...
#+ подумайте о написании программы на C или C++,
#+ с использованием соответствующих библиотечных функций, таких как 'alarm' и 'setitimer'.

exit 0

              

В качестве альтернативы можно использовать stty.

Пример 9-3. Еще один пример ограничения времени ожидания ввода от пользователя

#!/bin/bash
# timeout.sh

# Автор: Stephane Chazelas,
# дополнен автором документа.

INTERVAL=5                # предел времени ожидания

timedout_read() {
  timeout=$1
  varname=$2
  old_tty_settings=`stty -g`
  stty -icanon min 0 time ${timeout}0
  eval read $varname      # или просто    read $varname
  stty "$old_tty_settings"
  # См. man stty.
}

echo; echo -n "Как Вас зовут? Отвечайте быстрее! "
timedout_read $INTERVAL your_name

# Такой прием может не работать на некоторых типах терминалов.
# Максимальное время ожидания зависит от терминала.
# (чаще всего это 25.5 секунд).

echo

if [ ! -z "$your_name" ]  # Если имя было введено...
then
  echo "Вас зовут $your_name."
else
  echo "Вы не успели ответить."
fi

echo

# Алгоритм работы этого сценария отличается от "timed-input.sh".
# Каждое нажатие на клавишу вызывает сброс счетчика в начальное состояние.

exit 0

              

Возможно самый простой способ -- использовать опцию -t команды read.

Пример 9-4. Ограничение времени ожидания команды read

#!/bin/bash
# t-out.sh 

TIMELIMIT=4        # 4 секунды

read -t $TIMELIMIT variable <&1

echo

if [ -z "$variable" ]
then
  echo "Время ожидания истекло."
else
  echo "variable = $variable"
fi  

exit 0

              
$UID

user id number

UID (идентификатор) текущего пользователя, в соответствии с /etc/passwd

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

Пример 9-5. Я -- root?

#!/bin/bash
# am-i-root.sh:   Root я, или не root?

ROOT_UID=0   # $UID root-а всегда равен 0.

if [ "$UID" -eq "$ROOT_UID" ]  # Настоящий "root"?
then
  echo "- root!"
else
  echo "простой пользователь (но мамочка вас тоже любит)!"
fi

exit 0


# ============================================================= #
#  Код, приведенный ниже, никогда не отработает,
#+ поскольку работа сценария уже завершилась выше

# Еще один способ отличить root-а от не root-а:

ROOTUSER_NAME=root

username=`id -nu`              # Или...   username=`whoami`
if [ "$username" = "$ROOTUSER_NAME" ]
then
  echo "Рутти-тутти. - root!"
else
  echo "Вы - лишь обычный юзер."
fi

              

См. также Пример 2-2.

Note

Переменные $ENV, $LOGNAME, $MAIL, $TERM, $USER и $USERNAME, не являются встроенными переменными Bash. Тем не менее, они часто инициализируются как переменные окружения в одном из стартовых файлов Bash. Переменная $SHELL, командная оболочка пользователя, может задаваться в /etc/passwd или в сценарии "init" и она тоже не является встроенной переменной Bash.

tcsh% echo $LOGNAME
bozo
tcsh% echo $SHELL
/bin/tcsh
tcsh% echo $TERM
rxvt

bash$ echo $LOGNAME
bozo
bash$ echo $SHELL
/bin/tcsh
bash$ echo $TERM
rxvt
             

                      

Позиционные параметры (аргументы)

$0, $1, $2 и т.д.

аргументы передаются... из командной строки в сценарий, функциям или команде set (см. Пример 4-5 и Пример 11-13)

$#

количество аргументов командной строки [2], или позиционных параметров (см. Пример 33-2)

$*

Все аргументы в виде одной строки (слова)

$@

То же самое, что и $*, но при этом каждый параметр представлен как отдельная строка (слово), т.е. параметры не подвергаются какой либо интерпретации.

Пример 9-6. arglist: Вывод списка аргументов с помощью переменных $* и $@

#!/bin/bash
# Вызовите сценарий с несколькими аргументами, например: "один два три".

E_BADARGS=65

if [ ! -n "$1" ]
then
  echo "Порядок использования: `basename $0` argument1 argument2 и т.д."
  exit $E_BADARGS
fi

echo

index=1

echo "Список аргументов в переменной \"\$*\":"
for arg in "$*"  # Работает некорректно, если "$*" не ограничена кавычками.
do
  echo "Аргумент #$index = $arg"
  let "index+=1"
done             # $* воспринимает все аргументы как одну строку.
echo "Полный список аргументов выглядит как одна строка."

echo

index=1

echo "Список аргументов в переменной \"\$@\":"
for arg in "$@"
do
  echo "Аргумент #$index = $arg"
  let "index+=1"
done             # $@ воспринимает аргументы как отдельные строки (слова).
echo "Список аргументов выглядит как набор различных строк (слов)."

echo

exit 0

              

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

#!/bin/bash
# Вызовите сценарий в таком виде: ./scriptname 1 2 3 4 5

echo "$@"    # 1 2 3 4 5
shift
echo "$@"    # 2 3 4 5
shift
echo "$@"    # 3 4 5

# Каждая из команд "shift" приводит к потере аргумента $1,
# но остальные аргументы остаются в "$@".

              

Специальная переменная $@ может быть использована для выбора типа ввода в сценария. Команда cat "$@" позволяет выполнять ввод как со стандартного устройства ввода stdin, так и из файла, имя которого передается сценарию из командной строки. См. Пример 12-17 и Пример 12-18.

Caution

Переменные $* и $@, в отдельных случаях, могут содержать противоречивую информацию! Это зависит от содержимого переменной $IFS.

Пример 9-7. Противоречия в переменных $* и $@

#!/bin/bash

#  Демонстрация противоречивости содержимого внутренних переменных "$*" и "$@",
#+ которая проявляется при изменении порядка заключения параметров в кавычки.
#  Демонстрация противоречивости, проявляющейся при изменении
#+ содержимого переменной IFS.


set -- "Первый один" "второй" "третий:один" "" "Пятый: :один"
# Установка аргументов $1, $2, и т.д.

echo

echo 'IFS по-умолчанию, переменная "$*"'
c=0
for i in "$*"               # в кавычках
do echo "$((c+=1)): [$i]"   # Эта строка остается без изменений во всех циклах.
                            # Вывод аргументов.
done
echo ---

echo 'IFS по-умолчанию, переменная $*'
c=0
for i in $*                 # без кавычек
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS по-умолчанию, переменная "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS по-умолчанию, переменная $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

IFS=:
echo 'IFS=":", переменная "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
echo ---

var=$*
echo 'IFS=":", переменная "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

var="$*"
echo 'IFS=":", переменная $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

var=$@
echo 'IFS=":", переменная $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

var="$@"
echo 'IFS=":", переменная "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", переменная $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done

echo

# Попробуйте запустить этот сценарий под ksh или zsh -y.

exit 0

# Это сценарий написан Stephane Chazelas,
# Незначительные изменения внесены автором документа.

              
Note

Различия между $@ и $* наблюдаются только тогда, когда они помещаются в двойные кавычки.

Пример 9-8. Содержимое $* и $@, когда переменная $IFS -- пуста

#!/bin/bash

# Если переменная $IFS инициализирована "пустым" значением,
# то "$*" и "$@" содержат аргументы не в том виде, в каком ожидается.

mecho ()       # Вывод аргументов.
{
echo "$1,$2,$3";
}


IFS=""         # Инициализация "пустым" значением.
set a b c      # Установка аргументов.

mecho "$*"     # abc,,
mecho $*       # a,b,c

mecho $@       # a,b,c
mecho "$@"     # a,b,c

# Поведение переменных $* и $@, при "пустой" $IFS, зависит
# от версии командной оболочки, Bash или sh.
# Поэтому, было бы неразумным пользоваться этой "фичей" в своих сценариях.


# Спасибо S.C.

exit 0

              

Прочие специальные переменные

$-

Список флагов, переданных сценарию (командой set). См. Пример 11-13.

Caution

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

$!

PID последнего, запущенного в фоне, процесса

LOG=$0.log

COMMAND1="sleep 100"

echo "Запись в лог всех PID фоновых процессов, запущенных из сценария: $0" >> "$LOG"
# Таким образом возможен мониторинг и удаление процессов по мере необходимости.
echo >> "$LOG"

# Команды записи в лог.

echo -n "PID of \"$COMMAND1\":  " >> "$LOG"
${COMMAND1} &
echo $! >> "$LOG"
# PID процесса "sleep 100":  1506

# Спасибо Jacques Lederer за предложенный пример.

              

$_

Специальная переменная, содержит последний аргумент предыдущей команды.

Пример 9-9. Переменная "подчеркивание"

#!/bin/bash

echo $_              # /bin/bash
                     # Для запуска сценария был вызван /bin/bash.

du >/dev/null        # Подавление вывода.
echo $_              # du

ls -al >/dev/null    # Подавление вывода.
echo $_              # -al  (последний аргумент)

:
echo $_              # :

              
$?

Код возврата команды, функции или скрипта (см. Пример 22-3)

$$

PID самого процесса-сценария. Переменная $$ часто используется при генерации "уникальных" имен для временных файлов (см. Пример A-14, Пример 29-6, Пример 12-23 и Пример 11-23). Обычно это проще чем вызов mktemp.

Примечания

[1]

PID текущего процесса хранится в переменной $$.

[2]

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