HereDoc 全名叫作 Here Document
,中文能够称之为 嵌入文档
。对它的叫法实际上不少,here文档,hereis,here-string 等等都是它。html
嵌入文档是 Shell I/O 重定向功能的一种替代。咱们已经知道 Shell 的 I/O 重定向是一种文件句柄的传输。例如:程序员
COMMAND 1>/tmp/1.lst 2>&1
复制代码
将命令的标准输出为一个文件,同时也将错误输出到同一个文件中。shell
而下例:bash
cat /etc/passwd | grep '^admin:'
复制代码
则经过管道将前一命令的输出当作后一命令的标准输入。app
Here Document 就是标准输入的一种替代品。它使得脚本开发人员能够没必要使用临时文件来构建输入信息,而是直接就地生产出一个文件并用做命令的标准输入。通常来讲其格式是这样的:ssh
COMMAND <<IDENT
...
IDENT
复制代码
在这里,<<
是引导标记,IDENT
是一个限定符,由开发人员自行选定,两个 IDENT
限定符之间的内容将被当作是一个文件并用做 COMMAND 的标准输入。例如echo大段文本时,咱们可使用 cat file
的语法:socket
cat <<EOF
SOME TEXT
HERE
!
EOF
复制代码
此例中,咱们使用 EOF
短语做为限定符。编辑器
Here Document 是能够嵌套的,只要双层分别使用不一样的
IDENT
限定符且保证正确的嵌套关系便可:idessh user@host <<EOT ls -la --color cat <<EOF from a remote host EOF [ -f /tmp/1.tmp ] && rm -f /tmp/1.tmp EOT 复制代码
看起来有点怪?其实还好啦。函数
实际上,限定符能够取得很是长,只要是字母开头且只包含字母和数字(一般,下划线和短横线也是有效的,不过根据 bash 的版本不一样、宿主实现的不一样,可能会有必定的出入)便可。
abs
中有一个例子,节选以下:
wall <<zzz23EndOfMessagezzz23
fdjsldj
fdsjlfdsjfdls
zzz23EndOfMessagezzz23
复制代码
这是正确有效的,不过这个其实更怪一些。
在 bash, ksh 和 zsh 中,还可使用 Here String:
$ tr a-z A-Z <<<"Yes it is a string"
YES IT IS A STRING
复制代码
此时也可使用变量:
$ tr a-z A-Z <<<"$var"
复制代码
有没有可能将HEREDOC存储为一个文件?显然是能够:
cat << EOF > /tmp/yourfilehere
These contents will be written to the file.
This line is indented.
EOF
复制代码
你能够注意到这种写法不一样于常常性的写法:
cat >/tmp/1<<EOF
s
EOF
复制代码
但二者都是对的。
但当须要 root 权限时,'>' 并不能很好地工做,此时须要 sudo tee
上场:
cat <<EOF | sudo tee /opt/1.log
s
EOF
复制代码
标准输出的重定向,还能够经过子 shell 的方式来构造:
(echo '# BEGIN OF FILE | FROM'
cat <<- _EOF_
LogFile /var/log/clamd.log
LogTime yes
DatabaseDirectory /var/lib/clamav
LocalSocket /tmp/clamd.socket
TCPAddr 127.0.0.1
SelfCheck 1020
ScanPDF yes
_EOF_
echo '# END OF FILE'
) > /etc/clamd.conf
复制代码
这个例子只是一个示意,由于实际上该例子用不着那么麻烦,单个 cat HEREDOC
足够达到目的了,也不须要开子 shell 那么重。
cat <<EOF
的少见的变形let() {
res=$(cat)
}
let <<'EOF'
...
EOF
复制代码
元芳,你怎么看?
还能够写做这样:
let() {
eval "$1"'=$(cat)'
}
let res<<'EOF'
...
EOF
复制代码
固然,其实它和单行指令是等效的:
{ res=$(cat); } <<'EOF'
...
EOF
复制代码
{}
是语句块,而不是子shell,于是更省力。根据具体状况来使用它,有时候你但愿子 shell 的变量无污染的效果,或者别的期待,那你就使用 ()
。
variable=$(cat <<SETVAR
This variable
runs over multiple lines.
SETVAR
)
echo "$variable"
复制代码
示例展现了在 $()
语法中能够随意地嵌入 HEREDOC。
若是你只是须要为变量用 HEREDOC 赋值,read var
一般是更好的主意:
read i <<!
Hi
!
echo $i # Hi
复制代码
GetPersonalData () {
read firstname
read lastname
read address
read city
read state
read zipcode
} # This certainly appears to be an interactive function, but . . .
# Supply input to the above function.
GetPersonalData <<RECORD001
Bozo
Bozeman
2726 Nondescript Dr.
Bozeman
MT
21226
RECORD001
echo
echo "$firstname $lastname"
echo "$address"
echo "$city, $state $zipcode"
echo
复制代码
能够看到,只要函数可以接收标准输入,那就能够将 HEREDOC 套用上去。
#!/bin/bash
# filename: aa.sh
: <<TESTVARIABLES
${UX?}, ${HOSTNAME?} | ${USER?} | ${MAIL?} # Print error message if one of the variables not set.
TESTVARIABLES
exit $?
复制代码
这个示例中,若是变量没有被设置,则会产生一条错误消息,而该 HEREDOC 的用处其实是用来展开要确认的变量,HEREDOC产生的结果做为 :
的标准输入,实际上被忽略了,最后只有 HEREDOC 展开的状态码被返回,用以确认是否是有某个变量还没有被设置:
$ ./aa; echo $?
./aa: line 3: UX: parameter null or not set
1
复制代码
因为 UX 变量缺失,所以调用的结果是一行错误输出,以及调用的退出码为 1,也就是 false 的意思。
:
是true
命令的同义词。就好像.
是source
命令的同义词同样。
除了用来一次性检测一大批变量有否被赋值的效果以外,匿名的 HEREDOC 也经常被用做大段的注释。
cat >/dev/null<<COMMENT
...
COMMENT
: <<COMMENT
...
COMMENT
复制代码
这些写法均可以,看你的我的喜爱。Bash 程序员的通常风格是能省键盘就省键盘。但有时候他们也喜欢能炫就炫:
:<<-!
____ _ ____ _
/ ___| ___ ___ __| | / ___| ___ ___ __| |
| | _ / _ \ / _ \ / _` | | | _ / _ \ / _ \ / _` |
| |_| | (_) | (_) | (_| | | |_| | (_) | (_) | (_| |
\____|\___/ \___/ \__,_| \____|\___/ \___/ \__,_|
____ _ _
/ ___|| |_ _ _ __| |_ _
\___ \| __| | | |/ _` | | | |
___) | |_| |_| | (_| | |_| |
|____/ \__|\__,_|\__,_|\__, |
|___/
!
复制代码
当咱们须要读一个csv文件时,咱们会用到 while read 结构。
将 csv 文件改成 HEREDOC:
while read pass port user ip files directs; do
sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directs
done <<____HERE
PASS PORT USER IP FILES DIRECTS
. . . . . .
. . . . . .
. . . . . .
PASS PORT USER IP FILES DIRECTS
____HERE
复制代码
因为不一样格式的 CSV 的处理并不是本文的主题,所以这里再也不展开讨论具体状况了。
对于 while … done
来讲,标准输入的重定向应该写在 done
以后。一样的,for … do … done
也是如此,until … done
也是如此。
while
while [ "$name" != Smith ] # Why is variable $name in quotes?
do
read name # Reads from $Filename, rather than stdin.
echo $name
let "count += 1"
done <"$Filename" # Redirects stdin to file $Filename.
复制代码
until
until [ "$name" = Smith ] # Change != to =.
do
read name # Reads from $Filename, rather than stdin.
echo $name
done <"$Filename" # Redirects stdin to file $Filename.
复制代码
for
for name in `seq $line_count` # Recall that "seq" prints sequence of numbers.
# while [ "$name" != Smith ] -- more complicated than a "while" loop --
do
read name # Reads from $Filename, rather than stdin.
echo $name
if [ "$name" = Smith ] # Need all this extra baggage here.
then
break
fi
done <"$Filename" # Redirects stdin to file $Filename.
复制代码
<<-IDENT
是新的语法,市面上的 Bash 均已支持这种写法。它的特殊之处就在于 HEREDOC 正文内容中的全部前缀 TAB 字符都会被删除。
这种语法每每被用在脚本的 if 分支,case 分支或者其余的代码有缩进的场所,这样 HEREDOC 的结束标记没必要非要在新的一行的开始之处不可。一方面视觉效果上 HEREDOC 跟随了所在代码块的缩进层次,可读性被提高,另外一方面对于许多懒惰的编辑器来讲,不会发生面对 HEREDOC 时语法分析出错、代码折叠的区块判断不正确的状况。
function a () {
if ((DEBUG)); then
cat <<-EOF
French
American
- Uses UTF-8
Helvetica
- Uses RTL
EOF
fi
}
复制代码
如上的脚本段落中,结束标记EOF能够没必要处于行首第一个字母,只要EOF以及其上的HEREDOC正文都以TAB字符进行缩进就能够了。
注意若是TAB字符缩进在这里没有被严格遵照的话,Bash解释器可能会报出错误。
像在正文中的 - Uses UTF-8
除开行首的 TAB字符缩进以外,还包含两个空格字符,这不会受到 <<-
的影响而被删除。
通常状况下,HEREDOC 中的 ${VAR}
,$(pwd)
,$((1+1))
等语句会被展开,当你想要编写 ssh 指令时,可能你但愿的是不要展开 $
标记。
这能够用 <<"EOF"
来实现。
只须要在 IDENT
标记上加上引号包围就能够达到效果,结束标记则无需引号。
cat <<"EOF"
Command is:
$ lookup fantasy
EOF
# 若是不想展开,则你须要对 $ 字符进行转义
cat <<EOF
\$ lookup fantasy
EOF
复制代码
这个例子中,请注意单个的 $
字符实际上是不会展开也不会报错的,因此咱们只是为了编写一个示例而已。
引号包围呢,单引号、双引号均可以,都会一样地生效。
甚至,你可使用转义语法,也就是说:
cat <<\EOF
Command is:
$ lookup fantasy
EOF
复制代码
也能禁止参数展开。
上面两个新的语法特性,是能够被同时组合和运用的:
cat <<-"EOF"
Command is:
$ lookup fantasy
EOF
复制代码
虽然你可能根本不须要遇到这样的情形。