循环遍历Bash中的文件内容

Pet*_*sen 1242 unix linux io bash loops

如何使用Bash迭代文本文件的每一行?

使用此脚本:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done
Run Code Online (Sandbox Code Playgroud)

我在屏幕上看到这个输出:

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'
Run Code Online (Sandbox Code Playgroud)

(后来我想做一些$p比输出到屏幕更复杂的事情.)


环境变量SHELL是(来自env):

SHELL=/bin/bash
Run Code Online (Sandbox Code Playgroud)

/bin/bash --version 输出:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
Run Code Online (Sandbox Code Playgroud)

cat /proc/version 输出:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006
Run Code Online (Sandbox Code Playgroud)

文件peptides.txt包含:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
Run Code Online (Sandbox Code Playgroud)

Bru*_*ine 1927

一种方法是:

while read p; do
  echo "$p"
done <peptides.txt
Run Code Online (Sandbox Code Playgroud)

正如评论中所指出的,这会产生修剪前导空格,解释反斜杠序列以及如果缺少终止换行而跳过尾随行的副作用.如果这些是问题,你可以这样做:

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt
Run Code Online (Sandbox Code Playgroud)

例外情况下,如果循环体可以从标准输入读取,则可以使用不同的文件描述符打开文件:

while read -u 10 p; do
  ...
done 10<peptides.txt
Run Code Online (Sandbox Code Playgroud)

这里,10只是一个任意数字(不同于0,1,2).

  • "将peptides.txt粘贴到while循环中,所以'read'命令有一些东西要消耗掉." 我的"cat"方法类似,将命令的输出发送到while块以供'read'使用,只有它启动另一个程序才能完成工作. (10认同)
  • 我该如何解释最后一行?文件peptides.txt被重定向到标准输入,并以某种方式重定向到整个while块? (7认同)
  • 此方法似乎跳过文件的最后一行. (7认同)
  • 双引线!! 回声"$ p"和文件..相信我,如果你不这样做,它会咬你!我知道!大声笑 (5认同)
  • 如果两个版本都未以换行符终止,则它们均无法读取最后一行。**在读取p ||时始终使用` [[-n $ p]]; 做...` (5认同)

War*_*ung 392

cat peptides.txt | while read line 
do
   # do something with $line here
done
Run Code Online (Sandbox Code Playgroud)

  • 还有一个更严重的问题:因为while循环是管道的一部分,它在子shell中运行,因此在循环中设置的任何变量在它退出时都会丢失(参见http://bash-hackers.org/维基/ doku.php /镜像/ bashfaq/024).这可能非常烦人(取决于你在循环中尝试做什么). (71认同)
  • 一般来说,如果你只使用一个参数使用"cat",那么你做错了(或次优). (64认同)
  • 这可能效率不高,但它比其他答案更具可读性. (56认同)
  • 是的,它不如布鲁诺的效率高,因为它不必要地启动了另一个程序.如果效率很重要,那就按照布鲁诺的方式行事吧.我记得我的方式,因为你可以将它与其他命令一起使用,其中"redirect in from"语法不起作用. (26认同)
  • 我使用"cat file |"作为我的很多命令的开头纯粹是因为我经常使用"head file |"原型 (22认同)
  • 当你关心表现的差异时,你不会问这些问题. (4认同)
  • @ OgrePsalm33:沃伦是对的."cat"命令用于连接文件.如果你没有连接文件,很可能你不需要使用"cat". (3认同)
  • @matkelcey另外,你怎么把整个文件放到管道的前面?Bash在这里为你提供了很棒的字符串,特别适合像`if grep -q'findme'<<<"$ var"`这样的东西,但是不可移植,而且我不想用一个开始一个大的管道.像`cat ifconfig.output |之类的东西 grep inet [^ 6] | grep -v'127.0.0.1'| awk'{print $ 2}'| cut -d':' - f2`更容易阅读,因为一切都是从左到右.这就像用'awk`而不是`cut`进行strtoking,因为你不想要空标记 - 这有点滥用命令,但这就是它的完成方式. (3认同)
  • 好的,有道理.我想说明一点,因为我在脚本中看到了很多过度使用的例子,其中"cat"只是作为获取单个文件内容的额外步骤. (2认同)
  • +1的可读性和模块性 - 通过将'cat ...'替换为其他输出,可以轻松地将此代码放入更复杂的管道中. (2认同)
  • 这比布鲁诺写的要好得多.当通过命令动态创建数据时,它特别有用.使用Bruno的解决方案,循环将在命令完成后接收任何数据.您的解决方案将命令结果放入循环中,而不从系统中获取缓冲区.例如,将'cat peptides.txt'替换为'find /',或者在之前的解决方案'done <peptides.txt'中用'done <$(find /)'替换.它可能会失败执行,因为有可能溢出缓冲区或消耗所有内存. (2认同)
  • 它跳过最后一行。因此,作为解决方法,必须在最后添加空行。 (2认同)

Sta*_*ves 139

选项1a: While循环:一次一行:输入重定向

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename
Run Code Online (Sandbox Code Playgroud)

选项1b: while循环:一次一行:
打开文件,从文件描述符读取(在本例中为文件描述符#4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done
Run Code Online (Sandbox Code Playgroud)

选项2: For循环:将文件读入单个变量并解析.
此语法将根据标记之间的任何空白区域解析"行".这仍然有效,因为给定的输入文件行是单字标记.如果每行有多个令牌,则此方法不起作用.此外,将整个文件读入单个变量对于大文件来说不是一个好策略.

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename
Run Code Online (Sandbox Code Playgroud)

  • 将使用进程退出清除文件描述符.可以进行显式关闭以重用fd编号.要关闭fd,请使用带有& - 语法的另一个exec,如下所示:exec 4 <& - (3认同)
  • 你应该更清楚地指出选项2是[强烈劝阻](http://mywiki.wooledge.org/DontReadLinesWithFor).@masgo选项1b应该在这种情况下工作,并且可以与选项1a中的输入重定向语法结合使用`done <$ filename`替换为`done 4 <$ filename`(如果你想读取文件名,这很有用)从命令参数,在这种情况下你可以用`$ 1`替换`$ filename`. (3认同)

mig*_*ile 78

这并不比其他答案更好,但是在没有空格的文件中完成工作的另一种方法(参见注释).我发现我经常需要单行来挖掘文本文件中的列表,而无需使用单独的脚本文件.

for word in $(cat peptides.txt); do echo $word; done
Run Code Online (Sandbox Code Playgroud)

这种格式允许我将它全部放在一个命令行中.将"echo $ word"部分更改为您想要的任何内容,您可以发出由分号分隔的多个命令.以下示例将文件的内容用作您可能编写的另外两个脚本的参数.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done
Run Code Online (Sandbox Code Playgroud)

或者,如果您打算像流编辑器一样使用它(学习sed),您可以将输出转储到另一个文件,如下所示.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt
Run Code Online (Sandbox Code Playgroud)

我已经使用了上面这些,因为我已经使用了文本文件,我用它创建了每行一个单词.(请参阅注释)如果你有空格,你不想拆分你的单词/行,它会变得有点丑陋,但同样的命令仍然如下工作:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS
Run Code Online (Sandbox Code Playgroud)

这只是告诉shell只分裂换行符,而不是空格,然后将环境返回到之前的状态.此时,您可能需要考虑将所有内容放入shell脚本中,而不是将其全部压缩到一行中.

祝你好运!

  • bash $(<peptides.txt)可能更优雅,但它仍然是错误的,Joao说的正确,你正在执行命令替换逻辑,其中空格或换行是同一个东西.如果一行中有一个空格,则该循环对该一行执行TWICE或更多.因此,您的代码应该正确读取:$(<peptides.txt)中的单词; 做....如果你知道一个事实没有空格,那么一行等于一个单词,你没事. (5认同)
  • @ JoaoCosta,maxpolk:我没有考虑过的好点.我编辑了原帖以反映它们.谢谢! (2认同)
  • 使用`for`使得输入令牌/行受到shell扩展的影响,这通常是不受欢迎的; 试试这个:`for $ in $(echo'*b c'); 回声"[$ l]"; 完成 - 你会看到,``` - 即使最初是*引用的*literal - 扩展到当前目录中的文件. (2认同)
  • @dblanchard:最后一个使用$ IFS的例子应该忽略空格.你试过那个版本吗? (2认同)
  • 当关键问题得到解决时,这个命令如何变得更复杂的方式,很好地说明了使用`for`来迭代文件行是一个坏主意.另外,@ mklement0提到的扩展方面(即使这可能通过引入转义引号来规避,这又会使事情变得更复杂,更不易读). (2认同)

cod*_*ter 64

还有一些其他答案未涵盖的内容:

从分隔文件中读取

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt
Run Code Online (Sandbox Code Playgroud)

使用进程替换从另一个命令的输出中读取

while read -r line; do
  # process the line
done < <(command ...)
Run Code Online (Sandbox Code Playgroud)

这种方法比command ... | while read -r line; do ...因为while循环在当前shell而不是子shell中运行更好,因为后者的情况.查看相关帖子不记得在while循环中修改的变量.

例如,从空分隔的输入读取 find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)
Run Code Online (Sandbox Code Playgroud)

相关阅读:BashFAQ/020 - 如何查找并安全处理包含换行符,空格或两者的文件名?

一次从多个文件中读取

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt
Run Code Online (Sandbox Code Playgroud)

根据@ chepner的答案在这里:

-u是一个bash扩展.对于POSIX兼容性,每个调用看起来都像read -r X <&3.

将整个文件读入数组(Bash版本早于4)

while read -r line; do
    my_array+=("$line")
done < my_file
Run Code Online (Sandbox Code Playgroud)

如果文件以不完整的行结束(结尾处缺少换行符),则:

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file
Run Code Online (Sandbox Code Playgroud)

将整个文件读入数组(Bash版本4x及更高版本)

readarray -t my_array < my_file
Run Code Online (Sandbox Code Playgroud)

要么

mapfile -t my_array < my_file
Run Code Online (Sandbox Code Playgroud)

然后

for line in "${my_array[@]}"; do
  # process the lines
done
Run Code Online (Sandbox Code Playgroud)

相关文章:


Jah*_*hid 45

使用while循环,如下所示:

while IFS= read -r line; do
   echo "$line"
done <file
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 如果没有IFS正确设置,则会丢失缩进.

  2. 您应该几乎总是将-r选项与read一起使用.

  3. 不要读行 for

  • 为什么`-r`选项? (2认同)
  • @ DavidC.Rankin -r选项可防止反斜杠解释.`Note#2`是一个详细描述的链接...... (2认同)

daw*_*awg 13

假设你有这个文件:

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR
Run Code Online (Sandbox Code Playgroud)

有四个元素会改变许多Bash解决方案读取的文件输出的含义:

  1. 空白行4;
  2. 两条线上的前导或尾随空格;
  3. 保持各条线的含义(即每条线都是记录);
  4. 第6行没有以CR结尾.

如果您希望逐行包含空行和没有CR的终止行的文本文件,则必须使用while循环,并且必须对最后一行进行备用测试.

以下是可能更改文件的方法(与cat返回的内容相比):

1)丢失最后一行以及前导和尾随空格:

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
Run Code Online (Sandbox Code Playgroud)

(如果while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt改为,则保留前导和尾随空格,但如果未以CR终止,则仍会丢失最后一行)

2)使用进程替换cat将在一个gulp中读取整个文件并失去各行的含义:

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'
Run Code Online (Sandbox Code Playgroud)

(如果你"从中删除$(cat /tmp/test.txt)你逐字逐句阅读文件而不是一口气.也可能不是意图......)


逐行读取文件并保留所有间距的最强大和最简单的方法是:

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'
Run Code Online (Sandbox Code Playgroud)

如果您想剥离领先和交易空间,请删除该IFS=部分:

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'
Run Code Online (Sandbox Code Playgroud)

(没有终止的文本文件\n,而相当普遍,被认为是POSIX断下.如果你能在后指望\n你不需要|| [[ -n $line ]]while循环.)

更多BASH常见问题解答


小智 13

如果您不希望您的阅读被换行符破坏,请使用 -

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"
Run Code Online (Sandbox Code Playgroud)

然后以文件名作为参数运行脚本.


ham*_*u92 13

我喜欢用xargs代替while. xargs功能强大且命令行友好

cat peptides.txt | xargs -I % sh -c "echo %"

使用xargs,您还可以添加详细信息-t和验证-p


xek*_*kon 12

这可能是最简单的答案,也许并不适用于所有情况,但它对我来说非常有用:

while read line;do echo "$line";done<peptides.txt
Run Code Online (Sandbox Code Playgroud)

如果需要用括号括起空格:

while read line;do echo \"$line\";done<peptides.txt
Run Code Online (Sandbox Code Playgroud)

啊,这与获得最多支持的答案几乎相同,但都在一行。


小智 5

#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done
Run Code Online (Sandbox Code Playgroud)

  • 此答案需要[mightypile的答案](/ q / 1521462 /#19182518)中提到的警告,并且如果任何行包含shell元字符(由于未引用“ $ x”),它可能会严重失败。 (6认同)
  • 我真的很惊讶,人们还没有提出通常的[不要阅读for的行](http://mywiki.wooledge.org/DontReadLinesWithFor)... (6认同)

归档时间:

查看次数:

1452181 次

最近记录:

5 年,10 月 前