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

Mar*_*rco 702 bash

我有以下.txt文件:

Marco
Paolo
Antonio
Run Code Online (Sandbox Code Playgroud)

我想逐行读取它,并且对于每行我想为变量分配.txt行值.假设我的变量是$name,流程是:

  • 从文件中读取第一行
  • 分配$name="Marco"
  • 做一些任务 $name
  • 从文件中读取第二行
  • 分配$name="保罗"

cpp*_*der 1268

以下(另存为IFS=)读取作为参数逐行传递的文件:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt
Run Code Online (Sandbox Code Playgroud)

说明:

  • IFS=''(或-r)防止修剪前导/尾随空格.
  • readfile 防止反斜杠转义被解释.
  • || [[ -n $line ]]如果最后一行不以a结尾\n(因为read在遇到EOF时返回非零退出代码),则阻止忽略最后一行.

运行脚本如下:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"
Run Code Online (Sandbox Code Playgroud)

....

  • 这种方法有一个警告.如果while循环中的任何内容是交互式的(例如从stdin读取),那么它将从$ 1获取其输入.您将无法手动输入数据. (22认同)
  • 值得注意的是 - 一些命令会破坏(例如,它们会破坏循环).例如,没有`-n`标志的`ssh`将有效地使你逃脱循环.这可能是一个很好的理由,但是在我发现这个之前,我花了一段时间来确定导致我的代码失败的原因. (9认同)
  • *grumble*re:建议`.sh`扩展名.UNIX上的可执行文件通常根本不具有扩展(你不运行`ls.elf`),并且有一个bash shebang(和bash-only工具,如`[[]]`)和一个暗示POSIX sh的扩展名兼容性在内部是矛盾的. (9认同)
  • @OndraŽižka,这是由'ffmpeg`消耗stdin引起的.将`</ dev/null`添加到你的`ffmpeg`行,它将无法或使用备用FD作为循环."替代FD"方法看起来像"IFS ="读取-r行<&3 || [[-n"$ line"]]; 做......; 完成3 <"$ 1"`. (8认同)
  • 作为一个单行:IFS =''读-r行|| [[-n"$ line"]]; 回声"$ line"; 完成<文件名 (6认同)
  • @JohnStrood:[`IFS`(内部字段分隔符)](https://www.gnu.org/software/bash/manual/bashref.html#Word-Splitting)是一个特殊变量,用于确定`read`如何分割每个换成单词(字段).`IFS =''` - 即,将`IFS`设置为空字符串的赋值 - 停用字拆分以及修剪前导和尾随空格以确保按原样返回整行.`IFS =''read ...`将`IFS`值更改为`read`命令.有关设置"IFS"的技术概述,请参阅我的[此答案](/sf/answers/2064550561/). (2认同)

Grz*_*cki 302

我鼓励你使用以下-r标志read代表:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.
Run Code Online (Sandbox Code Playgroud)

我引用了man 1 read.

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

这是更新的代码:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"
Run Code Online (Sandbox Code Playgroud)

  • 修剪线条的前导和尾随空间 (4认同)
  • @TranslucentCloud,如果这个有效并且接受的答案没有,我怀疑你的shell是'sh`,而不是'bash`; `||中使用的扩展测试命令 接受的答案中的[[-n"$ line"]]`语法是一种基础.也就是说,该语法实际上具有相关意义:它导致循环继续输入文件中的最后一行,即使它没有换行符.如果你想以符合POSIX的方式做到这一点,你需要`|| [-n"$ line"]`,使用`[`而不是`[[`. (3认同)
  • 也就是说,**仍然需要修改为`read`设置`IFS =`以防止修剪空格. (3认同)

小智 126

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

while read name; do
    # Do what you want to $name
done < filename
Run Code Online (Sandbox Code Playgroud)

  • 作为一个单行:同时读取名称; do echo $ {name}; 完成<文件名 (14认同)
  • 除了你想要`read -r`,你需要引用``$ name"`. (8认同)
  • @Matthias,最终证明是假的假设是最大的漏洞来源之一,无论是安全影响还是其他方面.我见过的最大的数据丢失事件是由于有人认为"从字面上永远不会出现"的情况 - 缓冲区溢出将随机内存转储到用于命名文件的缓冲区中,导致脚本假设哪些名称可能永远不会出现发生非常*非常*不幸的行为. (7认同)
  • @Matthias,......这就是**,因为在StackOverflow中显示的代码示例旨在用作教学工具,以便人们在自己的工作中重用这些模式! (5认同)
  • @Matthias,我完全不同意"你应该只为你期望的数据设计代码"的说法.意外的情况是你的bug存在的地方,你的安全漏洞在哪里 - 处理它们是slapdash代码和健壮的代码之间的区别.当然,处理不需要花哨 - 它可能只是"退出并出现错误" - 但如果您根本没有处理,那么您在意外情况下的行为是不确定的. (5认同)
  • @CalculusKnight,它只是"有效",因为你没有使用足够有趣的数据进行测试.尝试使用反斜杠的内容,或者只包含`*`的行. (4认同)
  • @Matthias,......该雇主的运营团队是该领域的资深人士,在他们中的任何一个犯了错误之前有多年的经验(依靠与`[0-9a-f] {24}相匹配的名字破坏了我们的客户账单备份 - 但如果这样的错误产生影响的地方有足够高的成本,那么即使这个场景是十年一遇,也值得保护.在重要的时候防止错误的最好方法是遵循最佳实践,即使你不知道它是否重要.代码被复制/粘贴并在其作者不期望的地方重复使用. (3认同)
  • @Matthias,...如果您正在编写Java或C或Python,您是否会添加额外的函数调用,而这些调用并不会产生任何影响,因为它们*会产生影响并破坏代码的情况不大可能?当你没有引用bash中的扩展时,你实际上要求shell执行globbing和string-splitting.当你没有传递`-r`来阅读时,你实际上要求shell处理反斜杠 - 转义 - 额外的步骤.如果您只想要一组特定的行为,那么您不应该要求的不仅仅是那些行为 - 即使是遗漏. (3认同)

小智 71

#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done
Run Code Online (Sandbox Code Playgroud)

  • 对其他的答案没有什么,也许他们更sofisticated,但我给予好评这个答案,因为它很简单,可读性,是足以让我需要什么.需要注意的是,为它工作,要读取必须以空行结束文本文件(即一个需要按下`最后一行之后Enter`),否则最后一行会被忽略.至少那是发生在我身上的事. (8认同)
  • 无用的猫,shurely? (8认同)
  • @AntonioViniciusMenezesMedei,...此外,我看到人们承受经济损失,因为他们认为这些警告对他们来说永远不会重要; 未能学习良好做法; 然后遵循他们在编写管理关键计费数据备份的脚本时习惯的习惯.学会正确行事很重要. (7认同)
  • 这里的另一个问题是管道打开一个新的子shell,即在循环结束后无法读取循环内设置的所有变量. (6认同)
  • 报价被打破了; 并且您不应该使用大写变量名称,因为它们是为系统使用而保留的. (4认同)
  • @AntonioViniciusMenezesMedei,bash充满了警告 - 很容易让一些东西在一个微不足道的情况下"起作用",但只要你在有趣的地方使用它就会中断.在这种情况下,它将扩展globs(用`*`换行并用文件名列表写一行),字符串拆分和重新加入空格(例如将标签更改为空格),并消除输入中的反斜杠. (2认同)
  • 除了警告,形式`ssh <ip>"lsusb"| 读ln; 回声$ ln; 完成这里暗示非常有用. (2认同)

Rau*_*una 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"
Run Code Online (Sandbox Code Playgroud)


小智 19

使用:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0
Run Code Online (Sandbox Code Playgroud)

如果你设置IFS不同,你会得到奇怪的结果.

  • [这是一种可怕的方法](http://mywiki.wooledge.org/DontReadLinesWithFor).请不要使用它,除非你想要在你意识到之前发生全球化问题! (32认同)
  • @MUYBelgium你试过一个包含一行`*`的文件吗?无论如何,***这是一个反模式***.[不要阅读有关的行](http://mywiki.wooledge.org/DontReadLinesWithFor). (13认同)
  • @OndraŽižka,“读取”方法是[通过社区共识的最佳实践方法](http://mywiki.wooledge.org/BashFAQ/001)。您在注释中提到的警告是一种适用于您的循环运行从stdin读取的命令(例如ffmpeg)的情况,这些问题通过使用非stdin FD进行循环或重定向此类命令的输入来轻松解决。相比之下,解决“ for”循环方法中的全局错误意味着要更改(然后需要撤消)shell全局设置。 (2认同)
  • @OndraŽižka,...此外,您在此处使用的“for”循环方法意味着必须在循环开始执行之前读取所有内容,如果您循环处理千兆字节的数据,则它完全无法使用,即使你*已经*禁用了通配符;“while read”循环一次需要存储不超过一行的数据,这意味着它可以在生成内容的子进程仍在运行时开始执行(因此可用于流式传输目的),并且内存消耗也有限。 (2认同)

glu*_*k47 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
Run Code Online (Sandbox Code Playgroud)

基于接受的答案bash-hackers重定向教程.

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


bvi*_*tor 7

为了正确处理错误:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}
Run Code Online (Sandbox Code Playgroud)