我有一个... bash shell脚本的奇怪问题,我希望得到一些见解.
我的团队正在编写一个脚本,该脚本遍历文件中的行并检查每个行中的内容.我们有一个错误,当通过将不同脚本排序在一起的自动化过程运行时,最后一行没有被看到.
使用的代码遍历文件中的行(名称存储在DATAFILE被
cat "$DATAFILE" | while read line
Run Code Online (Sandbox Code Playgroud)
我们可以从命令行运行脚本,它会看到文件中的每一行,包括最后一行,就好了.但是,当由自动化进程(运行在相关脚本之前生成DATAFILE的脚本)运行时,永远不会看到最后一行.
我们更新了代码以使用以下代码迭代这些行,并清除了问题:
for line in `cat "$DATAFILE"`
Run Code Online (Sandbox Code Playgroud)
注意:DATAFILE没有在文件末尾写入换行符.
我的问题是两部分......为什么最后一行不会被原始代码看到,为什么这会改变有所作为呢?
我只想到我能想出为什么最后一行不会被看到:
话虽这么说,似乎,如果你在shell脚本中有2个命令,那么第一个应该在脚本运行第二个时完全关闭.
对问题的任何见解,尤其是第一个,都将非常感激.
Jon*_*ler 77
C标准表示文本文件必须以换行符结束,否则可能无法正确读取最后一行换行符后的数据.
ISO/IEC 9899:2011§7.21.2流
文本流是由行组成的有序字符序列,每行由零个或多个字符加上终止的换行符组成.最后一行是否需要终止换行符是实现定义的.可能必须在输入和输出上添加,更改或删除字符,以符合在主机环境中表示文本的不同约定.因此,流中的字符与外部表示中的字符之间不需要一一对应.从文本流读入的数据必须与之前写入该流的数据相等,只有在以下情况下:数据仅包含打印字符,控制字符包含水平制表符和新行; 空格字符前面不会有任何换行符; 最后一个字符是换行符.在读入时是否出现在换行符之前立即写出的空格字符是实现定义的.
我不会在文件末尾意外丢失换行符导致bash(或任何Unix shell)出现问题,但这似乎是可重现的问题($是此输出中的提示):
$ echo xxx\\c
xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y
$ cat y
abc
def
ghi
xxx$
$ while read line; do echo $line; done < y
abc
def
ghi
$ bash -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ ksh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ zsh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ for line in $(<y); do echo $line; done # Preferred notation in bash
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done # UUOC Award pending
abc
def
ghi
xxx
$
Run Code Online (Sandbox Code Playgroud)
它也不仅限于bash- Korn shell(ksh),并且zsh行为也是如此.我活着,我学习; 谢谢你提出这个问题.
如上面的代码所示,该cat命令读取整个文件.该for line in `cat $DATAFILE`技术收集所有输出并用一个空格替换任意的空白序列(我得出结论,文件中的每一行都不包含空格).
在Mac OS X 10.7.5上测试.
POSIX read命令规范说:
读取实用程序应从标准输入读取一行.
默认情况下,除非
-r指定了该选项,否则<backslash>将充当转义字符.未转义的<反斜杠>应保留后续字符的文字值,但<newline>除外.如果<newline>跟在<反斜杠>之后,则读取实用程序应将此解释为行继续.<newline>在将输入拆分为字段之前,应删除<反斜杠> .将输入拆分为字段后,应删除所有其他未转义的<反斜杠>字符.如果标准输入是终端设备并且调用shell是交互式的,则当读取以<反斜杠> <换行符>结尾的输入行时,读取将提示继续行,除非
-r指定了该选项.终止<换行符> (如果有的话)应从输入中删除,结果应分成字段,如shell中的参数扩展结果(参见Field Splitting); [...]
请注意'(如果有的话')(重点在引用中添加)!在我看来,如果没有新行,它仍然应该读取结果.另一方面,它还说:
STDIN
标准输入应为文本文件.
然后你回到关于不以换行结尾的文件是否是文本文件的争论.
但是,同一页面文件的基本原理:
虽然标准输入必须是文本文件,因此总是以<换行符>结束(除非它是空文件),但是在
-r不使用该选项时继续行的处理可能导致输入不以一个<换行符>.如果输入文件的最后一行以<backslash> <newline>结尾,则会发生这种情况.出于这个原因,在"终止<换行符>(如果有的话)中使用"if any"将从描述中的输入中删除".放宽标准输入作为文本文件的要求并不放松.
该基本原理必须意味着文本文件应该以换行符结束.
POSIX文本文件的定义是:
3.395文本文件
包含组织为零行或多行的字符的文件.这些行不包含NUL字符,长度不能超过{LINE_MAX}个字节,包括<newline>字符.尽管POSIX.1-2008不区分文本文件和二进制文件(请参阅ISO C标准),但许多实用程序在操作文本文件时仅产生可预测或有意义的输出.具有此类限制的标准实用程序始终在其STDIN或INPUT FILES部分中指定"文本文件".
这并没有直接规定"以<newline>结尾",而是遵循C标准.
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
Run Code Online (Sandbox Code Playgroud)
因此,他的技术:
while read line || [ -n "$line" ]; do echo $line; done < y
Run Code Online (Sandbox Code Playgroud)
要么:
cat y | while read line || [ -n "$line" ]; do echo $line; done
Run Code Online (Sandbox Code Playgroud)
将适用于最后没有换行的文件(至少在我的机器上).
我仍然惊讶地发现shell丢弃了最后一个段(它不能被称为一行,因为它不以换行符结束),但POSIX中可能有足够的理由这样做.显然,最好确保您的文本文件确实是以换行符结尾的文本文件.
Gor*_*son 44
根据read命令的POSIX规范,如果"检测到文件结尾或发生错误",它应返回非零状态.由于EOF在读取最后一个"行"时被检测到,因此它设置$line然后返回错误状态,并且错误状态阻止循环在最后一行"行"上执行.解决方案很简单:如果读取命令成功或者读入任何内容,则执行循环$line.
while read line || [ -n "$line" ]; do
Run Code Online (Sandbox Code Playgroud)
Jah*_*hid 18
catwhile循环.while ...;do something;done<file足够.for.使用while循环读取行时:
IFS正确(否则可能会丢失缩进).满足上述要求适当的while循环将如下所示:
while IFS= read -r line; do
...
done <file
Run Code Online (Sandbox Code Playgroud)
并使其在没有换行符的情况下使用文件(从此处重新发布我的解决方案):
while IFS= read -r line || [ -n "$line" ]; do
echo "$line"
done <file
Run Code Online (Sandbox Code Playgroud)
或者使用grepwhile循环:
while IFS= read -r line; do
echo "$line"
done < <(grep "" file)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
40344 次 |
| 最近记录: |