shell教程
OutOfMemory.CN技术专栏-> shell-> shell教程-> Chapter 14. 命令替换

Chapter 14. 命令替换

命令替换将会重新分配一个命令[1]甚至是多个命令的输出;它会将命令的输出如实地添加到另一个上下文中.[2]使用命令替换的典型形式是使用后置引用(`...`).后置引用形式的命令(就是被反引号括起来)将会产生命令行文本.script_name=`basename$0`echo"Thenameofthisscriptis$script_name."这样的话,命令的输出可以被当成传递到另一个命令的参数,

命令替换 将会重新分配一个命令[1]甚至是多个命令的输出; 它会将命令的输出如实地添加到另一个上下文中. [2]

使用命令替换的典型形式是使用后置引用(`...`). 后置引用形式的命令(就是被反引号括起来)将会产生命令行文本.

script_name=`basename $0`
echo "The name of this script is $script_name."

这样的话, 命令的输出可以被当成传递到另一个命令的参数, 或者保存到变量中, 甚至可以用来产生for循环的参数列表.

rm `cat filename`   # "filename" 包含了需要被删除的文件列表.
#
# S. C. 指出使用这种形式, 可能会产生"参数列表太长"的错误.
# 更好的方法是              xargs rm -- < filename
# ( -- 同时覆盖了那些以"-"开头的文件所产生的特殊情况 )

textfile_listing=`ls *.txt`
# 变量中包含了当前工作目录下所有的*.txt文件.
echo $textfile_listing

textfile_listing2=$(ls *.txt)   # 这是命令替换的另一种形式.
echo $textfile_listing2
# 同样的结果.

# 将文件列表放入到一个字符串中的一个可能的问题就是
# 可能会混进一个新行.
#
# 一个安全的将文件列表传递到参数中的方法就是使用数组.
#      shopt -s nullglob    # 如果不匹配, 那就不进行文件名扩展.
#      textfile_listing=( *.txt )
#
# Thanks, S.C.

命令替换将会调用一个subshell.

命令替换可能会引起word splitting.

COMMAND `echo a b`     # 2个参数: a and b

COMMAND "`echo a b`"   # 1个参数: "a b"

COMMAND `echo`         # 无参数

COMMAND "`echo`"       # 一个空的参数


# Thanks, S.C.

即使没有引起word splitting, 命令替换也会去掉多余的新行.

# cd "`pwd`"  # 这句总会正常的工作.
# 然而...

mkdir 'dir with trailing newline
'

cd 'dir with trailing newline
'

cd "`pwd`"  # 错误消息:
# bash: cd: /tmp/file with trailing newline: No such file or directory

cd "$PWD"   # 运行良好.





old_tty_setting=$(stty -g)   # 保存老的终端设置.
echo "Hit a key "
stty -icanon -echo           # 对终端禁用"canonical"模式.
                             # 这样的话, 也会禁用了*本地*的echo.
key=$(dd bs=1 count=1 2> /dev/null)   # 使用'dd'命令来取得一个按键.
stty "$old_tty_setting"      # 保存老的设置.
echo "You hit ${#key} key."  # ${#variable} = number of characters in $variable
#
# 按键任何键除了回车, 那么输出就是"You hit 1 key."
# 按下回车, 那么输出就是"You hit 0 key."
# 新行已经被命令替换吃掉了.

Thanks, S.C.

当一个变量是使用命令替换的结果做为值的时候, 然后使用echo命令来输出这个变量(并且不引用这个变量, 就是不用引号括起来), 那么命令替换将会从最终的输出中删掉换行符. 这可能会引起一些异常情况.

dir_listing=`ls -l`
echo $dir_listing     # 未引用, 就是没用引号括起来

# 想打出来一个有序的目录列表.Expecting a nicely ordered directory 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`      #  将"file1"的内容放到"variable1"中.
variable2=`cat file2`   #  将"file2"的内容放到"variable2"中.
                        #  但是这行将会fork一个新进程, This, however, forks a new process,
                        #+ 所以这行代码将会比第一行代码执行得慢.

#  注意:
#  变量中是可以包含空白的,
#+ 甚至是 (厌恶至极的), 控制字符.

#  摘录自系统文件, /etc/rc.d/rc.sysinit
#+ (这是红帽安装中使用的)


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

不要将一个非常长的文本文件的内容设置到一个变量中, 除非你有一个非常好的原因非要这么做不可. 不要将2进制文件的内容保存到变量中.


Example 14-1. 愚蠢的脚本策略

#!/bin/bash
# stupid-script-tricks.sh: 朋友, 别在家这么做.
# 来自于"Stupid Script Tricks," 卷I.


dangerous_variable=`cat /boot/vmlinuz`   # 这是压缩过的Linux内核本身.

echo "string-length of \$dangerous_variable = ${#dangerous_variable}"
# 这个字符串变量的长度是 $dangerous_variable = 794151
# (不要使用'wc -c /boot/vmlinuz'来计算长度.)

# echo "$dangerous_variable"
# 千万别尝试这么做! 这样将挂起这个脚本.


#  文档作者已经意识到将二进制文件设置到
#+ 变量中是一个没用的应用.

exit 0

注意, 在这里是不会发生缓冲区溢出错误. 因为这是一个解释型语言的实例, Bash就是一种解释型语言, 解释型语言会比编译型语言提供更多的对程序错误的保护措施.

变量替换允许将一个循环的输出放入到一个变量中.这么做的关键就是将循环中echo命令的输出全部截取.


Example 14-2. 从循环的输出中产生一个变量

#!/bin/bash
# csubloop.sh: 从循环的输出中产生一个变量.

variable1=`for i in 1 2 3 4 5
do
  echo -n "$i"                 #  对于这里的命令替换来说
done`                          #+ 这个'echo'命令是非常关键的.

echo "variable1 = $variable1"  # variable1 = 12345


i=0
variable2=`while [ "$i" -lt 10 ]
do
  echo -n "$i"                 # 再来一个, 'echo'是必须的.
  let "i += 1"                 # 递增.
done`

echo "variable2 = $variable2"  # variable2 = 0123456789

#  这就证明了在一个变量声明中
#+ 嵌入一个循环是可行的.

exit 0

对于命令替换来说,$(COMMAND) 形式已经取代了反引号"`".

output=$(sed -n /"$1"/p $file)   # 来自于 "grp.sh"例子.

# 将一个文本的内容保存到变量中.
File_contents1=$(cat $file1)
File_contents2=$(<$file2)        # Bash 也允许这么做.

$(...) 形式的命令替换在处理双反斜线(\\)时与`...`形式不同.

 bash$ echo `echo \\`
 
 
 bash$ echo $(echo \\)
 \
 	      

$(...) 形式的命令替换是允许嵌套的. [3]

word_count=$( wc -w $(ls -l | awk '{print $9}') )

或者, 可以更加灵活. . .


Example 14-3. 找anagram(回文构词法, 可以将一个有意义的单词, 变换为1个或多个有意义的单词, 但是还是原来的子母集合)

#!/bin/bash
# agram2.sh
# 关于命令替换嵌套的例子.

#  使用"anagram"工具
#+ 这是作者的"yawl"文字表包中的一部分.
#  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
#  http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz

E_NOARGS=66
E_BADARG=67
MINLEN=7

if [ -z "$1" ]
then
  echo "Usage $0 LETTERSET"
  exit $E_NOARGS         # 脚本需要一个命令行参数.
elif [ ${#1} -lt $MINLEN ]
then
  echo "Argument must have at least $MINLEN letters."
  exit $E_BADARG
fi



FILTER='.......'         # 必须至少有7个字符.
#       1234567
Anagrams=( $(echo $(anagram $1 | grep $FILTER) ) )
#           |     |    嵌套的命令替换        | |
#        (              数组分配                 )

echo
echo "${#Anagrams[*]}  7+ letter anagrams found"
echo
echo ${Anagrams[0]}      # 第一个anagram.
echo ${Anagrams[1]}      # 第二个anagram.
                         # 等等.

# echo "${Anagrams[*]}"  # 在一行上列出所有的anagram . . .

#  考虑到后边还有"数组"作为单独的一章进行讲解,
#+ 这里就不深入了.

# 可以参阅agram.sh脚本, 这也是一个找出anagram的例子.

exit $?

命令替换在脚本中使用的例子:

  1. Example 10-7

  2. Example 10-26

  3. Example 9-28

  4. Example 12-3

  5. Example 12-19

  6. Example 12-15

  7. Example 12-49

  8. Example 10-13

  9. Example 10-10

  10. Example 12-29

  11. Example 16-8

  12. Example A-17

  13. Example 27-2

  14. Example 12-42

  15. Example 12-43

  16. Example 12-44

注意事项:

[1]

对于命令替换来说, 这个命令可以是外部的系统命令, 也可以是内部脚本的内建命令, 甚至是一个脚本函数.

[2]

从技术的角度来讲, 命令替换将会抽取出一个命令的输出, 然后使用=操作赋值到一个变量中.

[3]

事实上, 对于反引号的嵌套是可行的, 但是只能将内部的反引号转义才行, 就像John默认指出的那样.

word_count=` wc -w \`ls -l | awk '{print $9}'\` `

© 内存溢出 OutOfMemory.CN