Chapter 14. 命令替换
命令替换将会重新分配一个命令使用命令替换的典型形式是使用后置引用(`...`). 后置引用形式的命令(就是被反引号括起来)将会产生命令行文本.
1 script_name=`basename $0` 2 echo "The name of this script is $script_name."
这样的话, 命令的输出可以被当成传递到另一个命令的参数, 或者保存到变量中, 甚至可以用来产生for循环的参数列表.
1 rm `cat filename` # "filename" 包含了需要被删除的文件列表. 2 # 3 # S. C. 指出使用这种形式, 可能会产生"参数列表太长"的错误. 4 # 更好的方法是 xargs rm -- < filename 5 # ( -- 同时覆盖了那些以"-"开头的文件所产生的特殊情况 ) 6 7 textfile_listing=`ls *.txt` 8 # 变量中包含了当前工作目录下所有的*.txt文件. 9 echo $textfile_listing 10 11 textfile_listing2=$(ls *.txt) # 这是命令替换的另一种形式. 12 echo $textfile_listing2 13 # 同样的结果. 14 15 # 将文件列表放入到一个字符串中的一个可能的问题就是 16 # 可能会混进一个新行. 17 # 18 # 一个安全的将文件列表传递到参数中的方法就是使用数组. 19 # shopt -s nullglob # 如果不匹配, 那就不进行文件名扩展. 20 # textfile_listing=( *.txt ) 21 # 22 # Thanks, S.C.
命令替换将会调用一个subshell.
命令替换可能会引起word splitting.
1 COMMAND `echo a b` # 2个参数: a and b 2 3 COMMAND "`echo a b`" # 1个参数: "a b" 4 5 COMMAND `echo` # 无参数 6 7 COMMAND "`echo`" # 一个空的参数 8 9 10 # Thanks, S.C.
即使没有引起word splitting, 命令替换也会去掉多余的新行.
1 # cd "`pwd`" # 这句总会正常的工作. 2 # 然而... 3 4 mkdir 'dir with trailing newline 5 ' 6 7 cd 'dir with trailing newline 8 ' 9 10 cd "`pwd`" # 错误消息: 11 # bash: cd: /tmp/file with trailing newline: No such file or directory 12 13 cd "$PWD" # 运行良好. 14 15 16 17 18 19 old_tty_setting=$(stty -g) # 保存老的终端设置. 20 echo "Hit a key " 21 stty -icanon -echo # 对终端禁用"canonical"模式. 22 # 这样的话, 也会禁用了*本地*的echo. 23 key=$(dd bs=1 count=1 2> /dev/null) # 使用'dd'命令来取得一个按键. 24 stty "$old_tty_setting" # 保存老的设置. 25 echo "You hit ${#key} key." # ${#variable} = number of characters in $variable 26 # 27 # 按键任何键除了回车, 那么输出就是"You hit 1 key." 28 # 按下回车, 那么输出就是"You hit 0 key." 29 # 新行已经被命令替换吃掉了. 30 31 Thanks, S.C.
当一个变量是使用命令替换的结果做为值的时候, 然后使用echo命令来输出这个变量(并且不引用这个变量, 就是不用引号括起来), 那么命令替换将会从最终的输出中删掉换行符. 这可能会引起一些异常情况.
1 dir_listing=`ls -l` 2 echo $dir_listing # 未引用, 就是没用引号括起来 3 4 # 想打出来一个有序的目录列表.Expecting a nicely ordered directory listing. 5 6 # 可惜, 下边将是我们所获得的: 7 # total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo 8 # bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh 9 10 # 新行消失了. 11 12 13 echo "$dir_listing" # 用引号括起来 14 # -rw-rw-r-- 1 bozo 30 May 13 17:15 1.txt 15 # -rw-rw-r-- 1 bozo 51 May 15 20:57 t2.sh 16 # -rwxr-xr-x 1 bozo 217 Mar 5 21:13 wi.sh
命令替换甚至允许将整个文件的内容放到变量中, 可以使用重定向或者cat命令.
1 variable1=`<file1` # 将"file1"的内容放到"variable1"中. 2 variable2=`cat file2` # 将"file2"的内容放到"variable2"中. 3 # 但是这行将会fork一个新进程, This, however, forks a new process, 4 #+ 所以这行代码将会比第一行代码执行得慢. 5 6 # 注意: 7 # 变量中是可以包含空白的, 8 #+ 甚至是 (厌恶至极的), 控制字符.
1 # 摘录自系统文件, /etc/rc.d/rc.sysinit 2 #+ (这是红帽安装中使用的) 3 4 5 if [ -f /fsckoptions ]; then 6 fsckoptions=`cat /fsckoptions` 7 ... 8 fi 9 # 10 # 11 if [ -e "/proc/ide/${disk[$device]}/media" ] ; then 12 hdmedia=`cat /proc/ide/${disk[$device]}/media` 13 ... 14 fi 15 # 16 # 17 if [ ! -n "`uname -r | grep -- "-"`" ]; then 18 ktag="`cat /proc/version`" 19 ... 20 fi 21 # 22 # 23 if [ $usb = "1" ]; then 24 sleep 5 25 mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"` 26 kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"` 27 ... 28 fi
不要将一个非常长的文本文件的内容设置到一个变量中, 除非你有一个非常好的原因非要这么做不可. 不要将2进制文件的内容保存到变量中.
Example 14-1. 愚蠢的脚本策略
1 #!/bin/bash 2 # stupid-script-tricks.sh: 朋友, 别在家这么做. 3 # 来自于"Stupid Script Tricks," 卷I. 4 5 6 dangerous_variable=`cat /boot/vmlinuz` # 这是压缩过的Linux内核本身. 7 8 echo "string-length of \$dangerous_variable = ${#dangerous_variable}" 9 # 这个字符串变量的长度是 $dangerous_variable = 794151 10 # (不要使用'wc -c /boot/vmlinuz'来计算长度.) 11 12 # echo "$dangerous_variable" 13 # 千万别尝试这么做! 这样将挂起这个脚本. 14 15 16 # 文档作者已经意识到将二进制文件设置到 17 #+ 变量中是一个没用的应用. 18 19 exit 0
注意, 在这里是不会发生缓冲区溢出错误. 因为这是一个解释型语言的实例, Bash就是一种解释型语言, 解释型语言会比编译型语言提供更多的对程序错误的保护措施.
变量替换允许将一个循环的输出放入到一个变量中.这么做的关键就是将循环中echo命令的输出全部截取.
Example 14-2. 从循环的输出中产生一个变量
1 #!/bin/bash 2 # csubloop.sh: 从循环的输出中产生一个变量. 3 4 variable1=`for i in 1 2 3 4 5 5 do 6 echo -n "$i" # 对于这里的命令替换来说 7 done` #+ 这个'echo'命令是非常关键的. 8 9 echo "variable1 = $variable1" # variable1 = 12345 10 11 12 i=0 13 variable2=`while [ "$i" -lt 10 ] 14 do 15 echo -n "$i" # 再来一个, 'echo'是必须的. 16 let "i += 1" # 递增. 17 done` 18 19 echo "variable2 = $variable2" # variable2 = 0123456789 20 21 # 这就证明了在一个变量声明中 22 #+ 嵌入一个循环是可行的. 23 24 exit 0
命令替换使得扩展有效的Bash工具集变为可能. 这样, 写一段小程序或者一段脚本就可以达到目的, 因为程序或脚本的输出会传到stdout上(就像一个标准的工具所做的那样), 然后重新将这些输出保存到变量中.(译者: 作者的意思就是在这种情况下写脚本和写程序作用是一样的.)
1 #include <stdio.h> 2 3 /* "Hello, world." C program */ 4 5 int main() 6 { 7 printf( "Hello, world." ); 8 return (0); 9 }
bash$ gcc -o hello hello.c
1 #!/bin/bash 2 # hello.sh 3 4 greeting=`./hello` 5 echo $greeting
bash$ sh hello.sh Hello, world.
对于命令替换来说,$(COMMAND)形式已经取代了反引号"`".
1 output=$(sed -n /"$1"/p $file) # 来自于 "grp.sh"例子. 2 3 # 将一个文本的内容保存到变量中. 4 File_contents1=$(cat $file1) 5 File_contents2=$(<$file2) # Bash 也允许这么做.
$(...)形式的命令替换在处理双反斜线(\\)时与`...`形式不同.
bash$ echo `echo \\`
bash$ echo $(echo \\)\
$(...)形式的命令替换是允许嵌套的.
Example 14-3. 找anagram(回文构词法, 可以将一个有意义的单词, 变换为1个或多个有意义的单词, 但是还是原来的子母集合)
1 #!/bin/bash 2 # agram2.sh 3 # 关于命令替换嵌套的例子. 4 5 # 使用"anagram"工具 6 #+ 这是作者的"yawl"文字表包中的一部分. 7 # http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz 8 # http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz 9 10 E_NOARGS=66 11 E_BADARG=67 12 MINLEN=7 13 14 if [ -z "$1" ] 15 then 16 echo "Usage $0 LETTERSET" 17 exit $E_NOARGS # 脚本需要一个命令行参数. 18 elif [ ${#1} -lt $MINLEN ] 19 then 20 echo "Argument must have at least $MINLEN letters." 21 exit $E_BADARG 22 fi 23 24 25 26 FILTER='.......' # 必须至少有7个字符. 27 # 1234567 28 Anagrams=( $(echo $(anagram $1 | grep $FILTER) ) ) 29 # | | 嵌套的命令替换 | | 30 # ( 数组分配 ) 31 32 echo 33 echo "${#Anagrams[*]} 7+ letter anagrams found" 34 echo 35 echo ${Anagrams[0]} # 第一个anagram. 36 echo ${Anagrams[1]} # 第二个anagram. 37 # 等等. 38 39 # echo "${Anagrams[*]}" # 在一行上列出所有的anagram . . . 40 41 # 考虑到后边还有"数组"作为单独的一章进行讲解, 42 #+ 这里就不深入了. 43 44 # 可以参阅agram.sh脚本, 这也是一个找出anagram的例子. 45 46 exit $?
命令替换在脚本中使用的例子:
注意事项:
对于命令替换来说, 这个命令可以是外部的系统命令, 也可以是内部脚本的内建命令, 甚至是一个脚本函数.
从技术的角度来讲, 命令替换将会抽取出一个命令的输出, 然后使用=操作赋值到一个变量中.
事实上, 对于反引号的嵌套是可行的, 但是只能将内部的反引号转义才行, 就像John默认指出的那样.
1 word_count=` wc -w \`ls -l | awk '{print $9}'\` `