Shell脚本读取缺less最后一行
我有一个奇怪的问题与一个bash shell脚本,我希望得到一些见解。
我的团队正在研究一个脚本,它遍历文件中的行并检查每个行中的内容。 我们有一个错误,当通过将不同脚本排列在一起的自动化过程运行时,最后一行没有被看到。
用于遍历文件中的行的代码(存储在DATAFILE
中的名称是
cat "$DATAFILE" | while read line
我们可以从命令行运行脚本,它会看到文件中的每一行,包括最后一行,都很好。 但是,当由自动化进程运行(运行脚本以在脚本之前生成DATAFILE)时,最后一行是不可见的。
我们更新了代码,使用以下代码遍历行,并清除了问题:
for line in `cat "$DATAFILE"`
注意:DATAFILE在文件末尾没有写过换行符。
我的问题是两部分…为什么最后一行不能被原始代码看到,为什么这会改变有所作为?
我只想到我可以想出为什么最后一行不会被看到是:
- 上一个写入文件的进程依赖于结束closures文件描述符的过程。
- 问题脚本启动并打开文件的速度足够快,以至于在前一个进程已经“结束”的时候,它没有“closures/清理”足够的系统来自动closures文件描述符。
话虽如此,如果你在一个shell脚本中有两条命令,第一条命令在脚本运行第二条命令时应该完全closures。
对于这些问题的深入了解,特别是第一个问题,我们将非常感激。
C标准说文本文件必须以换行符结束,否则最后换行符后的数据可能无法正确读取。
ISO / IEC 9899:2011§7.21.2stream
文本stream是组成行的有序字符序列,每行由零个或多个字符加上一个终止的换行符组成。 最后一行是否需要终止换行字符是实现定义的。 在input和输出中可能需要添加,更改或删除字符,以符合在主机环境中表示文本的不同约定。 因此,stream中的字符与外部表示中的字符之间不需要一一对应。 只有在以下情况下,从文本stream中读取的数据必定与先前写入到该stream中的数据相比较:数据仅由打印字符和控制字符水平制表符和换行符组成; 空行字符之前不会有新行字符; 最后一个字符是换行符。 在读入时出现在换行符之前的空格字符是否是实现定义的。
我不会意外地在文件末尾丢失一个换行符,导致在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 $
它也不限于bash
– Korn shell( ksh
)和zsh
也是这样的。 我活着,我学习; 感谢提出这个问题。
如上面的代码所示, cat
命令读取整个文件。 for line in `cat $DATAFILE`
的for line in `cat $DATAFILE`
收集所有输出,并用一个空白replace空白的任意序列(我断定文件中的每一行都不包含空格)。
在Mac OS X 10.7.5上testing
POSIX说什么?
POSIX read
命令规范说:
读取实用程序应从标准input中读取一行。
默认情况下,除非指定了
-r
选项,否则<backslash>应作为转义字符。 一个未转义的<backslash>应该保留以下字符的字面值,除了<newline>之外。 如果<换行符>跟在<反斜杠>之后,则读取实用程序应将其解释为行延续。 在将input拆分为字段之前,应将<backslash>和<newline>
删除。 所有其他未转义的<backslash>字符在将input拆分为字段之后将被删除。如果标准input是一个terminal设备,并且调用shell是交互式的,那么当读取一个以<backslash> <newline>结尾的input行时,read将提示input一个延续行,除非指定了
-r
选项。终止的<newline> (如果有的话)将从input中删除,并且结果将被拆分成与参数展开结果的shell中相同的字段(参见Field Splitting)。 […]
注意'(如果有的话)'(强调加在报价单上)! 在我看来,如果没有换行符,它仍然应该读取结果。 另一方面,它也说:
STDIN
标准input应该是一个文本文件。
然后你回到关于一个不以换行符结尾的文件是否是文本文件的争论。
但是,在同一页面文件的理由:
虽然标准input必须是一个文本文件,因此总是以<换行符>结尾(除非它是一个空文件),当不使用
-r
选项时继续行的处理可能导致input不是以<换行符>结尾。 如果input文件的最后一行以<backslash> <newline>结尾,则会发生这种情况。 因为这个原因,在描述中的“终止<新行>(如果有的话)将被从input中删除”中使用“如果有的话”。 这不是标准input作为文本文件的要求的放松。
理由必须意味着文本文件应该以换行符结束。
一个文本文件的POSIX定义是:
3.395文本文件
包含组成零个或多个行的字符的文件。 这些行不包含NUL字符,并且都不能超过{LINE_MAX}个字节,包括<newline>字符。 虽然POSIX.1-2008没有区分文本文件和二进制文件(参见ISO C标准),但是在文本文件上操作时,许多实用程序只能产生可预测或有意义的输出。 具有这种限制的标准实用程序始终在STDIN或INPUT FILES部分中指定“文本文件”。
这并没有直接规定“以<newline>结束”,而是遵循C标准。
解决“无terminal换行”问题
注意Gordon Davisson的答案 。 一个简单的testing表明他的观察是准确的:
$ while read line; do echo $line; done < y; echo $line abc def ghi xxx $
因此,他的技术:
while read line || [ -n "$line" ]; do echo $line; done < y
要么:
cat y | while read line || [ -n "$line" ]; do echo $line; done
将在文件末尾(至less在我的机器上)没有换行符的情况下工作。
我仍然惊讶地发现,这些shell会丢弃最后一个段(它不能被称为一行,因为它没有以换行符结尾),但是在POSIX中可能有足够的理由去做。 显然,最好确保你的文本文件真的是以换行符结尾的文本文件。
根据读取命令的POSIX规范,如果“检测到文件结尾或发生错误”,则应该返回非零状态。 由于EOF在读取最后一个“行”时被检测到,所以它设置$行,然后返回一个错误状态,并且错误状态阻止循环在最后的“行”上执行。 解决方法很简单:如果读取命令成功,或者如果有任何内容读入$行,则使循环执行。
while read line || [ -n "$line" ]; do
添加一些额外的信息:
- 没有必要在while循环中使用
cat
。while ...;do something;done<file
就足够了。 - 不要用
for
读行。
使用while循环读取行时:
- 正确设置
IFS
(否则可能会丢失缩进)。 - 你应该几乎总是使用阅读-r选项。
满足上面的要求一个适当的while循环将看起来像这样:
while IFS= read -r line; do ... done <file
并在最后使用不带换行符的文件(从这里重新发布我的解决scheme):
while IFS= read -r line || [ -n "$line" ]; do echo "$line" done <file
或者用while循环使用grep
:
while IFS= read -r line; do echo "$line" done < <(grep "" file)
使用sed来匹配文件的最后一行,如果不存在的话,它会附加一个换行符,然后让它执行文件的内联replace:
sed -i '' -e '$a\' file
代码来自这个stackexchange 链接
注意:我已经将空单引号添加到-i ''
因为至less在OS X中, -i
是使用-e
作为备份文件的文件扩展名。 我本来很乐意评论原文,但缺less50分。 也许这会在这个线程中获得一些,谢谢。
我怀疑在你的文件的最后一行没有换行符可能会导致这个问题。 对于testing,你可以稍微修改你的脚本,并像这样读取DATAFILE:
while read line do echo $line # do processing here done < "$DATAFILE"
看看这是否有所作为。
我在命令行中testing了这个
# create dummy file. last line doesn't end with newline printf "%i\n%i\nNo-newline-here" >testing
testing你的第一种forms(pipe道到while循环)
cat testing | while read line; do echo $line; done
这错过了最后一行,这是有道理的,因为read
得到以换行符结尾的input。
testing你的第二种forms(命令replace)
for line in `cat testbed1` ; do echo $line; done
这也得到最后一行
如果只有换行符终止, read
input,这就是为什么你错过了最后一行。
另一方面,在第二种forms
`cat testing`
扩展到的forms
line1\nline2\n...lineM
这是由shell分隔成多个字段使用IFS,所以你得到
line1 line2 line3 ... lineM
这就是为什么你仍然得到最后一行。
p / s:我不明白的是你如何得到第一个表格的工作…
作为一种解决方法,在从文本文件读取之前,可以将新行附加到文件。
echo "\n" >> $file_path
这将确保以前在文件中的所有行将被读取。
我有一个类似的问题。 我正在做一个文件的猫,pipe道到一个sorting,然后pipe道结果'一边读var1 var2 var3'。 即: cat $ FILE | sort -k3 | while read读取IP名称do “do”下的工作是一个if语句,用于标识$ Name字段中的数据更改,并根据更改或无变化执行$ Count的总和或打印该报告的总结线。 我也遇到了无法将最后一行打印到报告的问题。 我用简单的方法将cat / sortredirect到一个新文件,并在新文件中回显一个换行符,然后在新文件上运行我的“计数IP名称”,结果成功。 即: cat $ FILE | sort -k3> NEWFILE echo“\ n”>> NEWFILE cat NEWFILE | while读取计数IP名称有时候简单,不雅是最好的方法。