linux shell 下here document 文档

来源:互联网 发布:重启网卡 linux 编辑:程序博客网 时间:2024/05/18 06:40
 http://techbbs.zol.com.cn/1/85_788.html
linux shell 下here document 文档

一个here document就是一段带有特殊目的的代码段. 它使用I/O重定向的形式将一个命令序列传递到一个交互程序或者命令中, 比如ftp, cat, 或者ex文本编辑器.

  1 COMMAND <<INPUTCOMESFROMHERE pre=""   3 inputcomesfromhere<=""   2 ...="">

limit string用来界定命令序列的范围(译者注: 两个相同的limit string之间就是命令序列). 特殊符号<<用来标识limit string. 这个符号的作用就是将文件的输出重定向到程序或命令的stdin中. 与interactive-program < command-file很相似, 其中command-file包含:

  1 command #1  2 command #2  3 ...

here document看上去是下面这个样子:

  1 #!/bin/bash  2 interactive-program <<LIMITSTRING pre=""   6 limitstring<=""   5 ...="" #2=""   4 command="" #1=""   3 command="">

选择一个名字非常诡异limit string能够有效的避免命令列表与limit string重名的问题.

注意, 某些情况下, 把here document用在非交互工具或命令中, 也会取得非常好的效果, 比如, wall.


例子 17-1. 广播: 将消息发送给每个登陆的用户

  1 #!/bin/bash  2   3 wall <<ZZZ23ENDOFMESSAGEZZZ23 pre="" 0<="" 15 exit="" 14 ="" 而且这种做法是一次性的.="" 方便但是不卫生)的做法,="" 只是一种?小吃店?(译者注:="" 13 #+="" 将消息模版嵌入到脚本中="" 12 #  然而,="" 

对于某些看上去不太可能的工具, 比如vi, 也能够使用here document.


例子 17-2. 虚拟文件: 创建一个2行的虚拟文件

  1 #!/bin/bash  2   3 # 用非交互的方式来使用'vi'编辑一个文件.   4 # 模仿'sed'.  5   6 E_BADARGS=65  7   8 if [ -z "$1" ]  9 then 10   echo "Usage: `basename $0` filename" 11   exit $E_BADARGS 12 fi 13  14 TARGETFILE=$1 15  16 # 在文件中插入两行, 然后保存.  17 #--------Begin here document-----------# 18 vi $TARGETFILE <<X23LIMITSTRINGX23 the="" 就行, 28 #+ 事实上它是键;.  29  30 #  Bram Moolenaar指出这种方法不能使用在'vim'上, (译者注: Bram Moolenaar是vim作者) 31 #+ 因为可能会存在终端相互影响的问题.  32  33 exit 0

上边的脚本也可以不用vi而改用ex来实现, here document包含ex命令列表的形式足以形成自己的类别了, 称为ex script.

  1 #!/bin/bash  2 #  把所有后缀为".txt"文件  3 #+ 中的"Smith"都替换成"Jones".   4   5 ORIGINAL=Smith  6 REPLACEMENT=Jones  7   8 for word in $(fgrep -l $ORIGINAL *.txt)  9 do 10   # ------------------------------------- 11   ex $word <<EOF pre="" 18 done<="" -------------------------------------="" 17   #="" :wq是保存并退出的意思.="" 16   #="" 与vi和vim的基本命令相同)="" (译者注:="" :%s是?ex?的替换命令.="" 15   #="" 14 eof="" 13   :wq="" g="" $replacement="" $original="" 12   :%s="">

"ex script"相似的是cat script.


例子 17-3. 使用cat的多行消息

  1 #!/bin/bash  2   3 #  'echo'对于打印单行消息来说是非常好用的,   4 #+  但是在打印消息块时可能就有点问题了.   5 #   'cat' here document可以解决这个限制.   6   7 cat < $Newfile <<END-OF-MESSAGE pre="" the="" 2="" of="" 1="" line="" is="" last="" 4="" 3="" message.="" <="" 除非它们被转义.="" 文本中可能不允许包含双引号,="" 然而,="" 36 #="" 35 -------------------------------------?="" 34 this="" 33 this="" 32 this="" 31 this="" 30 this="" ?-------------------------------------="" 29 echo="" 指出下边代码也能够达到相同目的.="" s.c.="" 28 #="" 27 ="" 0?.="" 因为上边有?exit="" 下边的代码不会运行,="" 26 #="" 25 #--------------------------------------------="" 24 ="" 23 ="" 0="" 22 exit="" 21 ="" 而不是stdout.="" 那么就会把输出写到文件$newfile中,="" 20 #+="" ^^^^^^^^^^="" 19 #+      ="">

-选项用来标记here document的limit string (<<-LimitString), 可以抑制输出时前边的tab(不是空格). 这么做可以增加一个脚本的可读性.


例子 17-4. 带有抑制tab功能的多行消息

  1 #!/bin/bash  2 # 与之前的例子相同, 但是...   3   4 #  - 选项对于here docutment来说,   5 #+ <<-可以抑制文档体前边的tab,   6 #+ 而*不*是空格.   7   8 cat <<-ENDOFMESSAGE  9 This is line 1 of the message. 10 This is line 2 of the message. 11 This is line 3 of the message. 12 This is line 4 of the message. 13 This is the last line of the message. 14 ENDOFMESSAGE 15 # 脚本在输出的时候左边将被刷掉.  16 # 就是说每行前边的tab将不会显示.  17  18 # 上边5行"消息"的前边都是tab, 而不是空格.  19 # 空格是不受<<-影响的.  20  21 # 注意, 这个选项对于*嵌在*中间的tab没作用.  22  23 exit 0

here document支持参数和命令替换. 所以也可以给here document的消息体传递不同的参数, 这样相应的也会修改输出.


例子 17-5. 使用参数替换的here document

  1 #!/bin/bash  2 # 一个使用'cat'命令的here document, 使用了参数替换.   3   4 # 不传命令行参数给它,   ./scriptname  5 # 传一个命令行参数给它,   ./scriptname Mortimer  6 # 传一个包含2个单词(用引号括起来)的命令行参数给它,   7 #                           ./scriptname "Mortimer Jones"  8   9 CMDLINEparem=1     #  所期望的最少的命令行参数个数.  10  11 if [ $# -ge $CMDLINEparem ] 12 then 13   NAME=$1          #  如果命令行参数超过1个,  14                    #+ 那么就只取第一个参数.  15 else 16   NAME="John Doe"  #  默认情况下, 如果没有命令行参数的话.  17 fi   18  19 RESPONDENT="the author of this fine script"   20    21  22 cat <<ENDOFMESSAGE pre="" 0<="" the="" to="" 26 ="" (译者注:="" 23 ="" 34 exit="" 尽量保持代码的原样)="" 这就是为什么不翻译那行注释的原因,="" 33 #="" 而上边那行?注释?当然也会打印到输出.="" 32 #="" 注意上边的空行也打印输出,="" 31 #="" 30 ="" 29 endofmessage="" 28 ="" (why?).="" output="" in="" up="" shows="" comment="" this="" 27 #="" $respondent.="" from="" $name,="" you,="" 25 greetings="" $name.="" there,="" 24 hello,="">

这是一个非常有用的脚本, 其中使用了包含参数替换的here document.


例子 17-6. 上传一个文件对到"Sunsite"的incoming目录

  1 #!/bin/bash  2 # upload.sh  3   4 #  上传这一对文件(Filename.lsm, Filename.tar.gz)  5 #+ 到Sunsite/UNC (ibiblio.org)的incoming目录.   6 #  Filename.tar.gz是自身的tar包.   7 #  Filename.lsm是描述文件.   8 #  Sunsite需要"lsm"文件, 否则就拒绝上传.   9  10  11 E_ARGERROR=65 12  13 if [ -z "$1" ] 14 then 15   echo "Usage: `basename $0` Filename-to-upload" 16   exit $E_ARGERROR 17 fi   18  19  20 Filename=`basename $1`           # 从文件名中去掉目录字符串.  21  22 Server="ibiblio.org" 23 Directory="/incoming/Linux" 24 #  在这里也不一定非得将上边的参数写死在这个脚本中,  25 #+ 可以使用命令行参数的方法来替换.  26  27 Password="your.e-mail.address"   # 可以修改成相匹配的密码.  28  29 ftp -n $Server <<END-OF-SESSION pre="" 0<="" 41 exit="" 40 ="" 39 end-of-session="" 38 bye="" ?$filename.tar.gz?="" 37 put="" ?$filename.lsm?="" 36 put="" $directory="" 35 cd="" 响铃.="" 在每个文件传输后,="" #="" 34 bell                            ="" 33 binary="" ?$password?="" anonymous="" 32 user="" 31 ="" -n选项禁用自动登录.="" 30 #="">

在here document的开头, 引用或转义"limit string", 会使得here document消息体中的参数替换被禁用.


例子 17-7. 关闭参数替换

  1 #!/bin/bash  2 #  一个使用'cat'的here document, 但是禁用了参数替换.   3   4 NAME="John Doe"  5 RESPONDENT="the author of this fine script"    6   7 cat <<'Endofmessage'  8   9 Hello, there, $NAME. 10 Greetings to you, $NAME, from $RESPONDENT. 11  12 Endofmessage 13  14 #  如果"limit string"被引用或转义的话, 那么就禁用了参数替换.  15 #  下边的两种方式具有相同的效果.  16 #  cat <<"Endofmessage" 17 #  cat <<\Endofmessage 18  19 exit 0

禁用了参数替换后, 将允许输出文本本身(译者注: 就是未转义的原文). 如果你想产生脚本甚至是程序代码的话, 那么可以使用这种办法.


例子 17-8. 生成另外一个脚本的脚本

  1 #!/bin/bash  2 # generate-script.sh  3 # 这个脚本的诞生基于Albert Reiner的一个主意.   4   5 OUTFILE=generated.sh         # 所产生文件的名字.   6   7   8 # -----------------------------------------------------------  9 # 'Here document包含了需要产生的脚本的代码.  10 ( 11 cat <<'EOF' 12 #!/bin/bash 13  14 echo "This is a generated shell script." 15 #  Note that since we are inside a subshell, 16 #+ we can't access variables in the "outside" script. 17  18 echo "Generated file will be named: $OUTFILE" 19 #  Above line will not work as normally expected 20 #+ because paremeter expansion has been disabled. 21 #  Instead, the result is literal output. 22  23 a=7 24 b=3 25  26 let "c = $a * $b" 27 echo "c = $c" 28  29 exit 0 30 EOF 31 ) > $OUTFILE 32 # ----------------------------------------------------------- 33  34 #  将'limit string'引用起来将会阻止上边 35 #+ here document消息体中的变量扩展.  36 #  这会使得输出文件中的内容保持here document消息体中的原文.  37  38 if [ -f "$OUTFILE" ] 39 then 40   chmod 755 $OUTFILE 41   # 让所产生的文件具有可执行权限.  42 else 43   echo "Problem in creating file: \"$OUTFILE\"" 44 fi 45  46 #  这个方法也可以用来产生 47 #+ C程序代码, Perl程序代码, Python程序代码, makefile,  48 #+ 和其他的一些类似的代码.  49 #  (译者注: 中间一段没译的注释将会被here document打印出来) 50 exit 0

也可以将here document的输出保存到变量中.

  1 variable=$(cat <<SETVAR pre="" ?$variable?<=""   6 echo=""   5 =""   4 setvar)="" lines.="" multiple="" over=""   3 runs="" variable=""   2 this="">

A here document can supply input to a function in the same script.


例子 17-9. Here document与函数

  1 #!/bin/bash  2 # here-function.sh  3   4 GetPersonalData ()  5 {  6   read firstname  7   read lastname  8   read address  9   read city  10   read state  11   read zipcode 12 } # 这个函数看起来就是一个交互函数, 但是...  13  14  15 # 给上边的函数提供输入. 16 GetPersonalData <<RECORD001 pre="" 0<="" 29 echo="" 24 ="" 31 ="" 32 exit="" 30 echo="" $zipcode?="" $state="" ?$city,="" ?$address?="" 28 echo="" $lastname?="" ?$firstname="" 27 echo="" 26 echo="" 25 ="" 23 record001="" 22 21226="" 21 md="" 20 baltimore="" dr.="" nondescript="" 19 2726="" 18 bozeman="" 17 bozo="">

也可以这么使用:(冒号), 做一个假命令来从一个here document中接收输出. 这么做事实上就是创建了一个"匿名"的here document.


例子 17-10. "匿名"的here Document

  1 #!/bin/bash  2   3 : <<TESTVARIABLES pre="" 0<=""   7 exit=""   6 =""   5 testvariables="" 那么就打印错误信息.="" 如果其中某个变量没被设置,=""   4 ${hostname?}${user?}${mail?}  #="">

Tip

上边所示技术的一种变化, 可以用来"注释"掉代码块.


例子 17-11. 注释掉一段代码块

  1 #!/bin/bash  2 # commentblock.sh  3   4 : <<COMMENTBLOCK pre="" 0<="" the="" 26 ="" of="" line="" is="" cat="" 16 ="" 0="" in="" comment="" #="" 30 exit="" 29 ="" 28 debugxxx="" 27 done="" ?$file?="" 25 do="" *="" file="" 24 for="" <

Tip

关于这种小技巧的另一个应用就是能够产生"自文档化(self-documenting)"的脚本.


例子 17-12. 一个自文档化(self-documenting)的脚本

  1 #!/bin/bash  2 # self-document.sh: 自文档化(self-documenting)的脚本  3 # 修改于"colm.sh".  4   5 DOC_REQUEST=70  6   7 if [ "$1" = "-h"  -o "$1" = "--help" ]     # 请求帮助.   8 then  9   echo; echo "Usage: $0 [directory-name]"; echo 10   sed --silent -e '/DOCUMENTATIONXX$/,/^DOCUMENTATIONXX$/p' "$0" | 11   sed -e '/DOCUMENTATIONXX$/d'; exit $DOC_REQUEST; fi 12  13  14 : <<DOCUMENTATIONXX size="" pre="" 0<="" or="" the="" to="" of="" line="" 34 exit="" in="" 30 echo="" 29 ="" 22 ="" no="" a="" 33 ="" -t="" column="" 1d)="" sed="" |="" ?$directory?="" -l="" ls="" 32 ;="" \="" prog-name\n?="" hh:mm="" day="" month="" group="" owner="" links="" ?permissions="" 31 (printf="" echo="" ?$directory?:?;="" ?listing="" 28 fi  ="" 27   directory="$1" 26 else="" 25   directory="." 24 then="" ]="" -r="" !="" -o="" ?$1?="" -z="" [="" 23 if="" 21 documentationxx="" 20 ="" directory.="" working="" current="" list="" 19 then="" read,="" cannot="" 18 if="" listed.="" be="" gives="" paremeter="" command="" 17 the="" 16 ---------------------------------------------------------------="" format.="" tabular="" directory="" specified="" statistics="" 15 list="">

使用cat脚本也能够完成相同的目的.

  1 DOC_REQUEST=70  2   3 if [ "$1" = "-h"  -o "$1" = "--help" ]     # 请求帮助.   4 then                                       # 使用"cat脚本" . . .   5   cat <<DOCUMENTATIONXX pre="" or="" the="" to="" of="" line="" in="" no="" a="" directory.="" working="" current="" list="" read,="" cannot="" listed.="" be="" gives="" paremeter="" command="" format.="" tabular="" directory="" specified="" statistics="" 14 fi<="" $doc_request="" 13 exit="" 12 documentationxx="" 11 ="" 10 then=""   9 if=""   8 the=""   7 ---------------------------------------------------------------=""   6 list="">

请参考例子 A-28可以看到更多关于"自文档化"脚本的好例子.

Note

Here document创建临时文件, 但是这些文件将在打开后被删除, 并且不能够被任何其他进程所访问.

bash$ bash -c 'lsof -a -p $$ -d0' << EOF> EOFlsof    1213 bozo    0r   REG    3,5    0 30386 /tmp/t1213-0-sh (deleted)      

Caution

某些工具是不能放入here document中运行的.

Warning

结尾的limit string, 就是here document最后一行的limit string, 必须从第一个字符开始. 它的前面不能够有任何前置的空白. 而在这个limit string后边的空白也会引起异常. 空白将会阻止limit string的识别. (译者注: 下边这个脚本由于结束limit string的问题, 造成脚本无法结束, 所有内容全部被打印出来, 所以注释就不译了, 保持这个例子脚本的原样.)

  1 #!/bin/bash  2   3 echo "----------------------------------------------------------------------"  4   5 cat <<LIMITSTRING comments="" pre="" an="" the="" here="" 2="" of="" 1="" line="" is="" 16 ="" 0="" this=""   6 echo="" 13 ="" not="" will="" ?this="" 20 ="" 11 ="" command.<="" ?exit?="" follows="" echo.?  #="" better="" had="" 21 echo="" 19 exit="" 18 ="" ?outside="" 17 echo="" echo.="" should="" and="" 15 #+="" document?,="" ?here="" outside="" are="" 14 #  these="" ?----------------------------------------------------------------------?="" 12 echo="" expected.="" as="" behave="" script="" error!="" string.="" limit="" 10 #^^^^indented="" limitstring=""   9     ="" final=""   8 echo=""   7 echo="" document.?="" inside="" message="">

对于那些使用"here document", 并且非常复杂的任务, 最好考虑使用expect脚本语言, 这种语言就是为了达到向交互程序添加输入的目的而量身定做的.