Глава 14. Подстановка команд

Подстановка команд -- это подстановка результатов выполнения команды [1] или даже серии команд; буквально, эта операция позволяет вызвать команду в другом окружении. [2]

Классический пример подстановки команд -- использование обратных одиночных кавычек (`...`). Команды внутри этих кавычек представляют собой текст командной строки.

script_name=`basename $0`
echo "Имя этого файла-сценария: $script_name."


Вывод от команд может использоваться: как аргумент другой команды, для установки значения переменной и даже для генерации списка аргументов цикла for.

rm `cat filename`   # здесь "filename" содержит список удаляемых файлов.
#
# S. C. предупреждает, что в данном случае может возникнуть ошибка "arg list too long".
# Такой вариант будет лучше:   xargs rm -- < filename
# ( -- подходит для случая, когда "filename" начинается с символа "-" )

textfile_listing=`ls *.txt`
# Переменная содержит имена всех файлов *.txt в текущем каталоге.
echo $textfile_listing

textfile_listing2=$(ls *.txt)   # Альтернативный вариант.
echo $textfile_listing2
# Результат будет тем же самым.

# Проблема записи списка файлов в строковую переменную состоит в том,
# что символы перевода строки заменяются на пробел.
#
# Как вариант решения проблемы -- записывать список файлов в массив.
#      shopt -s nullglob    # При несоответствии, имя файла игнорируется.
#      textfile_listing=( *.txt )
#
# Спасибо S.C.


Note

Подстанавливаемая команда выполняется в подоболочке.

Caution

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

COMMAND `echo a b`     # 2 аргумента: a и b

COMMAND "`echo a b`"   # 1 аргумент: "a b"

COMMAND `echo`         # без аргументов

COMMAND "`echo`"       # один пустой аргумент


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


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

# cd "`pwd`"  # Должна выполняться всегда.
# Однако...

mkdir 'dir with trailing newline
'

cd 'dir with trailing newline
'

cd "`pwd`"  # Ошибка:
# bash: cd: /tmp/dir with trailing newline: No such file or directory

cd "$PWD"   # Выполняется без ошибки.





old_tty_setting=$(stty -g)   # Сохранить настройки терминала.
echo "Нажмите клавишу "
stty -icanon -echo           # Запретить "канонический" режим терминала.
                             # Также запрещает эхо-вывод.
key=$(dd bs=1 count=1 2> /dev/null)   # Поймать нажатие на клавишу.
stty "$old_tty_setting"      # Восстановить настройки терминала.
echo "Количество нажатых клавиш = ${#key}."  # ${#variable} = количество символов в переменной $variable
#
# Нажмите любую клавишу, кроме RETURN, на экране появится "Количество нажатых клавиш = 1."
# Нажмите RETURN, и получите: "Количество нажатых клавиш = 0."
# Символ перевода строки будет "съеден" операцией подстановки команды.

Спасибо S.C.


Caution

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

dir_listing=`ls -l`
echo $dir_listing     # без кавычек

# Вы наверно ожидали увидеть удобочитаемый список каталогов.

# Однако, вы получите:
# total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
# bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh

# Символы перевода строки были заменены пробелами.


echo "$dir_listing"   # в кавычках
# -rw-rw-r--    1 bozo       30 May 13 17:15 1.txt
# -rw-rw-r--    1 bozo       51 May 15 20:57 t2.sh
# -rwxr-xr-x    1 bozo      217 Mar  5 21:13 wi.sh


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

variable1=`<file1`      # Записать в переменную  "variable1" содержимое файла "file1".
variable2=`cat file2`   # Записать в переменную "variable2" содержимое файла "file2".
                        #  Однако, эта команда порождает дочерний процесс,
                        #+ поэтому эта строка выполняется медленнее, чем предыдущая.

#  Замечание:
#  В переменные можно записать даже управляющие символы.


#  Выдержки из системного файла /etc/rc.d/rc.sysinit
#+ (Red Hat Linux)


if [ -f /fsckoptions ]; then
        fsckoptions=`cat /fsckoptions`
...
fi
#
#
if [ -e "/proc/ide/${disk[$device]}/media" ] ; then
             hdmedia=`cat /proc/ide/${disk[$device]}/media`
...
fi
#
#
if [ ! -n "`uname -r | grep -- "-"`" ]; then
       ktag="`cat /proc/version`"
...
fi
#
#
if [ $usb = "1" ]; then
    sleep 5
    mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"`
    kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"`
...
fi


Caution

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

Пример 14-1. Глупая выходка

#!/bin/bash
# stupid-script-tricks.sh: Люди! Будьте благоразумны!
# Из "Глупые выходки", том I.


dangerous_variable=`cat /boot/vmlinuz`   # Сжатое ядро Linux.

echo "длина строки \$dangerous_variable = ${#dangerous_variable}"
# длина строки $dangerous_variable = 794151
# ('wc -c /boot/vmlinuz' даст другой результат.)

# echo "$dangerous_variable"
# Даже не пробуйте раскомментарить эту строку! Это приведет к зависанию сценария.


#  Автор этого документа не знает, где можно было бы использовать
#+ запись содержимого двоичных файлов в переменные.

exit 0

Обратите внимание: в данной ситуации не возникает ошибки переполнения буфера. Этот пример показывает превосходство защищенности интерпретирующих языков, таких как Bash, от ошибок программиста, над компилирующими языками программирования.

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

Пример 14-2. Запись результатов выполнения цикла в переменную

#!/bin/bash
# csubloop.sh: Запись результатов выполнения цикла в переменную

variable1=`for i in 1 2 3 4 5
do
  echo -n "$i"                 #  Здесь 'echo' -- это ключевой момент
done`

echo "variable1 = $variable1"  # variable1 = 12345


i=0
variable2=`while [ "$i" -lt 10 ]
do
  echo -n "$i"                 # Опять же, команда 'echo' просто необходима.
  let "i += 1"                 # Увеличение на 1.
done`

echo "variable2 = $variable2"  # variable2 = 0123456789

exit 0
Note

Альтернативой обратным одиночным кавычкам, используемым для подстановки команд, можно считать такую форму записи: $(COMMAND).

output=$(sed -n /"$1"/p $file)   # К примеру из
"grp.sh".
        
# Запись в переменную содержимого текстового файла.
File_contents1=$(cat $file1)
File_contents2=$(<$file2)        # Bash допускает и такую
запись.


Следует упомянуть, что в конструкции $(...) два идущих подряд обратных слэша интерпретируются несколько иначе, чем в конструкции `...`.

bash$ echo `echo \\`


bash$ echo $(echo \\)
\
        


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

  1. Пример 10-7

  2. Пример 10-26

  3. Пример 9-27

  4. Пример 12-3

  5. Пример 12-18

  6. Пример 12-15

  7. Пример 12-42

  8. Пример 10-13

  9. Пример 10-10

  10. Пример 12-27

  11. Пример 16-7

  12. Пример A-19

  13. Пример 27-2

  14. Пример 12-35

  15. Пример 12-36

  16. Пример 12-37


Notes

[1]

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

[2]

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