本文主要学习总结一下三方面问题:
- 通过 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 测试】
测试文件如下
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 |
|
测试一:读取文件的首行并赋值给变量
1 2 3 |
|
这一行命令用到了 Bash 的内置命令 read,和输入重定向操作符 < 。read 命令从标准输入中读取一行,并将内容保存到变量 line 中。在这里,-r 选项保证读入的内容是原始的内容,意味着反斜杠转义的行为不会发生。输入重定向操作符 < file 打开并读取文件 file ,然后将它作为 read 命令的标准输入。
记住,read 命令会删除包含在 IFS 变量中出现的所有字符(这个说法似乎不够准确),IFS 的全称是 Internal Field Separator,Bash 根据 IFS 中定义的字符来分隔单词。在这里,read 命令读入的行被分隔成多个单词。默认情况下,IFS 包含空格,制表符和回车,这意味着开头和结尾的空格和制表符都会被删除。如果你想保留这些符号,可以通过设置 IFS 为空来完成:
1 2 3 |
|
IFS 的变化仅会影响当前的命令,这行命令可以保证读入原始的首行内容到变量 line 中,同时行首与行尾的空白字符被保留。
测试二:依次读入文件每一行
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 |
|
这是一种正确的读取文件内容的做法,read 命令放在 while 循环中。当 read 命令遇到文件结尾时(EOF),它会返回一个正值,导致循环判断失败终止。
=== 我是火影终结的分隔线 ===
关于 read 命令遇到文件结尾返回一个正值的结论,之前我一直持怀疑态度。因为经常会遇到这样的用法:
1 |
|
而按照常规编程思维,while 循环的判定条件应该是不为 0 则循环,似乎这里就出现了矛盾。
于是进行如下实验进行验证
1 2 3 4 5 6 7 8 9 10 11 |
|
结果证明,read 读到文件结束时确实返回 1 ,而读到内容时返回 0 。
最后再确认一下 while 的判定规则
1 2 3 4 |
|
哈哈,套用工藤新一的话“真相只有一个”~~
=== 我是火影终结的分隔线 ===
记住,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 |
|
从上面可以看出 < file 永远是放在最后的,如果你不想将 < file 放在最后,可以通过管道将文件的内容输入到 while 循环中:
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 |
|
测试三:读取文件首行前三个字段并赋值给变量
1 2 3 4 |
|
如果在 read 命令中指定多个变量名,它会将读入的内容分隔成多个字段,然后依次赋值给对应的变量,第一个字段赋值给第一个变量,第二个字段赋值给第二个变量,等等,最后将剩余的所有字段赋值给最后一个变量。这也是为什么,在上面的例子中,我们加了一个 throwaway 变量,否则的话,当文件的一行大于三个字段时,第三个变量的内容会包含所有剩余的字段。
有时候,为了书写方便,可以简单地用 _ 来替换 throwaway 变量:
1 2 3 4 |
|
又或者,如果你的文件确实只有三个字段,可以忽略 _ 。
【cat 与 <<】
- cat 命令是 linux 下的一个文本输出命令,通常是用于观看某个文件的内容的; 命令 cat >file 可以用于将键盘上的输入写到文件中。
- EOF 为 "end of file" 的缩写,从语义上代表文本的结束符。
- 通过 cat <<EOF 将两者结合使用(EOF 和 << 中间是否有空格没有关系),即可避免使用多行 echo 命令的方式,并实现多行输出的结果。原则上讲,此处的 EOF 可以使用任何其他字符替代。
测试 - 1
1 2 3 4 |
|
测试 - 2
1 2 3 4 |
|
测试 - 3
1 2 3 4 |
|
【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 |
|
This yields the output:
1 2 |
|
此处使用 <<END_TEXT 或 << END_TEXT 都正确。
在 << 后添加 - 符号的作用是可以忽略掉前置的 tab 。这将允许在 shell 命令行上直接对包含缩进的 here document 进行操作,而不用变更脚本的内容。
注意:要想在 shell 命令行上输入 TAB ,需要连续输入 CTRL-V 、TAB 才行。
1 2 3 4 |
|
This yields the same output, notably not indented:
1 2 |
|
(
补充测试:
1 2 3 4 5 |
|
将得到
1 2 3 |
|
)
在默认情况下,变量将被内插替换,包含在 `` 中的命令将被求值。
backtick 即传说中的反引号。
1 2 3 |
|
yields:
1 |
|
上述行为可以通过引号引用标签的任何部分进行取消。例如,将 EOF 使用单引号或者双引号进行包含:
1 2 3 |
|
yields:
1 |
|
(
补充测试:
1 2 3 |
|
将得到
1 |
|
)
(后面还有对 here string 的介绍,此处略过)
man 手册上的说明
Here Documents
This 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 |
|
执行后,$sql 变量中将会包含带换行符的字串,你可以通过 echo -e "$sql" 命令进行查看。
(上面的结论和我自己的实验结果有出入,按照原文的说法,shell 命令输入时是带有 \n 字符的,并且只有在 -e 选项下能被解析,然而实验结果表明,只要将 $sql 用双引号括起来,结果一定是带有换行符的;而不用双引号括起来的 $sql 则被显示为单行。这里引出一个问题,"$sql" 和 $sql 在 shell 中的区别是什么?)
例子二:将多行字符串传递给一个文件
1 2 3 4 5 |
|
The print.sh file now contains:
1 2 3 4 |
|
例子三:将多行字符串传递给一个 command/pipe
1 2 3 4 5 6 |
|
上述命令只将 bar 打印到标准输出,但会创建 b.txt 文件,其中包含了 bar 和 baz 两行字符串。
===
在上面的例子中,"EOF" 被用作 "Here Tag" 。简单的讲,"<< Here" 的作用就在于告诉 shell 下面将开始一段多行字符串输入,并且该多行字符串以 "Here" 作为终止。你也可以将 "Here" 替换成任何你想要的内容,但通常会使用 EOF 或者 STOP 。
一些关于 Here 标签的规则:
- 标签可以是任何字符串,大小写字母均可,但通常人们习惯使用大写字母。
- 若(尾部)标签所在行还存在其他字符,则该标签将不作为标签起作用。在这种情况下,该标签将仅作为字符串的一部分。标签必须自身独占一行才会被判定为正确的标签。
- 标签所在行中的标签不应该具有前置或后置的空白符,只有这样才被判定为正确的标签。否则会被认为是字符串的一部分。
1 2 3 4 5 6 7 |
|