第十七章 Here Documents
here document就是一段特殊的代码块。他使用I/O重定向的形式来将一个命令序列传递到一个交互程序或者命令中,比如ftp、cat或者ex文本编辑器。
limit string 用来划定命令序列的范围(注: 两个相同的 limit string 之间就是命令序列)。特殊符号 << 用来标识 limit string。这个符号具有重定向文件的输出到程序或命令的输入的作用。与 interactive-program < command-file 很相象。
command-file 包含:
command #1
command #2
...
而here document的形式看上去是如下样子:
#!/bin/bash
interactive-program << LimitString
command #1
command #2
...
LimitString
选择一个名字非常诡异的 limit string 将会避免命令列表和 limit string 重名的问题。注意:某些时候 here document 用在非交互工具和命令上的时候也会有好的效果,比如:wall。
Example 17-1 广播:发送消息给每个登录上的用户
#!/bin/bash
#
wall << zzz23EndOfMessagezzz23
E-mail your noontime orders for pizza to the system administrator.
#额外的消息文本写在这里.
# 注意: 'wall' 会打印注释行.
zzz23EndOfMessagezzz23
#可以使用更有效的做法:
#wall < message-file
#然而将消息模版嵌入到脚本中是一种"小吃店"(快速但是比较脏)的只能使用一次的解决办法.
exit 0
即使是某些不大可能的工具,如 vi 也可以使用 here document。(亲测貌似不适用,会产生一个交换文件)
Example 17-2 仿造文件:创建一个两行的仿造文件
#!/bin/bash
#
#用非交互的方式来使用vi编辑一个文件。:模仿sed
E_BADARGS=65
if [ -z "$1" ];then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
TARGETFILE=$1
#在文件中插入两行,然后保存。
vi $TARGETFILE << x23LimitStringx23
i
This is line 1 of the example file.
This is line 2 of the example file.
^[ #^[是一个转义符,键入 Ctrl+v 就行,事实上它是键.
ZZ
x23LimitStringx23
# Bram Moolenaar 指出这种方法不能正常地用在'vim'上,因为可能会有终端的相互影响问题.
exit 0
上边的脚本也可以不用 vi 而用 ex 来实现。Here document 包含 ex 命令列表的做法足够形成自己的类别了,叫 ex scripts。
#!/bin/bash
#
#把所有后缀为".txt"文件中的"Smith"都替换成"Jones".
ORIGINAL=Smith
REPLACEMENT=Jones
for word in $(fgrep -l $ORIGINAL *.txt) #grep -l:列出文件内容符合指定的样式的文件名称
do
ex $word <<EOF
:%s/$ORIGINAL/$REPLACEMENT/g
:wq
EOF
done
与"ex scripts"相似的是 cat scripts。
Example 17-3 使用cat的多行消息
#!/bin/bash
#
#'echo' 对于打印单行消息是非常好的,但是在打印消息块时可能就有点问题了.
#'cat' here document 可以解决这个限制.
cat << End-of-message
-------------------------------------
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is line last of the message.
-------------------------------------
End-of-message
#用下边这行代替上边的第 7 行,会把输出写到文件$Newfile 中, 而不是 stdout.
#cat > $Newfile << End-of-message
exit 0
“-” 选项用来标记here document的limit string(<<-LimitString),可以抑制输出时前边的tab(不是空格)。这可以增加一个脚本的可读性。
Example 17-4 带有抑制tab功能的多行消息
#!/bin/bash
#
#与之前的例子相同, 但是...
#- 选项对于 here docutment 来说,<<-可以抑制文档体前边的 tab,而不是空格.
cat <<- End-of-message
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is line last of the message.
End-of-message
#脚本在输出的时候左边将被刷掉.
#就是说每行前边的 tab 将不会显示.
#上边 5 行"消息"的前边都是 tab, 不是空格.空格是不受<<-影响的.
# 注意, 这个选项对于*嵌在*中间的 tab 没作用.
exit 0
here document 支持参数和命令替换。所以也可以给 here document 的消息体传递不同的参数,这样相应的也会修改输出。
Example 17-5 使用参数替换的here document
#!/bin/bash
#
CMDLINEPAPAM=1
if [ $# -ge $CMDLINEPAPAM ];then
NAME=$1
else
NAME="John Doe"
fi
RESPONDENT="the author of this fine script"
cat << EOF
Hello,there,$NAME
Greetings to you, $NAME, from $RESPONDENT.
#This comment shows up in the output (why?).
EOF
exit 0
这是一个包含参数替换的 here document 的有用的脚本.
Example 17-6 上传一个文件对到"Sunsite"的 incoming 目录
#!/bin/bash
#
E_ARGERROR=65
if [ -z "$1" ];then
echo "Usage: `basename $0` Filename-to-upload"
exit $E_ARGERROR
fi
Filename=`basename $1` # 从文件名中去掉目录字符串.
Server="192.168.1.69"
Directory="/tmp"
Password="123456"
ftp -n $Server <<EOF #ftp -n:禁用自动登录.
user ftpadmin "$Password"
binary
bell
cd $Directory
put "$Filename.lsm"
put "$Filename.tar.gz"
bye
EOF
exit 0
在 here document 的开头引用或转义"limit string"会使得 here document 的消息体中的参数替换被禁用。
Example 17-7 关闭参数替换
#!/bin/bash
#
NAME="John Doe"
RESPONDENT="the author of this fine script"
cat << 'EOF'
Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.
EOF
#当"limit string"被引用或转义那么就禁用了参数替换.
#下边的两种方式具有相同的效果.
#cat << "EOF"
#cat << \EOF
exit 0
禁用了参数替换后,将允许输出文本本身。产生脚本甚至是程序代码就是这种用法的用途之一。
Example 17-8 一个产生另外一个脚本的脚本
#!/bin/bash
#
OUTFILE=generated.sh # 所产生文件的名字.
# 'Here document 包含了需要产生的脚本的代码.
(
cat <<'EOF'
#!/bin/bash
echo "This is a generated shell script."
# Note that since we are inside a subshell,
#+ we can't access variables in the "outside" script.
echo "Generated file will be named: $OUTFILE"
# Above line will not work as normally expected
#+ because parameter expansion has been disabled.
# Instead, the result is literal output.
a=7
b=3
let "c = $a * $b"
echo "c = $c"
exit 0
EOF
) > $OUTFILE
if [ -f "$OUTFILE" ];then
chmod 755 $OUTFILE
else
echo "Problem in creating file: \"$OUTFILE\""
fi
#这个方法也用来产生 C 程序代码, Perl 程序代码, Python 程序代码, makefile,和其他的一些类似的代码。
exit 0
也可以将 here document 的输出保存到变量中。
[root@localhost shell]# variable=$(cat << EOF
> This variable
> runs over multiple lines.
> EOF
> )
[root@localhost shell]# echo "$variable"
This variable
runs over multiple lines.
同一脚本中的函数也可以接受 here document 的输出作为自身的参数。
Example 17-9 Here document与函数
#!/bin/bash
#
GetPersonalData(){
read firstname
read lastname
read address
read city
read state
read zipcode
} #这个函数无疑的看起来就一个交互函数, 但是...
#给上边的函数提供输入
GetPersonalData << EOF
Bozo
Bozeman
2726 Nondescript Dr.
Baltimore
MD
21226
EOF
echo
echo "$firstname $lastname"
echo "$address"
echo "$city, $state $zipcode"
echo
exit 0
也可以这么使用: 做一个假命令来从一个 here document 中接收输出。这么做事实上就是创建了 一个"匿名"的 here document。
Example 17-10 匿名 here document
#!/bin/bash
#
: << EOF
${HOSTNAME?}${USER?}${MAIL?} #如果其中一个变量没被设置, 那么就打印错误信息.
EOF
exit 0
Example 17-11 注释掉一段代码块
#!/bin/bash
#
: << EOF
echo "This line will not echo."
This is a comment line missing the "#" prefix.
This is another comment line missing the "#" prefix.
&*@!!++=
The above line will cause no error message,
because the Bash interpreter will ignore it.
EOF
echo "Exit value of above \"EOF\" is $?"
#上边的这种技术当然也可以用来注释掉一段正在使用的代码, 如果你有某些特定调试要求的话.
#这将比对每行都敲入"#"来得方便的多,而且如果你想恢复的话, 还得将添加上的"#"删除掉.
: << EOF
for file in *
do
cat "$file"
done
EOF
exit 0
注意:关于这种小技巧的另一个应用就是能够产生自文档化(self-documenting)的脚本。
Example 17-12 一个自文档化的脚本
#!/bin/bash
#
DOC_REQUEST=70
if [ "$1" = "-h" -o "$1" = "--help" ];then
echo; echo "Usage: $0 [directory-name]"; echo
sed --silent -e '/DOCUMENTATIONXX$/,/^DOCUMENTATIONXX$/p' "$0" | sed -e '/DOCUMENTATIONXX$/d'
exit $DOC_REQUEST
fi
: << DOCUMENTATIONXX
以表格格式列出指定目录的统计信息。
---------------------------------------------------------------
命令行参数给出要列出的目录。
如果没有指定目录或指定的目录无法读取,则列出当前工作目录。
DOCUMENTATIONXX
if [ -z "$1" -o ! -r "$1" ];then
directory=
else
directory="$1"
fi
echo "Listing of "$directory":"; echo
(printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" ; ls -l "$directory" | sed 1d) | column -t
#column -t:判断输入行的列数来创建一个表。
#分隔符是使用在-s中指定的字符。如果没有指定分隔符,默认是空格
exit 0
使用cat脚本也能够完成相同的目的
DOC_REQUEST=70
if [ "$1" = "-h" -o "$1" = "--help" ];then
cat << DOCUMENTATIONXX
List the statistics of a specified directory in tabular format.
---------------------------------------------------------------
The command line parameter gives the directory to be listed.
If no directory specified or directory specified cannot be read,
then list the current working directory.
DOCUMENTATIONXX
exit $DOC_REQUEST
fi
参见Example A-27 可以了解更多关于自文档化脚本的好例子。
注意: Here document 创建临时文件,但是这些文件将在打开后被删除,并且不能够被任何其他进程所存取。
注意: 某些工具是不能工作在 here document 中的。
警告: 结束的 limit string,就是 here document 最后一行的 limit string,必须开始于第一个字符位置。它的前面不能够有任何前置的空白。而在这个 limit string 后边的空白也会引起异常问题。空白将会阻止 limit string 的识别。(注: 下边这个脚本由于结束 limit string 的问题, 造成脚本无法结束, 所有内容全部被打印出来。)
#!/bin/bash
#
echo "-----------------------------------------------------------------"
cat << EOF
echo "This is line 1 of the message inside the here document."
echo "This is line 2 of the message inside the here document."
echo "This is the final line of the message inside the here document."
EOF
echo "-----------------------------------------------------------------"
echo "Outside the here document."
exit 0
对于那些使用"here document"得非常复杂的任务,最好考虑使用 expect 脚本语言,这种语言就是为了达到向交互程序添加输入的目的而量身定做的。
17.1 Here Strings
here string 可以被认为是 here document 的一种定制形式。除了 COMMAND <<< $WORD 就什么都没有了,$WORD 将被扩展并且被送入 COMMAND 的 stdin 中。
Example 17-13 在一个文件的开头添加文本
#!/bin/bash
#
E_NOSUCHFILE=65
read -p "File: " file
if [ ! -e "$file" ];then
echo "File $file not found."
exit $E_NOSUCHFILE
fi
read -p "Title: " title
cat - $file <<< $title > $file.new
echo "Modified file is $file.new"
exit 0
#下边是'man bash'中的一段:
# Here Strings
# here document 的一种变形,形式如下:
# <<<word :word 被扩展并且提供到 command 的标准输入中.
为什么是在行首添加?
第十八章 休息时间
sleep