脚本之内嵌文档
http://blog.csdn.net/ysdaniel/article/details/6899861
注意这里HERE还有一个特殊的用法 :
就是在HERE前面加上 - 或者给HERE加上' ',加上- 表明下述文字段所有TAB键将全部忽略,加上' '表明以下凡是变量定义用到了' ',将会使变量呈现所见即所得的形式,也即关闭变量替换;如果加上的是" "双引号,则会进行变量替换。
======================
http://my.oschina.net/u/1032146/blog/146941
linux shell 的here document 用法 (cat << EOF)
什么是Here Document
Here Document 是在Linux Shell 中的一种特殊的重定向方式,它的基本的形式如下
cmd << delimiter
Here Document Content
delimiter
它的作用就是将两个 delimiter 之间的内容(Here Document Content 部分) 传递给cmd 作为输入参数。
比如在终端中输入cat << EOF
,系统会提示继续进行输入,输入多行信息再输入EOF,中间输入的信息将会显示在屏幕上。如下:
fish@mangos:~$ cat << EOF
> First Line
> Second Line
> Third Line EOF
> EOF
First Line
Second Line
Third Line EOF
注: >
这个符号是终端产生的提示输入信息的标识符
这里要注意几点
- EOF 只是一个标识而已,可以替换成任意的合法字符
- 作为结尾的delimiter一定要顶格写,前面不能有任何字符
- 作为结尾的delimiter后面也不能有任何的字符(包括空格)
- 作为起始的delimiter前后的空格会被省略掉
Here Document 不仅可以在终端上使用,在shell 文件中也可以使用,例如下面的here.sh 文件
cat << EOF > output.sh
echo "hello"
echo "world"
EOF
使用 sh here.sh
运行这个脚本文件,会得到output.sh 这个新文件,里面的内容如下
echo "hello"
echo "world"
Here Document的变形
delimiter 与变量
在Here Document 的内容中,不仅可以包括普通的字符,还可以在里面使用变量,例如将上面的here.sh 改为
cat << EOF > output.sh
echo "This is output"
echo $1
EOF
使用sh here.sh HereDocument
运行脚本得到output.sh的内容
echo "This is output"
echo HereDocument
在这里 $1
被展开成为了脚本的参数 HereDocument
但是有时候不想展开这个变量怎么办呢,可以通过在起始的 delimiter的前后添加 "
来实现,例如将上面的here.sh 改为
cat << "EOF" > output.sh #注意引号
echo "hello"
echo "world"
EOF
得到的output.sh 的内容为
echo "This is output"
echo $1
« 变为 «-
Here Document 还有一个用法就是将 '«' 变为 '«-'。 使用 <<-
的唯一变化就是Here Document 的内容部分每行前面的 tab (制表符)将会被删除掉,这种用法是为了编写Here Document的时候可以将内容部分进行缩进,方便阅读代码。
参考链接
Wiki: Here Document
Learn Linux, 101: Streams, pipes, and redirects
=========================
http://www.linuxfly.org/post/146/
[原]使用cat命令和EOF标识输出多行文件

一、cat和EOF
cat命令是linux下的一个文本输出命令,通常是用于观看某个文件的内容的;
EOF是“end of file”,表示文本结束符。
结合这两个标识,即可避免使用多行echo命令的方式,并实现多行输出的结果。
二、使用
看例子是最快的熟悉方法:
> #!/bin/bash
> #you Shell script writes here.
> EOF
结果:
#!/bin/bash
#you Shell script writes here.
可以看到,test.sh的内容就是cat生成的内容。
三、其他写法
1、追加文件
2、换一种写法
3、EOF只是标识,不是固定的
> sdlkfjksl
> sdkjflk
> asdlfj
> HHH
这里的“HHH”就代替了“EOF”的功能。结果是相同的。
sdlkfjksl
sdkjflk
asdlfj
4、非脚本中
如果不是在脚本中,我们可以用Ctrl-D输出EOF的标识
skldjfklj
sdkfjkl
kljkljklj
kljlk
Ctrl-D
结果:
skldjfklj
sdkfjkl
kljkljklj
kljlk
※关于“>”、“>>”、“<”、“<<”等的意思,请自行查看bash的介绍。
===============================================
http://my.oschina.net/moooofly/blog/341769
原创】shell 操作之 read、cat 和 here document
目录[-]
本文主要学习总结一下三方面问题:
- 通过 read 进行行读
- here document
- here document 的应用
【read】
在 linux 下执行 man read 能看到如下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...] One line is read from the standard input, or from the file descriptor fd supplied as an argument to the -u option, and the first word is assigned to the first name, the second word to the second name, and so on, with leftover words and their intervening separators assigned to the last name. If there are fewer words read from the input stream than names, the remaining names are assigned empty values. The characters in IFS are used to split the line into words. The backslash character (\) may be used to remove any special meaning for the next character read and for line continuation. Options, if supplied, have the following meanings: 从标准输入上读取一行数据,或者从通过 -u 选项指定的文件描述符 fd 上读取,并且按照顺序,将读取的第一个 word 赋值给第一个 name ,第二个 word 赋值给 name ,以此类推,对于剩余的 words 和 word 之间的分隔符都被赋值到最后一个 name 上。如果从输入流上读取的 word 的数量少于给出 的 name 数量,则多出来的 names 将被自动赋值为空值。IFS 中所包含的字符用于将整行字符拆分成单独的 word (换句话说也就是将 IFS 中包含的字符 从行数据中去除,IFS 中默认包含的字符为空格、制表符和回车)。反斜线字符(\)可以用于移除紧随其后读到的字符的任何特殊含义,还可用于行接续。 若提供了选项,则具有如下意义: -a aname The words are assigned to sequential indices of the array variable aname, starting at 0. aname is unset before any new values are assigned. Other name arguments are ignored. -d delim The first character of delim is used to terminate the input line, rather than newline. delim 的首字符被用于作为输入行数据的终止符,而不是换行符。 -e If the standard input is coming from a terminal, readline (see READLINE above) is used to obtain the line. Readline uses the current (or default, if line editing was not previously active) editing settings. -i text If readline is being used to read the line, text is placed into the editing buffer before editing begins. -n nchars read returns after reading nchars characters rather than waiting for a complete line of input, but honor a delimiter if fewer than nchars characters are read before the delimiter. -N nchars read returns after reading exactly nchars characters rather than waiting for a complete line of input, unless EOF is encountered or read times out. Delimiter characters encountered in the input are not treated specially and do not cause read to return until nchars characters are read . -p prompt Display prompt on standard error, without a trailing newline, before attempting to read any input. The prompt is displayed only if input is coming from a terminal. 在开始读取任何输入前,向标准出错上显示提示信息,并且不带尾部换行符。该提示信息仅在输入数据来自终端的时候才被显示。 -r Backslash does not act as an escape character. The backslash is considered to be part of the line. In particular, a backslash-newline pair may not be used as a line continuation. 反斜线不作为转义字符起作用。反斜线被当做行数据的一部分。 特别值得注意的是,反斜线-换行 组合将不能作为行接续来使用。 -s Silent mode. If input is coming from a terminal, characters are not echoed. 安静模式。如果输入来自终端,字符将不会被 echo 。 -t timeout Cause read to time out and return failure if a complete line of input is not read within timeout seconds. timeout may be a decimal number with a fractional portion following the decimal point. This option is only effective if read is reading input from a terminal, pipe, or other special file ; it has no effect when reading from regular files. If timeout is 0, read returns success if input is available on the specified file descriptor, failure otherwise. The exit status is greater than 128 if the timeout is exceeded. -u fd Read input from file descriptor fd. 从文件 fd 读取输入数据。 If no names are supplied, the line read is assigned to the variable REPLY. The return code is zero, unless end-of- file is encountered, read times out ( in which case the return code is greater than 128), or an invalid file descriptor is supplied as the argument to -u. 如果没有 name 变量被指定,所读取的行数据将被赋值给变量 REPLY 。除非遇到了文件结束符(EOF),或者发生读取超时(此时返回值将大于 128),或者 通过 -u 指定了无效的文件描述符,其他情况返回值均为 0 。 |
【read 测试】
测试文件如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
[root@Betty Shell] # vi file -module( unique_name_test ) . -compile(export_all). %% @spec (Nibble::integer()) -> char() %% @doc Returns the character code corresponding to Nibble. %% %% Nibble must be >=0 and =<16. hex_digit(0) -> $0; hex_digit(1) -> $1; hex_digit(2) -> $2; hex_digit(3) -> $3; hex_digit(4) -> $4; hex_digit(5) -> $5; hex_digit(6) -> $6; hex_digit(7) -> $7; hex_digit(8) -> $8; hex_digit(9) -> $9; hex_digit(10) -> $A; hex_digit(11) -> $B; hex_digit(12) -> $C; hex_digit(13) -> $D; hex_digit(14) -> $E; hex_digit(15) -> $F. |
测试一:读取文件的首行并赋值给变量
1
2
3
|
[root@Betty Shell] # read -r line < file [root@Betty Shell] # echo $line -module( unique_name_test ) . |
记住,read 命令会删除包含在 IFS 变量中出现的所有字符(这个说法似乎不够准确),IFS 的全称是 Internal Field Separator,Bash 根据 IFS 中定义的字符来分隔单词。在这里,read 命令读入的行被分隔成多个单词。默认情况下,IFS 包含空格,制表符和回车,这意味着开头和结尾的空格和制表符都会被删除。如果你想保留这些符号,可以通过设置 IFS 为空来完成:
1
2
3
|
[root@Betty Shell] # IFS= read -r line < file [root@Betty Shell] # echo $line -module( unique_name_test ) . |
测试二:依次读入文件每一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
[root@Betty Shell] # while read -r line; do > echo "test $line" ; > done < file test -module( unique_name_test ) . test test -compile(export_all). test test test %% @spec (Nibble::integer()) -> char() test %% @doc Returns the character code corresponding to Nibble. test %% test %% Nibble must be >=0 and =<16. test hex_digit(0) -> $0; test hex_digit(1) -> $1; test hex_digit(2) -> $2; test hex_digit(3) -> $3; test hex_digit(4) -> $4; test hex_digit(5) -> $5; test hex_digit(6) -> $6; test hex_digit(7) -> $7; test hex_digit(8) -> $8; test hex_digit(9) -> $9; test hex_digit(10) -> $A; test hex_digit(11) -> $B; test hex_digit(12) -> $C; test hex_digit(13) -> $D; test hex_digit(14) -> $E; test hex_digit(15) -> $F. test test [root@Betty Shell] # |
=== 我是火影终结的分隔线 ===
关于 read 命令遇到文件结尾返回一个正值的结论,之前我一直持怀疑态度。因为经常会遇到这样的用法:
1
|
while read -r line; do echo $line; done < file |
于是进行如下实验进行验证
1
2
3
4
5
6
7
8
9
10
11
|
[root@Betty workspace] # touch abc.txt [root@Betty workspace] # cat abc.txt [root@Betty workspace] # read -r line < abc.txt [root@Betty workspace] # echo $? 1 [root@Betty workspace] # echo "1" >> abc.txt [root@Betty workspace] # cat abc.txt 1 [root@Betty workspace] # read -r line < abc.txt [root@Betty workspace] # echo $? 0 |
最后再确认一下 while 的判定规则
1
2
3
4
|
while list; do list; done The while command continuously executes the do list as long as the last command in list returns an exit status of zero. The exit status of the while commands is the exit status of the last do list command executed, or zero if none was executed. |
=== 我是火影终结的分隔线 ===
记住,read 命令会删除首尾多余的空白字符,所以如果你想保留,请设置 IFS 为空值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
[root@Betty Shell] # while IFS= read -r line; do > echo "test $line" ; > done < file test -module( unique_name_test ) . test test -compile(export_all). test test test %% @spec (Nibble::integer()) -> char() test %% @doc Returns the character code corresponding to Nibble. test %% test %% Nibble must be >=0 and =<16. test hex_digit(0) -> $0; test hex_digit(1) -> $1; test hex_digit(2) -> $2; test hex_digit(3) -> $3; test hex_digit(4) -> $4; test hex_digit(5) -> $5; test hex_digit(6) -> $6; test hex_digit(7) -> $7; test hex_digit(8) -> $8; test hex_digit(9) -> $9; test hex_digit(10) -> $A; test hex_digit(11) -> $B; test hex_digit(12) -> $C; test hex_digit(13) -> $D; test hex_digit(14) -> $E; test hex_digit(15) -> $F. test test [root@Betty Shell] # |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
[root@Betty Shell] # cat file | while IFS= read -r line; do > echo "test $line" ; > done test -module( unique_name_test ) . test test -compile(export_all). test test test %% @spec (Nibble::integer()) -> char() test %% @doc Returns the character code corresponding to Nibble. test %% test %% Nibble must be >=0 and =<16. test hex_digit(0) -> $0; test hex_digit(1) -> $1; test hex_digit(2) -> $2; test hex_digit(3) -> $3; test hex_digit(4) -> $4; test hex_digit(5) -> $5; test hex_digit(6) -> $6; test hex_digit(7) -> $7; test hex_digit(8) -> $8; test hex_digit(9) -> $9; test hex_digit(10) -> $A; test hex_digit(11) -> $B; test hex_digit(12) -> $C; test hex_digit(13) -> $D; test hex_digit(14) -> $E; test hex_digit(15) -> $F. test test [root@Betty Shell] # |
测试三:读取文件首行前三个字段并赋值给变量
1
2
3
4
|
[root@Betty Shell] # head -1 file | while read -r field1 field2 field3 throwaway; do echo "filed1 = $field1";echo "field2 = $field2";echo "field3 = $field3"; done filed1 = -module( field2 = unique_name_test field3 = ) |
有时候,为了书写方便,可以简单地用 _ 来替换 throwaway 变量:
1
2
3
4
|
[root@Betty Shell] # head -1 file | while read -r field1 field2 field3 _; do echo "filed1 = $field1";echo "field2 = $field2";echo "field3 = $field3"; done filed1 = -module( field2 = unique_name_test field3 = ) |
【cat 与 <<】
- cat 命令是 linux 下的一个文本输出命令,通常是用于观看某个文件的内容的; 命令 cat >file 可以用于将键盘上的输入写到文件中。
- EOF 为 "end of file" 的缩写,从语义上代表文本的结束符。
- 通过 cat <<EOF 将两者结合使用(EOF 和 << 中间是否有空格没有关系),即可避免使用多行 echo 命令的方式,并实现多行输出的结果。原则上讲,此处的 EOF 可以使用任何其他字符替代。
测试 - 1
1
2
3
4
|
cat > test .cfg <<EOF log_facility=daemon pid_file=/var/run/nrpe.pid EOF |
测试 - 2
1
2
3
4
|
cat > test .cfg <<ABC log_facility=daemon pid_file=/var/run/nrpe.pid ABC |
测试 - 3
1
2
3
4
|
cat <<ABC > test .cfg log_facility=daemon pid_file=/var/run/nrpe.pid ABC |
【Here document】
- 有写书籍将 here document 翻译为内嵌文档。
- here document 的别名有 here-document 、heredoc 、hereis 、 here-string 或 here-script 。
- here document 原本指 file literal 或者 input stream literal ;后来也指 multiline string literals 。
- here document 会保留 text 中的 line break 和其他 whitespace (including indentation) 的含义。
- here document 起始于 Unix shell ,后在各种其他 shell 中被使用。
- here document 风格的字符串在很多高级语言中存在,尤其是 Perl 语言和其他受 Perl 影响的语言,如 PHP 和 Ruby 。
- 对于 here document 而言,无论指代的是文件还是字符串,一些语言都将其看做格式化字符串,并允许在其内部进行变量替换和命令替代。
- here document 的最通用语法起源于 Unix shell ,使用 "<< delimiter" 的形式(delimiter 通常为 EOF 或 END)标识多行字串的开始,之后新起一行包含相应的文本,最后以相同的 delimiter 独占一行标识多行字串的结束。这种语法形式是因为 here documents 主要用于 stream literal ,且 document 的内容被重定向到前面 command 的标准输入,即 here document 的语法模拟了输入重定向的语法,也就是 < 所表示的“从后续 command 的输出获取输入”。
- 其它语言通常使用了非常相似的语法,但是语法的细节和实际的功能可能非常的不同。
Unix shell 中的应用
In the following example, text is passed to the tr command using a here document. This could be in a shell file, or entered interactively at a prompt.
1
2
3
4
|
[root@Betty Shell] # tr a-z A-Z << END_TEXT > one two three > uno dos tres > END_TEXT |
1
2
|
ONE TWO THREE UNO DOS TRES |
在 << 后添加 - 符号的作用是可以忽略掉前置的 tab 。这将允许在 shell 命令行上直接对包含缩进的 here document 进行操作,而不用变更脚本的内容。
注意:要想在 shell 命令行上输入 TAB ,需要连续输入 CTRL-V 、TAB 才行。
1
2
3
4
|
[root@Betty Shell] # tr a-z A-Z <<- END_TEXT >(Ctrl-V + TAB)one two three >(Ctrl-V + TAB)uno dos tres >(Ctrl-V + TAB)END_TEXT |
1
2
|
ONE TWO THREE UNO DOS TRES |
补充测试:
1
2
3
4
5
|
[root@Betty Shell] # tr a-z A-Z << END_TEXT >(Ctrl-V + TAB)one two three >(Ctrl-V + TAB)uno dos tres >(Ctrl-V + TAB)END_TEXT > END_TEXT |
1
2
3
|
(Ctrl-V + TAB)ONE TWO THREE (Ctrl-V + TAB)UNO DOS TRES (Ctrl-V + TAB)END_TEXT |
在默认情况下,变量将被内插替换,包含在 `` 中的命令将被求值。
backtick 即传说中的反引号。
1
2
3
|
[root@Betty Shell] # cat << EOF > Working dir $PWD > EOF |
1
|
Working dir /root/workspace/CODE_TEST/Shell |
1
2
3
|
[root@Betty Shell] # cat << "EOF" > Working dir $PWD > EOF |
1
|
Working dir $PWD |
补充测试:
1
2
3
|
[root@Betty Shell] # cat << "E"OF Working dir $PWD EOF |
1
|
Working dir $PWD |
(后面还有对 here string 的介绍,此处略过)
man 手册上的说明
Here DocumentsThis type of redirection instructs the shell to read input from the current source until a line containing only delimiter (with no trailing blanks) is seen. All of the lines read up to that point are then used as the standard input for a command.
这种类型的重定向会使得 shell 从当前源读取输入,直到遇到仅包含 delimiter 的行(尾部没有任何空白符)。此时读取到的全部行将被作为 command 的标准输入。
The format of here-documents is:
<<[-]word
here-document
delimiter
No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. In the latter case, the character sequence \<newline> is ignored, and \ must be used to quote the characters \, $, and `.
针对 word 不会执行任何参数扩展、命令替代、算数扩展,或路径扩展。如果 word 中有任何字符是被引号括起来的,那么 delimiter 将是 word 移除引用部分后的内容,此时位于 here-document 中的行将不会被扩展,如果 word 没有被引号括起来,here-document 中的所有行都要受到参数扩展、命令替换和算数扩展的影响。在后者的情况下,字符序列 \<newline> 会被忽略,并且只要存在 \,$ 和 ` 字符都要使用 \ 进行转义(如果你确实打算输出未被转义的字符)。
If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter. This allows here-documents within shell scripts to be indented in a natural fashion.
如果重定向操作符为 <<- ,那么所有前置 tab 字符都将被从输入的行数据和仅包含 delimiter 的行中移除。这将使得 here-document 用于 shell 脚本时能够以自然方式进行缩进。
Here Strings
A variant of here documents, the format is:
<<<word
The word is expanded and supplied to the command on its standard input.
=== 我是 7 代的分隔线 ===
stackoverflow 上的讨论
在 stackoverflow 上有如下针对 bash 中使用 cat << EOF 的讨论。bash 语法 cat <<EOF 在你遇到 Bash 上使用多行字符串的时候是非常有用的,例如,当传递多行字串到一个变量、文件,或者管道中的情况。
例子一: 将多行字符串传递给一个变量 (原文中的测试此处被我加强了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
[root@Betty Shell] # sql=$(cat <<EOF > SELECT foo, bar FROM db > WHERE foo= 'baz' > EOF > ) [root@Betty Shell] # [root@Betty Shell] # echo $sql SELECT foo, bar FROM db WHERE foo= 'baz' [root@Betty Shell] # [root@Betty Shell] # echo -e $sql -e enable interpretation of backslash escapes SELECT foo, bar FROM db WHERE foo= 'baz' [root@Betty Shell] # [root@Betty Shell] # echo -E $sql -E disable interpretation of backslash escapes (default) SELECT foo, bar FROM db WHERE foo= 'baz' [root@Betty Shell] # [root@Betty Shell] # echo "$sql" SELECT foo, bar FROM db WHERE foo= 'baz' [root@Betty Shell] # [root@Betty Shell] # echo -e "$sql" -e enable interpretation of backslash escapes SELECT foo, bar FROM db WHERE foo= 'baz' [root@Betty Shell] # [root@Betty Shell] # echo -E "$sql" -E disable interpretation of backslash escapes (default) SELECT foo, bar FROM db WHERE foo= 'baz' [root@Betty Shell] # |
(上面的结论和我自己的实验结果有出入,按照原文的说法,shell 命令输入时是带有 \n 字符的,并且只有在 -e 选项下能被解析,然而实验结果表明,只要将 $sql 用双引号括起来,结果一定是带有换行符的;而不用双引号括起来的 $sql 则被显示为单行。这里引出一个问题,"$sql" 和 $sql 在 shell 中的区别是什么?)
例子二:将多行字符串传递给一个文件
1
2
3
4
5
|
[root@Betty Shell] # cat <<EOF > print.sh > #!/bin/bash > echo \$PWD > echo $PWD > EOF |
1
2
3
4
|
[root@Betty Shell] # cat print.sh #!/bin/bash echo $PWD -- 未被命令替换 echo /root/workspace/CODE_TEST/Shell -- 被命令替换 |
1
2
3
4
5
6
|
[root@Betty Shell] # cat <<EOF | grep 'b' | tee b.txt | grep 'r' > foo > bar > baz > EOF bar |
===
在上面的例子中,"EOF" 被用作 "Here Tag" 。简单的讲,"<< Here" 的作用就在于告诉 shell 下面将开始一段多行字符串输入,并且该多行字符串以 "Here" 作为终止。你也可以将 "Here" 替换成任何你想要的内容,但通常会使用 EOF 或者 STOP 。
一些关于 Here 标签的规则:
- 标签可以是任何字符串,大小写字母均可,但通常人们习惯使用大写字母。
- 若(尾部)标签所在行还存在其他字符,则该标签将不作为标签起作用。在这种情况下,该标签将仅作为字符串的一部分。标签必须自身独占一行才会被判定为正确的标签。
- 标签所在行中的标签不应该具有前置或后置的空白符,只有这样才被判定为正确的标签。否则会被认为是字符串的一部分。
1
2
3
4
5
6
7
|
[root@Betty Shell] # cat >> test <<HERE > Hello world HERE <--- Not the end of string > HERE <-- Leading space, so not end of string > HERE <-- Now we have the end of the string [root@Betty Shell] # cat test Hello world HERE HERE |
参考
1. Linux man 手册
2. bash read 背后的故事二:read -r
3. 关于 cat > file 和 cat > file <<EOF
4. Here document
5. how does ` cat << EOF` work in bash?
===================================================
http://zh.wikipedia.org/wiki/Here%E6%96%87%E6%A1%A3
here文档[1],又称作heredoc、hereis、here-字串或here-脚本,是一种在命令行shell(如sh、csh、ksh、bash、PowerShell和zsh)和程序语言(像Perl、PHP、Python和Ruby)里定义一个字串的方法。它可以保存文字里面的换行或是缩排等空白字符。一些语言允许在字串里执行变量替换和命令替换。
here文档最通用的语法是<<
紧跟一个标识符,从下一行开始是想要引用的文字,然后再在单独的一行用相同的标识符关闭。在Unix shell里,here文档通常用于给命令提供输入内容。
目录
[隐藏]
实例[编辑]
以下几节提供了不同语言和环境中的例子。
命令行 shell[编辑]
Unix shell[编辑]
在以下几个例子中,文字用here文档传递给tr命令。
$ tr a-z A-Z <<END_TEXT
> one two three
> uno dos tres
> END_TEXT
ONE TWO THREE
UNO DOS TRES
END_TEXT
被用作标识符。它指定了here文档的开始和结束ONE TWO THREE
和UNO DOS TRES
是执行后tr
的输出。
在<<后面添加一个减号,可以使TAB字符被忽略。这允许在shell脚本中缩进here文档而不改变它们的值。(注意在命令行上可能会需要输入Ctrl-v TAB来真正地输入一个制表符。下边的例子用空格模拟制表符;不要复制粘贴。)
$ tr a-z A-Z <<-END_TEXT
> one two three
> uno dos tres
> END_TEXT
ONE TWO THREE
UNO DOS TRES
默认地,会进行变量替换和命令替换:
$ cat << EOF
> Working dir $PWD
> EOF
Working dir /home/user
这可以通过使用引号包裹标识符来禁用。可以使用单引号或双引号:
$ cat << "EOF"
> Working dir $PWD
> EOF
Working dir $PWD
bash,ksh或zsh中也可以用here-字串:
$ tr a-z A-Z <<<"Yes it is a string"
YES IT IS A STRING
Windows 命令行[编辑]
等价的代码目前没有找到。下列代码较为有用。
set GREETING=Hello
echo %GREETING%
cmd /k
echo %GREETING%
set GREETING=Goodbye
echo %GREETING%
exit
echo %GREETING%
C:\>
C:\>set GREETING=Hello
C:\>echo %GREETING%
Hello
C:\>cmd /k
C:\> echo %GREETING%
Hello
C:\> set GREETING=Goodbye
C:\> echo %GREETING%
Goodbye
C:\>exit
C:\>echo %GREETING%
Hello
C:\>
Windows PowerShell[编辑]
在Windows PowerShell里,here文档表示的是here-字串。一个here-字串是由@"
或@'
开始,由独立成行的"@
或'@
结束的字串。所有在开始符号和结束符号之间的字符都被当做字面的字串[3]。
使用双引号引起来的here-字串允许变量替换,而单引号不行[4]。
变量替换只发生于简单变量(如$x
,但不是$x.y
或$x[0]
)。
可以将命令放进$()
中来获取执行结果。
在如下的PowerShell的代码中,文字使用here-字串传递给一个函数。这个函数ConvertTo-UpperCase
定义如下:
PS> function ConvertTo-UpperCase($string) { $string.ToUpper() }
PS> ConvertTo-UpperCase @' >> one two three >> eins zwei drei >> '@ >> ONE TWO THREE EINS ZWEI DREI
下边是一个证明了双引号的here-字串里的变量替换和命令替换的例子:
$doc, $marty = 'Dr. Emmett Brown', 'Marty McFly' $time = [DateTime]'Friday, October 25, 1985 8:00:00 AM' $diff = New-TimeSpan -Minutes 25 @" $doc : Are those my clocks I hear? $marty : Yeah! Uh, it's $($time.Hour) o'clock! $doc : Perfect! My experiment worked! They're all exactly $($diff.Minutes) minutes slow. $marty : Wait a minute. Wait a minute. Doc... Are you telling me that it's $(($time + $diff).ToShortTimeString())? $doc : Precisely. $marty : Damn! I'm late for school! "@
输出:
Dr. Emmett Brown : Are those my clocks I hear? Marty McFly : Yeah! Uh, it's 8 o'clock! Dr. Emmett Brown : Perfect! My experiment worked! They're all exactly 25 minutes slow. Marty McFly : Wait a minute. Wait a minute. Doc... Are you telling me that it's 08:25? Dr. Emmett Brown : Precisely. Marty McFly : Damn! I'm late for school!
如果用单引号的here-字串代替,输出看起来会像这样:
$doc : Are those my clocks I hear? $marty : Yeah! Uh, it's $($time.Hour) o'clock! $doc : Perfect! My experiment worked! They're all exactly $($diff.Minutes) minutes slow. $marty : Wait a minute. Wait a minute. Doc... Are you telling me that it's $(($time + $diff).ToShortTimeString())? $doc : Precisely. $marty : Damn! I'm late for school!
编程语言[编辑]
C++[编辑]
C++11引入了原始字面字串。原始字面字串的前缀有一个“R”,以"分隔符(
开始,以)分隔符"
结束。分隔符可以是0到16字符长,可以包括简单的字符,除开空格,括号与反斜杠。
char const *a = R"(The escape sequence '\n' represents a newline character.)";
wchar_t const *b = LR"...(Raw strings look like R"(...)")...";
char16_t const *b = uR"xyz(
Universal character names such as "\u5367\u864E\u85CF\u3863" are not
processed in raw string literals. Therefore the above can be written
as "臥虎藏龍" in a raw string literal, but only if the source character
set contains those characters.
)xyz";
D语言[编辑]
从2.0版本开始,D语言支持用“q”引导的here-字串。这些字串以一个括号(<>,[],(),{})或者单独成行的标识符开始。
下列D代码展示了使用括号和标识符的here-字串。
int main() {
string list = q"[1. Item One
2. Item Two
3. Item Three]";
writef( list );
}
使用标识符:
int main() {
string list = q"IDENT
1. Item One
2. Item Two
3. Item Three
IDENT";
writef( list );
}
Lua[编辑]
Lua使用[[
和]]
定义字面字串,字面字串中的换行会原样保留,不允许含有转义字符。这不便放置长的注释(--[[注释]]
)和一些字串(x = a[b[c]]
)。所以在版本5.1时,Lua添加了一个新语法:起始的两个括号中间可以加入任意多的等号,并且只有相同的等号数字才能关闭字串。
local ls = [[
Initial newline isn't part of the string.
Two lines.]]
local lls = [==[
This notation can be used for Windows paths:
local path = [=[C:\Windows\Fonts]=]
]==]
Perl[编辑]
在Perl里有许多不同的方法使用here文档[5]。在here文档的标签名前后加括号的效果和一般的字面字串效果是一样的:标签前后加双引号允许变量扩展,单引号则不行,不加引号的和加双引号的效果一样。加反引号将会把here文档当做shell脚本执行,并获取输出。需要保证结束标签必须在一行的开始,不然这个标签不会被直译器认出。
注意here文档不是从标签开始的,而是从下一行开始的。所以包含标签的语句将会在标签后继续。
这是一个使用双引号的例子:
my $sender = "Buffy the Vampire Slayer";
my $recipient = "Spike";
print <<"END";
Dear $recipient,
I wish you to leave Sunnydale and never return.
Not Quite Love,
$sender
END
输出:
Dear Spike, I wish you to leave Sunnydale and never return. Not Quite Love, Buffy the Vampire Slayer
这是使用单引号的例子:
print <<'END';
Dear $recipient,
I wish you to leave Sunnydale and never return.
Not Quite Love,
$sender
END
输出:
Dear $recipient, I wish you to leave Sunnydale and never return. Not Quite Love, $sender
另外一个使用反引号的例子(可能不具有可移植性):
my $shell_script_stdout = <<`END`;
echo foo
echo bar
END
可以在同一行上开始多个here文档:
say(<<BEGIN . "this is the middle\n" . <<END);
This is the beginning:
BEGIN
And now it is over!
END
#上边的和这个相同:
say("This is the beginning:\nthis is the middle\nAnd now it is over!");
标签本身可以使用空格,这允许here文档不会破坏缩进。
say <<' END';
Hello World
END
PHP[编辑]
<?php
$name = "Joe Smith";
$occupation = "Programmer";
echo <<<EOF
This is a heredoc section.
For more information talk to $name, your local $occupation.
Thanks!
EOF;
$toprint = <<<EOF
Hey $name! You can actually assign the heredoc section to a variable!
EOF;
echo $toprint;
?>
输出:
This is a heredoc section. For more information talk to Joe Smith, your local Programmer. Thanks! Hey Joe Smith! You can actually assign the heredoc section to a variable!
包含关闭标识符的行不得包含除了(可选的)分号的任何其他字符。不然它就不会被识别为关闭标识符,PHP就会继续寻找一个。如果没有找到关闭标识符,分析错误会发生在最后一行[6]。
在PHP 5.3和以后的版本中,就像Perl一样,可以用单引号包裹标识符阻止变量扩展;这叫作nowdoc[7]:
$x = <<<'END'
Dear $recipient,
I wish you to leave Sunnydale and never return.
Not Quite Love,
$sender
END;
在PHP5.3和以后的版本中,也可以用双引号包裹标识符,像Perl一样,和不用引号的效果一样。
Python[编辑]
Python支持使用三个连续单引号或双引号的字面字串(如'''
或"""
)。这些字面字串可以跨越多行,支持here文档的功能。
一个简单的Python3兼容的例子给出像上边第一个Perl例子一样:
message="""Dear {recipient},
I wish you to leave Sunnydale and never return.
Not Quite Love,
{sender}
"""
print(message.format(sender='Buffy the Vampire Slayer', recipient='Spike'))
在Python3.0以前的版本中,用print关键字代替print函数。
R[编辑]
R语言在字串里使用空格,包括换行。不执行变量替换。字串可以用textConnection()
函数转化为文件描述符。例如,以下代码将一个嵌入源码的数据表转化为一个数据框架变量:
str <-
"State Population Income Illiteracy Life.Exp Murder HS.Grad Frost
Alabama 3615 3624 2.1 69.05 15.1 41.3 20
Alaska 365 6315 1.5 69.31 11.3 66.7 152
Arizona 2212 4530 1.8 70.55 7.8 58.1 15
Arkansas 2110 3378 1.9 70.66 10.1 39.9 65"
x <- read.table(textConnection(str), header=TRUE, row.names=1)
Racket[编辑]
Racket的here字串以#<<
开始,紧跟定义字串终止的标识符[8]。
字串的内容包括所有的在#<<
一行和仅包括定义了的终止符的那一行。即:字串的内容开始于#<<
后的新行,结束于终止符之前的一行。
#lang racket
(displayln
#<<HERESTRING
This is a simple here string in Racket.
* One
* Two
* Three
HERESTRING
)
输出:
This is a simple here string in Racket. * One * Two * Three
here字串中的转义字符不被识别;字串(和终止符)中所有的字符都会保持原样。
#lang racket
(displayln
#<<A here string in Racket ☺
This string spans for multiple lines
and can contain any Unicode symbol.
So things like λ, ☠, α, β, are all fine.
In the next line comes the terminator. It can contain any Unicode symbol as well, even spaces and smileys!
A here string in Racket ☺
)
输出:
This string spans for multiple lines and can contain any Unicode symbol. So things like λ, ☠, α, β, are all fine. In the next line comes the terminator. It can contain any Unicode symbol as well, even spaces and smileys!
here字串可以像一般的字串一样使用:
#lang racket
(printf #<<END
Dear ~a,
Thanks for the insightful conversation ~a.
~a
END
"Isaac"
"yesterday"
"Carl")
输出:
Dear Isaac, Thanks for the insightful conversation yesterday. Carl
一个有趣的替代方案是使用语言的扩展at-exp
来写@-表达式[9]。
它们看起来像这样:
#lang at-exp racket
(displayln @string-append{
This is a long string,
very convenient when a
long chunk of text is
needed.
No worries about escaping
"quotes". It's also okay
to have λ, γ, θ, ...
Embed code: @|(number->string (+ 3 4))|
})
输出:
This is a long string, very convenient when a long chunk of text is needed. No worries about escaping "quotes". It's also okay to have λ, γ, θ, ... Embed code: 7
Ruby[编辑]
下列Ruby代码用here文档显示了一个列表:
puts <<GROCERY_LIST
Grocery list
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
* Organic
GROCERY_LIST
输出:
Grocery list ------------ 1. Salad mix. 2. Strawberries.* 3. Cereal. 4. Milk.* * Organic
写入文件:
File::open("grocery-list", "w") do |f|
f << <<GROCERY_LIST
Grocery list
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
* Organic
GROCERY_LIST
end
Ruby也允许标识符不起始于行首,需要以<<-
起始here文档。
另外,Ruby对待here文档就像一个双引号括起来的字串,即可以使用#{}来进行代码替换。
以下例子展示了这2个特性:
now = Time.now
puts <<-EOF
It's #{now.hour} o'clock John, where are your kids?
EOF
但是如果标识符是用单引号引起来的,则当做单引号内的字串对待[10]。
类似于Perl,Ruby允许在一行内开始多个here文档[10]:
puts <<BEGIN + "<--- middle --->\n" + <<END
This is the beginning:
BEGIN
And now it is over!
END
# 以上相等于:
puts "This is the beginning:\n<--- middle --->\nAnd now it is over!"
Tcl[编辑]
Tcl没有为here文档设立特殊的语法,因为一般的字串语法已经允许嵌入换行和保持缩进。用括号括起来的字串,没有扩展:
puts { Grocery list ------------ 1. Salad mix. 2. Strawberries.* 3. Cereal. 4. Milk.* * Organic }
用引号括起来的字串在运行时执行替换:
set sender "Buffy the Vampire Slayer"
set recipient "Spike"
puts "
Dear $recipient,
I wish you to leave Sunnydale and never return.
Not Quite Love,
$sender
"
在括号包裹的字串里,起始括号和终止括号数量应该一样多。在引号包裹的字串里,括号可以不一样多,但是反斜杠,美元符号和括号都会被替换,此时第一个没有被转义的双引号会结束字串。
需要注意的一点是:上边的字串的第一个和最后一个字符都是换行。string trim
可以用来删除头尾空行:
puts [string trim "
Dear $recipient,
I wish you to leave Sunnydale and never return.
Not Quite Love,
$sender
" \n]
其它[编辑]
微软 NMAKE[编辑]
在微软NMAKE里,here文档是行内的文件。行内文件以<<
或<<文件名
开始[11]。第一种方法创建一个临时文件。第二种创建(或覆盖)特定文件。所有的行内文件都终止于独自成行的<<
,后边可以添加不区分大小写的KEEP
或NOKEEP
来决定该文件是否保留。两个都不添加和加入NOKEEP
效果一样[12]。
target0: dependent0
command0 <<
临时行内文件
...
<<
target1: dependent1
command1 <<
临时行内文件,但保留
...
<<KEEP
target2: dependent2
command2 <<filename2
专有行内文件,但用完后删除
...
<<NOKEEP
target3: dependent3
command3 <<filename3
专有行内文件
...
<<KEEP
参见[编辑]
======================
http://qindongliang.iteye.com/blog/2117973
在linux下面,shell脚本熟练使用,有时候可以帮助我们解决很多需要人工做的事情,有些公司比如阿里,或者京东,集群规模非常大,动辄成百上千台服务器,大量的机器如果需要人工去做某些事情,是非常低效,繁琐,容易出错的,所以每个公司都有自己的一套自动化运维的一套程序,今天,散仙在这里分享的只是利用shell+expect实现的一个自动化的部署,主要有2个功能,批量建立用户和批量配置SSH无密码双向登陆的脚本,在文末散仙,会打包上传这几个脚本,欢迎使用和测试,主要包含的东西:3个脚本外加一个hosts文件和一个使用说明书。
脚本如下:
批量建立用户的脚本cuser.sh:
- if [ ! $# -eq 2 ] ; then
- echo "请输入用户名和密码以空格分开!"
- exit
- else
- name="$1"
- passwd="$2"
- fi
- cat hosts | while read hosts
- do
- echo "正在$hosts上用户$name"
- expect <<EOF
- spawn ssh $hosts "useradd $name; echo $name:$passwd | chpasswd"
- expect {
- "*yes/no" {send "yes\r" ;exp_continue}
- "*password:" {send "dongliang\r" ;exp_continue }
- }
- EOF
- echo "成功建立"
- done
root用户初始化脚本,initroot.sh
- if [ ! $# -eq 2 ] ;then
- echo "请输入用户密码以空格分开"
- exit
- else
- #用户名
- uname="$1"
- #密码
- passwd="$2"
- fi
- #执行检测并安装expect模块
- ep=`rpm -qa | grep expect`
- if [ -z $ep ] ; then
- echo "检测到你的系统,没有安装expect模块,准备执行安装"
- sleep 2
- yum install -y expect
- else
- echo "已经安装expect模块,3秒后,开始执行ssh文件同步"
- sleep 3
- fi
- #公钥无ssh认证模块
- cat hosts | while read host
- do
- echo "当前正在向$host上传输ssh文件"
- expect <<EOF
- spawn scp -r .ssh/ $host:/$uname
- expect {
- "*yes/no" {send "yes\r" ;exp_continue}
- "*password:" {send "$passwd\r" ;exp_continue }
- }
- EOF
- echo "当前正在$host上进行公钥认证....."
- sleep 2
- expect <<EOF
- spawn ssh-copy-id -i .ssh/id_rsa.pub $host
- expect {
- "*yes/no" {send "yes\r" ;exp_continue}
- "*password:" {send "$passwd\r" ;exp_continue }
- }
- EOF
- echo "认证成功...."
- done
- #切换root权限进行hosts文件分发
- echo "同步本机的hosts文件到各个机器上"
- sleep 1
- #同步本机的hosts文件到其他各个机器上
- cat hosts | while read host
- do
- scp -r /etc/hosts $host:/etc/
- done
- echo "同步hosts文件完毕"
普通用户的安装脚本,noroot.sh
- if [ ! $# -eq 2 ] ; then
- echo "请输入用户名密码以空格分开"
- exit
- else
- #用户的名字
- uname="$1"
- #用户的统一密码
- pd="$2"
- fi
- #公钥无ssh认证模块
- cat hosts | while read host
- do
- echo "当前正在向$host上传输ssh文件"
- expect <<EOF
- spawn scp -r .ssh/ $host:/home/$uname
- expect {
- "*yes/no" {send "yes\r" ;exp_continue}
- "*password:" {send "$pd\r" ;exp_continue }
- }
- EOF
- echo "当前正在$host上进行公钥认证....."
- sleep 2
- expect <<EOF
- spawn ssh-copy-id -i .ssh/id_rsa.pub $host
- expect {
- "*yes/no" {send "yes\r" ;exp_continue}
- "*password:" {send "$pd\r" ;exp_continue }
- }
- EOF
- echo "认证成功...."
- done
hosts文件示例:
- 192.168.46.28
- 192.168.46.29
- 192.168.46.33
使用说明书:
- 脚本主要完成的功能:
- 1,自动化批量建立用户
- 2,自动化批量双向配置SSH认证
- 3,测试通过Centos6.5,其他系统大同小异,如报错,可改部分代码
- 使用步骤:
- 1,配置集群的host文件,把所有机器的IP都写在hosts文件里,注意默认这几个文件解压在root根目录即可。
- 2,先在第一台root用户上执行 ssh-keygen -t rsa -P ''一路回车生成ssh文件
- 3,然后执行initroot.sh脚本,并传入root账号的用户名,密码,此脚本会首先检测系统是否安装过expect,如果没有
- 安装,则yum下载,如果安装过,则直接进行ssh文件拷贝,并执行公钥认证
- 4,执行cuser.sh脚本,并传入要建立用户的账号密码,就可以批量的在多个机器上建立用户了
- 5,然后拷贝root跟目下下的hosts文件和noroot.sh文件到上面建的用
- 户根目录下,并把权限改成用户权限
- 6,然后切入刚才建立的账户,同样执行第2个步骤生成ssh文件
- 7,最后执行noroot.sh文件,给刚才建的批量用户之间建立ssh双向无密码认证
- 8,至此完成,批量用户创建和SSH认证。