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


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

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

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


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

  1. rm `cat filename`   # здесь "filename" содержит список удаляемых файлов.
  2. #
  3. # S. C. предупреждает, что в данном случае может возникнуть ошибка "arg list too long".
  4. # Такой вариант будет лучше:   xargs rm — < filename
  5. # ( — подходит для случая, когда "filename" начинается с символа "-" )
  6. textfile_listing=`ls *.txt`
  7. # Переменная содержит имена всех файлов *.txt в текущем каталоге.
  8. echo $textfile_listing
  9. textfile_listing2=$(ls *.txt)   # Альтернативный вариант.
  10. echo $textfile_listing2
  11. # Результат будет тем же самым.
  12. # Проблема записи списка файлов в строковую переменную состоит в том,
  13. # что символы перевода строки заменяются на пробел.
  14. #
  15. # Как вариант решения проблемы — записывать список файлов в массив.
  16. #      shopt -s nullglob    # При несоответствии, имя файла игнорируется.
  17. #      textfile_listing=( *.txt )
  18. #
  19. # Спасибо S.C.


Note

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

Caution

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

  1. COMMAND `echo a b`     # 2 аргумента: a и b
  2. COMMAND "`echo a b`"   # 1 аргумент: "a b"
  3. COMMAND `echo`         # без аргументов
  4. COMMAND "`echo`"       # один пустой аргумент
  5. # Спасибо S.C.


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

  1. # cd "`pwd`"  # Должна выполняться всегда.
  2. # Однако...
  3. mkdir 'dir with trailing newline
  4. '
  5. cd 'dir with trailing newline
  6. '
  7. cd "`pwd`"  # Ошибка:
  8. # bash: cd: /tmp/dir with trailing newline: No such file or directory
  9. cd "$PWD"   # Выполняется без ошибки.
  10. old_tty_setting=$(stty -g)   # Сохранить настройки терминала.
  11. echo "Нажмите клавишу "
  12. stty -icanon -echo           # Запретить "канонический" режим терминала.
  13.                              # Также запрещает эхо-вывод.
  14. key=$(dd bs=1 count=1 2> /dev/null)   # Поймать нажатие на клавишу.
  15. stty "$old_tty_setting"      # Восстановить настройки терминала.
  16. echo "Количество нажатых клавиш = ${#key}."  # ${#variable} = количество символов в переменной $variable
  17. #
  18. # Нажмите любую клавишу, кроме RETURN, на экране появится "Количество нажатых клавиш = 1."
  19. # Нажмите RETURN, и получите: "Количество нажатых клавиш = 0."
  20. # Символ перевода строки будет "съеден" операцией подстановки команды.
  21. Спасибо S.C.


Caution

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

  1. dir_listing=`ls -l`
  2. echo $dir_listing     # без кавычек
  3. # Вы наверно ожидали увидеть удобочитаемый список каталогов.
  4. # Однако, вы получите:
  5. # total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
  6. # bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh
  7. # Символы перевода строки были заменены пробелами.
  8. echo "$dir_listing"   # в кавычках
  9. # -rw-rw-r--    1 bozo       30 May 13 17:15 1.txt
  10. # -rw-rw-r--    1 bozo       51 May 15 20:57 t2.sh
  11. # -rwxr-xr-x    1 bozo      217 Mar  5 21:13 wi.sh


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

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


  1. #  Выдержки из системного файла /etc/rc.d/rc.sysinit
  2. #+ (Red Hat Linux)
  3. if [ -f /fsckoptions ]; then
  4.         fsckoptions=`cat /fsckoptions`
  5. ...
  6. fi
  7. #
  8. #
  9. if [ -e "/proc/ide/${disk[$device]}/media" ] ; then
  10.              hdmedia=`cat /proc/ide/${disk[$device]}/media`
  11. ...
  12. fi
  13. #
  14. #
  15. if [ ! -n "`uname -r | grep — "-"`" ]; then
  16.        ktag="`cat /proc/version`"
  17. ...
  18. fi
  19. #
  20. #
  21. if [ $usb = "1" ]; then
  22.     sleep 5
  23.     mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"`
  24.     kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"`
  25. ...
  26. fi


Caution

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

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

  1. #!/bin/bash
  2. # stupid-script-tricks.sh: Люди! Будьте благоразумны!
  3. # Из "Глупые выходки", том I.
  4. dangerous_variable=`cat /boot/vmlinuz`   # Сжатое ядро Linux.
  5. echo "длина строки \$dangerous_variable = ${#dangerous_variable}"
  6. # длина строки $dangerous_variable = 794151
  7. # ('wc -c /boot/vmlinuz' даст другой результат.)
  8. # echo "$dangerous_variable"
  9. # Даже не пробуйте раскомментарить эту строку! Это приведет к зависанию сценария.
  10. #  Автор этого документа не знает, где можно было бы использовать
  11. #+ запись содержимого двоичных файлов в переменные.
  12. exit 0

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

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

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

  1. #!/bin/bash
  2. # csubloop.sh: Запись результатов выполнения цикла в переменную
  3. variable1=`for i in 1 2 3 4 5
  4. do
  5.   echo -n "$i"                 #  Здесь 'echo' — это ключевой момент
  6. done`
  7. echo "variable1 = $variable1"  # variable1 = 12345
  8. i=0
  9. variable2=`while [ "$i" -lt 10 ]
  10. do
  11.   echo -n "$i"                 # Опять же, команда 'echo' просто необходима.
  12.   let "i += 1"                 # Увеличение на 1.
  13. done`
  14. echo "variable2 = $variable2"  # variable2 = 0123456789
  15. exit 0
Note

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

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


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

  1. bash$ echo `echo \\`
  2. bash$ echo $(echo \\)
  3. \
  4.        


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

  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


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

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