为啥你不能使用 cat 逐行读取文件,其中每行都有分隔符

Posted

技术标签:

【中文标题】为啥你不能使用 cat 逐行读取文件,其中每行都有分隔符【英文标题】:Why can't you use cat to read a file line by line where each line has delimiters为什么你不能使用 cat 逐行读取文件,其中每行都有分隔符 【发布时间】:2013-06-10 14:14:54 【问题描述】:

我有一个包含如下内容的文本文件:

abc 123, comma
the quick brown fox
jumped over the lazy dog
comma, comma

我写了一个脚本

for i in `cat file`
do
   echo $i
done

由于某种原因,脚本的输出不会逐行输出文件,而是在逗号和换行符处将其断开。为什么 cat 或 "for blah in cat xyz" 这样做,我怎样才能让它不这样做?我知道我可以使用

while read line
do
   blah balh blah
done < file

但我想知道为什么 cat 或“for blah in”这样做是为了加深我对 unix 命令的理解。 Cat 的手册页对我没有帮助,在 bash 手册中查找或循环也没有得到任何答案(http://www.gnu.org/software/bash/manual/bashref.html)。提前感谢您的帮助。

【问题讨论】:

【参考方案1】:

IFS - 可以设置内部字段分隔符以获得您想要的。

要一次读取整行,请使用: IFS=""

【讨论】:

【参考方案2】:

for 循环加上内部字段分隔符 (IFS) 的更改将按预期读取文件

用于输入

abc 123, comma
the quick brown fox
jumped over the lazy dog
comma, comma

For 循环加上 IFS 更改

old_IFS=$IFS
IFS=$'\n'
for i in `cat file`
do
        echo $i
done
IFS=$old_IFS

结果

abc 123, comma
the quick brown fox
jumped over the lazy dog
comma, comma

【讨论】:

只需使用IFS= read -r line 保留行中的所有空格。 while 循环“丢失”间距的唯一原因是您使用了echo $line 而不是echo "$line"。如果间距很重要,请将变量引用用双引号括起来。 正如 chepner 所说,这应该是 read -r 以避免意外的副作用(评估反斜杠转义序列)。【参考方案3】:

您可以使用IFS 变量来指定您想要换行符作为字段分隔符:

IFS=$'\n'
for i in `cat file`
do
   echo $i
done

【讨论】:

不安全——你已经阻止了字符串拆分,但你没有阻止全局扩展。如果一行包含*,则在回显期间将扩展为当前目录中的名称列表。【参考方案4】:

问题不在于cat,也不在于for 循环本身;它是在使用反引号。当你写任何一个时:

for i in `cat file`

或(更好):

for i in $(cat file)

或(bash):

for i in $(<file)

shell 执行命令并将输出捕获为字符串,在$IFS 中的字符处分隔单词。如果您想将行输入到$i,则必须摆弄IFS 或使用while 循环。如果存在处理的文件很大的危险,while 循环会更好;与使用$(...) 的版本不同,它不必一次将整个文件读入内存。

IFS='
'
for i in $(<file)
do echo "$i"
done

"$i" 周围的引号通常是个好主意。在这种情况下,使用修改后的$IFS,实际上并不重要,但好习惯就是好习惯。它在以下脚本中很重要:

old="$IFS"
IFS='
'
for i in $(<file)
do
   (
   IFS="$old"
   echo "$i"
   )
done

当数据文件包含多个单词之间的空格时:

$ cat file
abc                  123,         comma
the   quick   brown   fox
jumped   over   the   lazy   dog
comma,   comma
$ 

输出:

$ sh bq.sh
abc                  123,         comma
the   quick   brown   fox
jumped   over   the   lazy   dog
comma,   comma
$

没有双引号:

$ cat bq.sh
old="$IFS"
IFS='
'
for i in $(<file)
do
   (
   IFS="$old"
   echo $i
   )
done
$ sh bq.sh
abc 123, comma
the quick brown fox
jumped over the lazy dog
comma, comma
$

【讨论】:

感谢您的帮助和回复。我对 bash/*nix 有点困惑。我没有改变IFS。它默认设置为换行符。我用 echo "IFS = $IFS word test" 检查了它,字符串 "word test" 打印到下一行,所以我们知道它默认是 \n 。在任何情况下,使用默认的 IFS,即使 IFS=\n,它也会在逗号处中断我的行。当我按照您上面的建议进行操作时,通过将 IFS 明确设置为 \n,它会打印我的整行而不会中断逗号。知道为什么它在显式设置为 \n 时起作用,而在默认情况下 IFS 已经是 \n 时不起作用?再次感谢。 IFS的默认值为(使用一段bash-speak)$' \t\n';也就是说,它由空白、制表符、换行符组成。这可能会改变您的分析。我相信,当您说“在逗号处中断”时,您的意思是它在逗号后的空格处中断,这与包含空白(以及制表符和换行符)的 IFS 一致。【参考方案5】:
cat filename | while read i
do
    echo $i
done

【讨论】:

以上是关于为啥你不能使用 cat 逐行读取文件,其中每行都有分隔符的主要内容,如果未能解决你的问题,请参考以下文章

如何逐行读取字符串生成器

Oracle pl/sql 从物理目录中读取文件的内容

delphi中怎么逐行读取文本文件的数据并将每行分别写入指定的不同编辑框

从文件中读取多行中的多个值(Java)

[Go]:使用并发性逐行读取文件

VB6.0中如何实现逐行读入文本文件?