如果文件末尾没有换行符,如何使用`while read`(Bash)读取文件中的最后一行?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4165135/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use `while read` (Bash) to read the last line in a file if there’s no newline at the end of the file?
提问by Mathias Bynens
Let's say I have the following Bash script:
假设我有以下 Bash 脚本:
while read SCRIPT_SOURCE_LINE; do
echo "$SCRIPT_SOURCE_LINE"
done
I noticed that for files without a newline at the end, this will effectively skip the last line.
我注意到对于末尾没有换行符的文件,这将有效地跳过最后一行。
I've searched around for a solution and found this:
我四处寻找解决方案并找到了这个:
When read reaches end-of-file instead of end-of-line, it does read in the data and assign it to the variables, but it exits with a non-zero status. If your loop is constructed "while read ;do stuff ;done
So instead of testing the read exit status directly, test a flag, and have the read command set that flag from within the loop body. That way regardless of reads exit status, the entire loop body runs, because read was just one of the list of commands in the loop like any other, not a deciding factor of if the loop will get run at all.
DONE=false until $DONE ;do read || DONE=true # process $REPLY here done < /path/to/file.in
当 read 到达文件尾而不是行尾时,它会读入数据并将其分配给变量,但它以非零状态退出。如果你的循环被构造为“while read ;do stuff ;done
因此,与其直接测试读取退出状态,不如测试一个标志,并让读取命令从循环体内设置该标志。这样,无论 read 退出状态如何,整个循环体都会运行,因为 read 只是循环中的命令列表之一,与其他任何命令一样,根本不是循环是否运行的决定因素。
DONE=false until $DONE ;do read || DONE=true # process $REPLY here done < /path/to/file.in
How can I rewrite this solution to make it behave exactly the same as the while
loop I was having earlier, i.e. without hardcoding the location of the input file?
我怎样才能重写这个解决方案以使其行为与while
我之前的循环完全相同,即没有对输入文件的位置进行硬编码?
采纳答案by netcoder
In your first example, I'm assuming you are reading from stdin. To do the same with the second code block, you just have to remove the redirection and echo $REPLY:
在您的第一个示例中,我假设您正在阅读stdin。要对第二个代码块执行相同的操作,您只需删除重定向并回显 $REPLY:
DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
回答by Adam Bryzak
I use the following construct:
我使用以下结构:
while IFS= read -r LINE || [[ -n "$LINE" ]]; do
echo "$LINE"
done
It works with pretty much anything except null characters in the input:
除了输入中的空字符外,它几乎适用于任何东西:
- Files that start or end with blank lines
- Lines that start or end with whitespace
- Files that don't have a terminating newline
- 以空行开头或结尾的文件
- 以空格开头或结尾的行
- 没有终止换行符的文件
回答by Jahid
Using grep
with while loop:
grep
与 while 循环一起使用:
while IFS= read -r line; do
echo "$line"
done < <(grep "" file)
Using grep .
instead of grep ""
will skip the empty lines.
使用grep .
而不是grep ""
将跳过空行。
Note:
笔记:
Using
IFS=
keeps any line indentation intact.File without a newline at the end isn't a standard unix text file.
使用
IFS=
可以保持任何行缩进完好无损。最后没有换行符的文件不是标准的 unix 文本文件。
回答by prabhakaran9397
Instead of read, try to use GNU Coreutilslike tee, cat, etc.
取而代之的读取,尽量使用GNU Coreutils的像T恤,猫等。
from stdin
来自标准输入
readvalue=$(tee)
echo $readvalue
from file
从文件
readvalue=$(cat filename)
echo $readvalue
回答by Olli K
This is the pattern I've been using:
这是我一直在使用的模式:
while read -r; do
echo "${REPLY}"
done
[[ ${REPLY} ]] && echo "${REPLY}"
Which works because even tho' the while
loop ends as the "test" from the read
exits with a non-zero code, read
still populates the inbuilt variable $REPLY
(or whatever variables you choose to assign with read
).
之所以有效,是因为即使while
循环以read
非零代码作为退出的“测试”结束,read
仍会填充内置变量$REPLY
(或您选择分配的任何变量read
)。
回答by vaab
The basic issue here is that read
will return errorlevel 1 when it encounters EOF, even if it'll still correctly feed the variable.
这里的基本问题是read
当它遇到 EOF 时将返回错误级别 1,即使它仍然会正确地提供变量。
So you can use errorlevel of read
right away in your loop, otherwize, the last data won't be parsed. But you could do this:
因此,您可以read
立即在循环中使用 errorlevel ,否则将不会解析最后的数据。但你可以这样做:
eof=
while [ -z "$eof" ]; do
read SCRIPT_SOURCE_LINE || eof=true ## detect eof, but have a last round
echo "$SCRIPT_SOURCE_LINE"
done
If you want a very solid way to parse your lines, you should use:
如果您想要一种非常可靠的方式来解析您的行,您应该使用:
IFS='' read -r LINE
Remember that:
请记住:
- NUL character will be ignored
- if you stick to using
echo
to mimick the behavior ofcat
you'll need to force anecho -n
upon EOF detected (you can use the condition[ "$eof" == true ]
)
- NUL 字符将被忽略
- 如果你坚持使用
echo
来模仿cat
你需要强制echo -n
检测到 EOF的行为(你可以使用条件[ "$eof" == true ]
)
回答by unsynchronized
@Netcoder's answer is good, this optimisation eliminates spurious blank lines, also allows for the last line not to have a newline, if that's how the original was.
@Netcoder 的回答很好,这种优化消除了虚假的空行,还允许最后一行没有换行符,如果原来是这样的话。
DONE=false
NL=
until $DONE ;do
if ! read ; then DONE=true ; NL='-n ';fi
echo $NL$REPLY
done
I used a variant of this to create 2 functions to allow piping of text that includes a '[' to keep grep happy. (you can add other translations)
我使用它的一个变体来创建 2 个函数,以允许包含“[”的文本管道化,以保持 grep 满意。(您可以添加其他翻译)
function grepfix(){
local x="$@";
if [[ "$x" == '-' ]]; then
local DONE=false
local xx=
until $DONE ;do
if ! IFS= read ; then DONE=true ; xx="-n "; fi
echo ${xx}${REPLY//\[/\\[}
done
else
echo "${x//\[/\\[}"
fi
}
function grepunfix(){
local x="$@";
if [[ "$x" == '-' ]]; then
local DONE=false
local xx=
until $DONE ;do
if ! IFS= read ; then DONE=true ; xx="-n "; fi
echo ${xx}${REPLY//\\[/\[}
done
else
echo "${x//\\[/\[}"
fi
}
(passing - as $1 enables pipe otherwise just translates arguments)
(传递 - 因为 $1 启用管道,否则只转换参数)