Read a file line by line assigning the value to a variable
在.txt有以下文件:
我想读它的线-线,和为每个线我想assign A .txt线两个变量的值。我supposing变$name冰,冰流
- 从文件读取第一行
- assign $name="马可"
- $name做一些任务。
- 二次线从文件读
- assign $name="保罗"
- 可能重复了bash中文件的内容?
- 这些问题能以某种方式合并吗?这两个问题都有一些非常好的答案,突出了问题的不同方面,坏的答案在评论中有深入的解释,从现在起,你不能从一对问题的答案中,真正地得到一个关于要考虑什么的整体的概述。把所有这些都放在一个地方会很有帮助,而不是浪费在两页纸上。
以下(另存为rr.sh)逐行读取作为参数传递的文件:
1 2 3 4
| #!/bin/bash
while IFS='' read -r line || [[ -n"$line" ]]; do
echo"Text read from file: $line"
done <"$1" |
说明:
- IFS=''或IFS=防止前导/尾随空格被修剪。
- -r防止反斜杠溢出被解释。
- 如果最后一行没有以
结尾,则|| [[ -n $line ]]可防止忽略它(因为read在遇到eof时返回非零退出代码)。
按如下方式运行脚本:
1 2
| chmod +x rr.sh
./rr.sh filename.txt |
…
- 这种方法有一个警告。如果while循环中的任何内容是交互式的(例如从stdin读取),那么它将从$1获取输入。您将没有机会手动输入数据。
- 注意-有些命令会打破(如中所述,它们会打破循环)这一点。例如,没有-n标志的ssh将有效地导致您退出循环。这可能是一个很好的原因,但是在我发现这个之前,我花了一段时间才弄清楚导致代码失败的原因。
- @mklement0希望我能对你的评论投两次赞成票——你刚刚帮了我一天的忙:)确认:非常好!(我指的是IFS提示)
- 这不再有效了-如果文件包含"*"字符,它们将由shell插入(文件在$pwd中)。
- 作为一行:while ifs=''read-r line[-n"$line"];do echo"$line";done
- 我对此有一个问题,那就是while..done可以变得非常大。在这一点上,当您数月或数年后回到代码中时,弄清楚while循环中的读取实际上是什么可能是一个问题。如果您可以使用一个将文件名附加到"while"关键字本身的语法,那就更好了。我的第一个虽然是…但会产生语法错误。最好的解决方案似乎是让shell用文件描述符(数字)打开一个文件。但是你还必须记住在文件末尾关闭文件。
- 不知道[[ ]]比[ ]的利润是多少
- 对于TSV文件来说似乎不是一个好的解决方案,因为它将表格转换为单个空格。
- @卡皮,谢谢!你说得对,我必须做"cat/dev/null mplayer…",因为mplayer会得到作为交互输入的文件名。我假设任何交互命令都是这样的,所以cat/dev/null应该对这里有所帮助——感谢您帮助解决这个问题:)
- 我几乎想投反对票。它有严重的问题。当您对循环中的其他脚本执行嵌套调用时,正在读取的文件中的位置将移动。我不知道为什么——不打电话给ffmpeg,它就行了。在一个嵌套脚本中调用它时,它没有这样做。可能是一个bash bug特性。
- @ Ondra?我?Ka,这是由ffmpeg消耗stdin引起的。将</dev/null添加到您的ffmpeg行中,它将无法或无法为循环使用备用fd。这种"替代fd"方法看起来像while IFS='' read -r line <&3 || [[ -n"$line" ]]; do ...; done 3<"$1"。
- 抱怨的回复:建议.sh延期。Unix上的可执行文件通常根本没有扩展(您不运行ls.elf),而具有bash shebang(和bash-only工具,如[[ ]])和一个暗示posix-sh兼容性的扩展在内部是矛盾的。
- @mklement0我还是没有得到IFS=''后面的逻辑?它是怎么工作的?它的解释和代码一样神秘。
- @johnstrood:IFS(内部字段分隔符)是一个特殊变量,它决定了read如何将每一行拆分为单词(字段)。IFS=''—即,将IFS设置为空字符串的赋值—取消分词以及前导和尾随空格的修剪,以确保整行按原样返回。IFS='' read ...只将IFS的值更改作用于read命令。有关设置IFS的技术概述,请参见我的回答。
我鼓励您使用-r标志表示read代表:
1 2
| -r Do not treat a backslash character in any special way. Consider each
backslash to be part of the input line. |
我是从江户记下的。
另一件事是以文件名作为参数。
以下是更新的代码:
1 2 3 4 5 6
| #!/usr/bin/bash
filename="$1"
while read -r line; do
name="$line"
echo"Name read from file - $name"
done <"$filename" |
- 从线条修剪前导和尾随空间
- @托马斯,中间的空间怎么了?提示:不需要的命令执行尝试。
- 这对我很有效,与公认的答案相反。
- @translucentcloud,如果这个方法有效,而接受的答案无效,我怀疑您的shell是sh,而不是bash;接受的答案中|| [[ -n"$line" ]]语法中使用的扩展测试命令是一种攻击。也就是说,该语法实际上具有相关的含义:它会导致循环继续执行输入文件中的最后一行,即使它没有换行符。如果您想以符合POSIX的方式来实现这一点,那么您应该使用[,而不是[[。
- 也就是说,这仍然需要修改,以设置IFS=,为read,以防止修剪空白。
- 如果读取的文件中有两行以上,则此操作不起作用。
- @陶塞夫好奇的家伙,…嗯?在ideone.com/mdpsak上看到它与四行输入完美结合
使用下面的bash模板应该允许您一次从一个文件中读取一个值并对其进行处理。
1 2 3
| while read name; do
# Do what you want to $name
done < filename |
- 作为一行:while read name;do echo$name done
- 除了你想要read -r,你需要引用"$name"。
- 我喜欢@gert的回答,因为它是一条单行线,但这个更简洁。这个例子很有效。while read line; do echo $line; done < filename
- @CalculausKnight,它只是"起作用",因为您没有使用足够有趣的数据进行测试。尝试使用反斜杠内容,或者使用只包含*的行。
- @查尔斯达菲,这可能很适合他的目的,以及他拥有的数据。如果每一个假设性的边缘案例从字面上看都没有出现,就没有理由覆盖它们。但是,很明显,了解您所指的边缘情况是很好的。
- @马蒂亚斯的假设最终被证明是错误的,这是最大的漏洞来源之一,无论是对安全的影响还是其他方面。我所看到的最大的数据丢失事件是由于一个场景,有人认为这个场景"字面上永远不会出现"——一个缓冲区溢出将随机内存转储到用于命名文件的缓冲区中,导致一个脚本对哪些名称可能会发生非常非常不幸的行为做出假设。
- @Matthias,…该雇主的运营团队是该领域的老兵,在他们中的任何一个犯了一个错误(依靠与[0-9a-f]{24}匹配的名称)之前有多年的经验,破坏了我们的客户账单备份——但是如果这样的错误影响的地方有足够高的成本,即使场景i十年一次。在重要的时候防止错误的最佳方法是遵循最佳实践,即使你不知道这一点也很重要。代码被复制/粘贴并在其作者不期望的地方重用。
- @Matthias,…在这里尤其如此,因为StackOverflow中显示的代码示例旨在用作教学工具,供人们在自己的工作中重用模式!
- 马蒂亚斯,如果你正在编写Java或C或Python,你会添加额外的函数调用吗?你没有想到会有任何效果,因为它们会有效果和破坏代码的场景是不可能的吗?当您不引用bash中的扩展名时,实际上是在要求shell执行globbing和字符串拆分。当你不通过-r来阅读时,你实际上是在要求shell处理反斜杠转义——额外的步骤。如果你只想要一组特定的行为,你不应该要求更多的行为——即使是遗漏。
- 是的,在我发表评论后,我看到了你对下面问题的评论。你说得对。但是我也是。例如,我使用它的目的是直接在终端中编写bash代码来完成一些脚本工作。在这种情况下,绝对不需要包括-r、IFS=''等。主要是你的措辞让我感到不安("足够有趣的数据"),一般来说,你应该只为你期望的数据设计代码(否则你会浪费开发人员的时间),但当然,合理的预防措施是不难添加的(如-r和引号),我认为这是个好主意。
- 我想我可以尝试在任何地方使用-r,以防万一:p
- @马蒂亚斯,我完全不同意"你应该为你期望的数据设计代码"的说法。意想不到的情况是你的错误在哪里,你的安全漏洞在哪里——处理它们是粗暴代码和健壮代码之间的区别。当然,这种处理不需要很花哨——它可能只是"带错误退出"——但是如果您根本没有处理,那么您在意外情况下的行为是未定义的。
- 我想我们同意,但这里只是沟通错误。例如,你提到的那些情况,意外的情况和产生错误的情况,都是我一直想要处理的(如果有可能发生的话)。当然,这取决于它们发生的后果。例如,对于一些边缘案例,没有意义的花费数月的编程处理,如果它们发生的话,只会发生一个小的日志故障,可以很快修复。
- 换句话说,确实希望人们尝试并利用您的输入接口。如果它们来自交互输入,那么产生错误的情况就不应该是意外的——如果你有一台机器可以产生你的输入,那么就完全可以这样做,因为只要那台机器本身没有错误,它就不会出错。
1 2 3 4
| #! /bin/bash
cat filename | while read LINE; do
echo $LINE
done |
- 没有什么与其他答案相反的,也许它们更具代表性,但我赞成这个答案,因为它简单易读,足以满足我的需要。请注意,要使其工作,要读取的文本文件必须以空行结尾(即在最后一行之后需要按Enter),否则最后一行将被忽略。至少这就是发生在我身上的事。
- 猫没用,嘘?
- 引用被破坏了;您不应该使用大写的变量名,因为它们是为系统使用而保留的。
- @Antonioviniciusmenezesmedei,bash充满了警告——在一个小的例子中,很容易有一些"有效"的东西,但是当你在某个有趣的地方使用它时,它就会中断。在这种情况下,它将扩展globs(用*换行并用文件名列表写一行)、字符串拆分和重新连接空白(例如,将制表符改为空格),并消除输入中的反斜杠。
- @Antonioviniciusmenezesmedei,…此外,我还看到人们承受经济损失,因为他们认为这些警告对他们来说无关紧要;没有学习好的实践;然后遵循他们在编写管理关键计费数据备份的脚本时习惯的做法。学会正确做事很重要。
- 除此之外,这里提到的ssh "lsusb" | while read ln; do echo $ln; done形式是非常有用的。
- 这在所有符合POSIX的Bourne shell中都是可移植的(不需要bash,/bin/sh也可以工作,以防您在没有bash的系统上(例如,默认的bsd、aix、irix、hpux安装))。
- @考伯特,"便携式",因为它在所有的问题上都有相同的漏洞。为什么你不想用在任何地方都正确的东西来代替呢?
- 这里的另一个问题是管道打开了一个新的子shell,也就是说,循环结束后,无法读取循环中设置的所有变量。
- @查尔斯达菲,你说得对。每一行进入一个变量,一个变量会把星号(*)和其他特殊字符搞得一团糟。我有一些基本的SQL查询行。我所有的select*都得到了文件名的回复!这种变量的使用是荒谬的。
- 将echo $LINE改为echo"$LINE"可以解决95%的问题。(将read LINE改为IFS= read -r LINE,将echo全部替换为printf '%s
'"$LINE",在给定有效的unix输入文件时,可以解决剩下的问题;要处理不带换行符的无效文件,还需要cppcoder的答案中的|| [[ $LINE ]]或其他替代方法)。参见bashpitfalls 14和bashfaq 1。
许多人发布了一个过度优化的解决方案。我不认为这是错误的,但我谦虚地认为,一个不太优化的解决方案将是可取的,使每个人都能轻松理解这是如何工作的。这是我的建议:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #!/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" |
用途:
1 2 3 4 5 6 7
| filename=$1
IFS=$'
'
for next in `cat $filename`; do
echo"$next read from $filename"
done
exit 0 |
如果将IFS设置为不同的值,则会得到奇怪的结果。
- 这是一个可怕的方法。请不要使用它,除非你想在你意识到它之前就出现问题。
- 这并不可怕,执行过程中没有中断。
- @Muybelgium你试过用一个包含单个*行的文件吗?总之,这是一个反模式。不要和for一起阅读行。
- 这是一个很好的方法,我推荐它用于更复杂的脚本。见我对read答案的评论。
- @ Ondra?我?Ka,read方法是社区共识的最佳实践方法。您在注释中提到的警告是,当您的循环运行从stdin读取的命令(如ffmpeg)时适用的警告,通过使用非stdin fd进行循环或重定向此类命令的输入来解决这些问题。相反,在你的for循环方法中处理全局错误意味着(然后需要反转)shell全局设置更改。
- @ Ondra?我?ka,…此外,您在这里使用的for循环方法意味着,在循环开始执行之前,必须先读取所有内容,这样即使禁用了globbing,如果循环超过GB的数据,它也将完全不可用;while read循环一次只需要存储一行数据,这意味着它可以在生成内容的子进程仍在运行时开始执行(因此可用于流式处理),并且内存消耗也有限制。
- (为了进一步扩大并发性差异:一个while read循环可以启动一个处理阶段,而前一个阶段仍在生成更多数据;一个for循环需要在完成任何操作之前完成收集。此外,还有持续的时间性能差异:for next in `cat filename` 涉及一个fork()调用来创建子shell,execve()用/bin/cat替换该子shell,以及运行外部工具所涉及的所有链接器/加载程序开销;while read循环在shell itsel内部执行所有必要的操作。f)。
- 好吧,你找到了我。(尽管我不认为bash是一种处理千兆字节大小文件的语言,而且还要记住,我需要为这种或那种类型的脚本做一个变通方案,这对开发人员不是很友好。)我不明白"重定向这种命令的输入"是什么意思。
- @ Ondra?我?ka,re:在任何命令(如ffmpeg)上重定向--</dev/null,否则会中断stdin流。大多数Unix命令的行为都很好,除非被告知,否则不会从stdin中读取。如果我不得不把某个问题称为导致良好实践出现异常的问题,那么这个问题就是ffmpeg。
- 实际上,即使是基于while的方法也似乎存在*-字符问题。见上述已接受答案的评论。尽管如此,并没有因为文件是反模式而反对迭代。
如果您需要同时处理输入文件和用户输入(或来自stdin的任何其他内容),请使用以下解决方案:
1 2 3 4 5
| #!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n"$line" ]]; do
read -p"> $line (Press Enter to continue)"
done |
基于接受的答案和bash hacker重定向教程。
这里,我们打开作为脚本参数传递的文件的文件描述符3,并告诉read使用这个描述符作为输入(-u 3)。因此,我们将默认的输入描述符(0)附加到终端或其他输入源上,从而能够读取用户输入。
要正确处理错误:
1 2 3 4 5 6 7 8 9
| #!/bin/bash
set -Ee
trap"echo error" EXIT
test -e ${FILENAME} || exit
while read -r line
do
echo ${line}
done < ${FILENAME} |
- 你能解释一下吗?
- 不幸的是,它错过了文件中的最后一行。
- …而且,由于缺乏引用,因此,如bashpitfalls 14中所述,咀嚼包含通配符的行。
下面将打印出文件的内容:
1 2 3 4 5 6
| cat $Path/FileName.txt
while read line;
do
echo $line
done |
我把这个问题理解为:
"如果我想用Expect读取文件,我该怎么做?我想这样做是因为当我写"用$name做一些任务"时,我的意思是我的任务是expect命令。"
从Expect本身内部读取文件:
您的预期脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #!/usr/bin/expect
# Pass in filename from command line
set filename [ lindex $argv 0 ]
# Assumption: file in the same directory
set inFile [ open $filename r ]
while { ! [ eof $inFile ] } {
set line [ gets $inFile ]
# You could set name directly.
set name $line
# Do other expect stuff with $name ...
puts" Name: $name"
}
close $inFile |
然后像这样称呼它:
1
| yourExpectScript file_with_names.txt |
- 这看起来不像巴什。
- 事实上,这是tcl(expect扩展的语言),这不是bash。
- 似乎有人从来没有听说过非TCL脚本。但是,如果我没有弄错,可以从bash调用tcl,反之亦然,所以这个答案实际上是有效的。
- 这实际上是一个有趣的答案,尽管它并不相关。