逐行读取文件,将值分配给变量


752

我有以下.txt文件:

Marco
Paolo
Antonio

我想逐行阅读它,并且我想为每行分配一个.txt行值给一个变量。假设我的变量是$name,流程是:

  • 从文件中读取第一行
  • 分配$name=“马可”
  • 做一些任务 $name
  • 从文件中读取第二行
  • 分配$name=“ Paolo”


3
可以将这些问题以某种方式合并吗?两者都有一些非常好的答案,突出了问题的各个方面,不好的答案在注释中有深入的解释,这对他们有什么不利之处,到目前为止,您不能真正从问题的答案中获得关于要考虑的内容的完整概述。一对中的一个问题。将所有内容集中在一个位置,而不是将其分散在两页上,将很有帮助。
Egor Hans

Answers:


1356

以下内容逐行读取作为参数传递的文件:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

这是在循环中从文件读取行的标准格式。说明:

  • IFS=(或IFS='')可防止对前导/尾随空格进行裁剪。
  • -r 防止反斜杠转义被解释。

或者,您可以将其放在bash文件帮助程序脚本中,示例内容:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

如果以上内容保存到具有filename的脚本中readfile,则可以按以下方式运行:

chmod +x readfile
./readfile filename.txt

如果该文件不是标准的POSIX文本文件(=不以换行符终止),则可以修改循环以处理尾随的分行:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

在这里,|| [[ -n $line ]]如果最后一行没有以a结尾\n(因为read遇到EOF时会返回非零退出代码),则可以避免忽略最后一行。

如果循环内的命令也从标准输入中读取,则read可能会使用所使用的文件描述符(避免使用标准文件描述符),例如:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(非Bash外壳可能不知道read -u3;请read <&3改用。)


23
有一种使用此方法的警告。如果while循环中的任何内容都是交互式的(例如,从stdin中读取),则它将从$ 1中获取其输入。您将没有机会手动输入数据。
carpie 2014年

10
值得注意的是-一些命令中断(例如,中断循环)。例如,ssh没有该-n标志将有效地导致您退出循环。可能有一个很好的理由,但是我花了一段时间才找出导致代码失败的原因,然后才发现这一点。
Alex

6
作为单线:当IFS =''时读-r行|| [[-n“ $ line”]]; 回显“ $ line”;完成<文件名
约瑟夫·约翰逊

8
@OndraŽižka,是由 ffmpeg食用了stdin。添加</dev/null到您的ffmpeg行中将无法执行此操作,或者将备用FD用于循环。这种“备用FD”方法看起来像while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1"
查尔斯·达菲

9
抱怨:建议.sh扩展。UNIX上的可执行文件通常根本不具有扩展名(您不运行ls.elf),并且具有bash shebang(以及仅限bash的工具,例如[[ ]])和扩展,这意味着POSIX sh兼容性在内部是矛盾的。
查尔斯·达菲

309

我鼓励您使用代表以下内容的-r标志read

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

我从引用man 1 read

另一件事是将文件名作为参数。

这是更新的代码:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

4
修剪

@Thomas,中间的空间怎么办?提示:不需要执行命令。
kmarsh

1
与公认的答案相反,这对我有用。
神经递质

3
@TranslucentCloud,如果此方法有效,并且无法接受,我怀疑您的shell是sh,不是bash; || [[ -n "$line" ]]在接受的答案中的语法中使用的扩展测试命令是bashism。就是说,该语法实际上具有相关的含义:即使没有换行符,它也会使循环在输入文件的最后一行继续。如果您想以符合POSIX的方式进行操作,则可以|| [ -n "$line" ]使用[而不是[[
查尔斯·达菲

3
这就是说,这个确实还是需要进行修改,以组IFS=read防止修剪空白。
查尔斯·达菲

131

使用以下Bash模板应该可以让您一次从文件中读取一个值并进行处理。

while read name; do
    # Do what you want to $name
done < filename

14
单线:读名字时;回显$ {name}; 完成<文件名
约瑟夫·约翰逊

4
@CalculusKnight,它只能“工作”,因为您没有使用足够有趣的数据进行测试。尝试使用反斜杠或仅包含一行的内容*
查尔斯·达菲

7
@Matthias,最终被证明是错误的假设是漏洞的最大来源之一,无论是影响安全性还是其他方面。我所见过的最大的数据丢失事件是由于有人认为“从字面上不会出现”的情况造成的-缓冲区溢出将随机内存转储到用于命名文件的缓冲区中,从而导致脚本做出了关于可能使用哪些名称的假设发生非常非常不幸的行为。
查尔斯·达菲

5
@Matthias,... 在这里尤其如此,因为StackOverflow上显示的代码示例旨在用作教学工具,以便人们在自己的工作中重用这些模式!
查尔斯·达菲

5
@Matthias,我完全不同意“您应该只为期望的数据设计代码”的说法。出乎意料的情况就是您的错误所在,安全漏洞所在的地方-处理它们是slapdash代码与健壮代码之间的区别。当然,处理并不需要花哨的时间-可以“有错误退出”-但是,如果您根本不进行任何处理,那么在意外情况下的行为是不确定的。
Charles Duffy

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
没有其他答案,也许他们比较老练,但是我赞成这个答案,因为它简单,易读并且足以满足我的需求。请注意,要使其正常工作,要读取的文本文件必须以空白行结尾(即,需要Enter在最后一行之后按一下),否则将忽略最后一行。至少那是发生在我身上的事情。
Antonio Vinicius Menezes Medei

12
毫无用处地使用猫吗?
布莱恩·阿格纽

5
报价被打断了;并且您不应使用大写的变量名,因为这些变量名仅供系统使用。
三胞胎

7
@AntonioViniciusMenezesMedei,...此外,我已经看到人们蒙受了经济损失,因为他们认为这些警告对他们来说并不重要。无法学习良好做法;然后遵循他们编写脚本来管理重要计费数据备份的习惯。学习正确做事很重要。
Charles Duffy

6
这里的另一个问题是管道打开了一个新的子外壳,即在循环完成后无法读取循环内设置的所有变量。
mxmlnkn

20

许多人发布了一个过于优化的解决方案。我不认为这是不正确的,但我虚心地认为,需要一种优化程度较低的解决方案,以使每个人都可以轻松地了解其工作原理。这是我的建议:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"

20

采用:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

如果您设置IFS不同,您将得到奇怪的结果。


34
这是一种可怕的方法。请不要使用它,除非您希望在意识到它之前会遇到问题!
gniourf_gniourf 2014年

13
@MUYBelgium您是否尝试过*在一行中包含单个文件的文件?无论如何,这是一种反模式不要读取带有for的行
gniourf_gniourf 2015年

2
@OndraŽižka,该read方法是社区共识中的最佳实践方法。您在注释中提到的警告是一种适用于您的循环运行ffmpeg从stdin读取的命令(例如)的方法,该问题通过使用非stdin FD循环或重定向此类命令的输入来轻松解决。相比之下,要解决for-loop方法中的全局错误,则意味着要更改(然后需要撤消)shell全局设置。
查尔斯·达菲,2016年

1
@OndraŽižka,......此外,for循环方法,你在这里使用的手段,所有的内容在之前的循环可以开始执行全部被读取,使其完全无法使用,如果你遍历千兆字节的数据,即使你残疾球 的while read循环需要在同一时间不超过一行的数据存储,这意味着它可以开始执行,而子过程产生内容仍在运行(从而是用于流目的可用),并且还具有有界的内存消耗。
查尔斯·达菲

1
实际上,while基于偶数的方法似乎也存在*字符问题。请参阅上面接受的答案的注释。但是,不反对在文件上重复进行反模式辩论。
Egor Hans,

9

如果您需要同时处理输入文件和用户输入(或stdin的其他任何内容),请使用以下解决方案:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

基于公认的答案bash-hackers重定向教程

在这里,我们打开作为脚本参数传递的文件的文件描述符3,并告诉read使用该描述符作为输入(-u 3)。因此,我们保留连接到终端或另一个输入源的默认输入描述符(0),它能够读取用户输入。




0

在bash中使用IFS(内部字段分隔符)工具,定义用于将行分隔为标记的字符,默认情况下包括< tab > / < space > / < newLine >

步骤1:加载文件数据并插入列表:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

步骤2:现在迭代并打印输出:

for line in "${array[@]}"
  do
    echo "$line"
  done

回显数组中的特定索引:访问数组中的变量:

echo "${array[0]}"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.