如何在 Bash 中迭代文件中的每一行?

43

我有一个包含多行文本的文本文件,我想在Bash脚本中迭代处理每一行。我尝试使用cut,但cut不接受\n(换行)作为分隔符。

以下是我正在处理的文件示例:

one
two 
three 
four

有没有人知道我如何在Bash中循环遍历文本文件的每一行?


这个链接:https://dev59.com/TWYr5IYBdhLWcg3wH2zK 也有相关的答案。 - user8395964
10个回答

88

我也遇到了相同的问题,以下方法适用于我:

cat file.cut | cut -d$'\n' -f1

或:
cut -d$'\n' -f1 file.cut

5
好的,我会尽力为您翻译。以下是所需翻译的内容:为什么这个能够工作?在这种情况下,美元符号起到什么作用? - Jules G.M.
10
@JulesG.M. 它的工作原理是因为Bash具有称为ANSI-C引用的功能。文档提到:“$'string'”形式的单词会被特殊处理。该单词扩展为字符串,并根据ANSI C标准指定的转义字符进行替换。您可以在此处找到文档:https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html - metator

35

使用cat进行连接或显示。这里不需要。

file="/path/to/file"
while read line; do
  echo "${line}"
done < "${file}"

1
我同意这个观点,但是这种方法明显比“cut”方法慢。 - Stan Strum
@StanStrum 这个解决方案应该比cut方法要快得多(时间复杂度为O(n)),并且更易读。OP在询问如何迭代文件中的每一行。使用 cut, 要么需要知道文件中的行数,并编写相应数量的cut语句,要么仍需编写 while 循环,这将使时间复杂度为 O(n + n²) - Andria
然而,这个解决方案并不完整,因为会出现一些陷阱和边缘情况。 - Andria

3
cat FILE|while read line; do # 'line' is the variable name
   echo "$line" # do something here
done

或(请查看注释):
while read line; do # 'line' is the variable name
   echo "$line" # do something here
done < FILE

3
UUOC(http://en.wikipedia.org/wiki/Cat_%28Unix%29#Useless_use_of_cat)- 使用while ... done < file - Kevin
2
这并不是无用的,也许有些浪费(在阅读了这段文字后),但在我看来更易读。 - 0xC0000022L

3

只需使用:

echo -n `cut ...`

这会压制结尾处的 \n。

2

所以,已经提供了一些非常好的(可能更好的)答案。但是,看着原始问题的措辞,希望使用BASH for循环,让我惊讶的是没有人提到通过更改字段分隔符IFS解决的方法。这是一个纯bash解决方案,就像被接受的read line一样。

old_IFS=$IFS
IFS='\n'
for field in $(<filename)
do your_thing;
done
IFS=$old_IFS

2
如果您确定输出始终为换行符分隔,可以使用head -n 1代替cut -f1(请注意,您在脚本中提到了for循环,而您的问题最终与脚本无关)。
许多其他答案,包括被接受的答案,都有不必要的多行。没有必要在多行上执行此操作或更改系统上的默认分隔符。
另外,Ivan提供的使用-d$'\n'的解决方案在我的Mac OSX或CentOS 7上也不起作用。由于他的答案已经四年了,我认为在这种情况下$字符的逻辑肯定已经发生了变化。

2

带输入重定向和read命令的while循环。

您不应该使用cut对文件中的每一行执行顺序迭代,因为cut没有设计用于此目的。

从每个FILE的选择部分的行打印到标准输出。 — man cut

TL;DR

您应该在函数范围内使用带有read -r命令和将标准输入重定向到文件的while循环,并将IFS设置为\n,并在使用echo时使用-E

processFile() {          # Function scope to prevent overwriting IFS globally
  file="$1"              # Any file that exists
  local IFS="\n"         # Allows spaces and tabs
  while read -r line; do # Read exits with 1 when done; -r allows \
    echo -E "$line"      # -E allows printing of \ instead of gibberish
  done < $file           # Input redirection allows us to read file from stdin
}
processFile /path/to/file

迭代

为了遍历文件的每一行,我们可以使用 while 循环。这将让我们迭代尽可能多的次数。

while <condition>; do
  <body>
done

准备读取文件

我们可以使用read命令将标准输入的单行存储到变量中。在我们可以使用它从文件中读取一行之前,我们需要重定向标准输入以指向我们的文件。我们可以使用输入重定向来实现这一点。根据bash的man页面,重定向的语法是[fd]<file,其中fd默认为标准输入(也称为文件描述符0)。我们可以在while循环之前或之后放置它。

while <condition>; do
  <body>
done < /path/to/file

# or the non-traditional way
</path/to/file while <condition>; do
  <body>
done

读取文件并结束循环

现在我们的文件可以从标准输入中读取,我们可以使用read。在我们的上下文中,read的语法是read [-r] var...,其中-r保留\(反斜杠)字符,而不是将其用作转义序列字符,var是存储输入的变量名称。您可以有多个变量来存储输入的片段,但我们只需要一个来读取整行。除此之外,为了保留echo输出中的任何反斜杠,您可能需要使用-E标志来禁用反斜杠转义的解释。如果您有任何缩进(空格或制表符),则需要暂时更改IFS(输入字段分隔符)变量仅为"\n";通常它设置为" \t\n"

main() {
  local IFS="\n"
  read -r line
  echo -E "$line"
}

main

如何使用read来结束while循环?

我知道的确切方法只有一种,就是检查read的退出值来确定何时完成文件读取。如果read的退出值为0,则我们成功读取了一行,如果它是1或更高,则到达了EOF(文件结尾)。有了这个想法,我们可以将对read的调用放在while循环的条件部分。

processFile() {
  # Could be any file you want hardcoded or dynamic
  file="$1"

  local IFS="\n"
  while read -r line; do
    # Process line here
    echo -E "$line"
  done < $file
}

processFile /path/to/file1
processFile /path/to/file2

通过 Explain Shell 可视化分解上述代码。


0
如果我正在执行一个命令并想要截取输出,但它有多行,我发现这样做很有帮助:
echo $([command]) | cut [....]

使用此命令可以将[command]的所有输出放在一行中,以便更容易处理。


0

在编程中,遍历文件记录最简单的方法是使用 bash 中的 for 循环。

#!/bin/bash
echo 'Showing the records in the file';
for i in `cat myfile.txt`; do echo $i; done;  # this will show records line by line
echo 'End of the script';

-2

我的观点是,“cut”命令使用'\n'作为默认分隔符。 如果你想使用cut命令,我有两种方法:

    cut -d^M -f1 file_cut

我通过按下回车键后再按Ctrl+V来制作^M。另一种方法是

    cut -c 1- file_cut

那能帮助吗?


很遗憾,以上方法都对我无效(bash,版本4.2.45)。 - Nikos Alexandris
2
"cut" 命令的默认分隔符不是 '\n'。 - MisterStrickland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接