繁体   English   中英

Bash循环仅读取最后一行

[英]Bash loop only read the last line

我在尝试使用while循环和awk在多行冒号后面提取数据时遇到问题。

这是我的数据结构:

Identifiers:BioSample:SAMD00019077
Identifiers:BioSample:SAMD00019076
Identifiers:BioSample:SAMD00019075
Identifiers:BioSample:SAMD00019074
Identifiers:BioSample:SAMD00019073
Identifiers:BioSample:SAMD00019072
Identifiers:BioSample:SAMD00019071;SRA:DRS051563
Identifiers:BioSample:SAMD00019070;SRA:DRS051562
Identifiers:BioSample:SAMD00019069;SRA:DRS051561
...
Identifiers:BioSample:SAMD00019005;SRA:DRS051497
Identifiers:BioSample:SAMD00015713;SRA:DRS012785

我要获取的是BioSample ID ,类似于SAMD00019077

我尝试的脚本:

  1. while read line ; do echo $line | awk -F':' '{print $3}' > 1.tmp2 ; done < 1.tmp
  2. for line in cat 1.tmp中的行; do echo $line | awk -F':' '{print $3}' > 1.tmp2 ; done ; do echo $line | awk -F':' '{print $3}' > 1.tmp2 ; done
  3. for line in cat 1.tmp中的行; do echo $line | awk -F: '{print $3 > "1.tmp2"}' ; done ; do echo $line | awk -F: '{print $3 > "1.tmp2"}' ; done

他们只提供了最后一行的Biosample ID

$ while read line ; do echo $line | 
  awk -F':' '{print $3}' > 1.tmp2 ; done < 1.tmp
$ head 1.tmp2
SAMD00015713;SRA

我在这里阅读了帖子,看来我的问题与stdinstdoutstderr

bash读取循环仅读取输入变量的第一行

bash while循环只能读一行

我尝试的解决方案,它给出了1行的结果

$ exec 3<&1
$ exec 1<&2
$ while read line ; do echo $line |  
  awk -F':' '{print $3}' > 1.tmp2 ; done< 1.tmp
$ head 1.tmp2
SAMD00015713;SRA
$ exec 1<&3 3<&-

我也尝试了exec < 1.tmp将文件定向到stdin但是会导致错误。

我发现这些脚本对我来说非常有效。 但是我真的很想知道为什么我上面尝试过的脚本会失败。

cat 1.tmp | awk -F: '{print $3}' | head

awk -F: '{print $3}' 1.tmp | head

由于您要遍历1.tmp中的每一行,因此请使用>> 1.tmp2以附加模式而不是> 1.tmp2重定向输出,这将继续替换上一个条目。

首先, awk具有循环线的能力,并且字段分隔符可以是正则表达式。

因此,您的脚本可以简化为以下优化格式:

awk -F'[;:]' '{print $3}' 1.tmp > 1.tmp2

这是您可以使用的优化格式。

话虽如此,您可能想知道脚本中的错误。

while read line ; do echo $line | awk -F':' '{print $3}' > 1.tmp2 ; done < 1.tmp
                                                         ^ here

上面标记的>是重定向运算符。 它将命令的标准输出(在这种情况下为awk )写入指定的文件。 它不会追加,但会覆盖。 因此,在循环的每次迭代中,都将清除文件,并将命令的输出写入其中。 因此,它仅保留最后一个条目。

要解决此问题,您可以使用附加重定向: >>

while read line ; do echo $line | awk -F':' '{print $3}' >> 1.tmp2 ; done < 1.tmp

现在,有一个警告。 如果文件本来不是空的怎么办? 此循环将追加到文件,而无需先清除文件。 要解决此问题,您可以先使用以下方法清除文件:

>1.tmp2; while read line ; do echo $line | awk -F':' '{print $3}' >> 1.tmp2 ; done < 1.tmp

但是,如果我们确定循环产生的所有stdout都需要放入文件中,则只需将重定向移出循环即可。 这样,shell不必一直打开和关闭文件描述符。

while read line ; do echo $line | awk -F':' '{print $3}'; done < 1.tmp > 1.tmp2

请注意,这些选项尚未优化,但仍然可以使用。 经过优化的选项将是让awk自己进行逐行处理,如答案的第一个片段中所述。

我把你的台词放在了一个叫做“ tmp”的文件中。

这是命令:

awk -F"[:;]" '{print $3}' tmp

结果是:

SAMD00019077
SAMD00019076
SAMD00019075
SAMD00019074
SAMD00019073
SAMD00019072
SAMD00019071
SAMD00019070
SAMD00019069
SAMD00019005

"[:;]"部分是一个正则表达式,它定义了两个定界符:;

编辑:如果您想在while循环中进行操作,这是窍门:

while read line; do echo $line | awk -F"[:;]" '{print $3}';done < <(cat tmp)

似乎循环工作正常,但您仅重定向了文件中的最后一个元素。 >用于重定向文件中的输出,每次它将清空文件并清除以前的数据。 >>会将数据追加到文件的最后一行。

如果在循环中使用awk ,则很可能使用错误。 awk读取每一行并通过应用您指定的规则对其进行操作。 几乎不需要循环调用它。 您的awk声明:

awk -F: '{print $3}' 1.tmp
  • 使用-F:指定内部awk变量FS (字段分隔符)设置为':'字符,因此您的字段将由':'分隔。
  • '{print $3}'awk规则。 {...} )您可以根据需要设置任意多个规则。 在这里print $3仅打印第三个字段。
  • 1.tmp显然是您的输入文件(您可以根据需要指定任意多个输入文件)。

然后,您将管道显示head ,显示前10行(默认)。

您不清楚的唯一问题是是否要在单独的文件中捕获第三个字段。 (在您尝试过的某些事情中包括1.tmp2 )。 如果确实要在单独的文件中捕获第三个字段,则可以通过在awk规则本身内重定向到该文件来实现,例如

awk -F: '{print $3 > "1.tmp2"}' 1.tmp

现在,您在1.tmp2捕获了第三个字段,如果要检查,可以使用head 1.tmp2

但是,由于您的第三个字段还包含BioSample ID和其他字符,例如某些字段上的;SRA ,因此,如果不需要多余的字符,则需要删除仅留下BioSample ID那些BioSample ID awk具有大量的字符串函数 ,其sub可以根据您提供的正则表达式替换字段(或变量)。

在您使用示例输入的情况下,例如

$ cat 1.tmp
Identifiers:BioSample:SAMD00019077
Identifiers:BioSample:SAMD00019076
Identifiers:BioSample:SAMD00019075
Identifiers:BioSample:SAMD00019074
Identifiers:BioSample:SAMD00019073
Identifiers:BioSample:SAMD00019072
Identifiers:BioSample:SAMD00019071;SRA:DRS051563
Identifiers:BioSample:SAMD00019070;SRA:DRS051562
Identifiers:BioSample:SAMD00019069;SRA:DRS051561
...
Identifiers:BioSample:SAMD00019005;SRA:DRS051497
Identifiers:BioSample:SAMD00015713;SRA:DRS012785

您可以使用以下命令(检查字段数以跳过"..."行)来隔离没有';'BioSample ID ';' 然后使用以下命令将结果写入1.tmp2

$ awk -F: 'NF >= 3 {sub(/;.*/,"",$3); print $3 > "1.tmp2"}' 1.tmp

注意:在规则之前添加NF >= 3可以确保仅处理规则中NF (字段数)大于或等于3的行)

示例输出文件

$ cat 1.tmp2
SAMD00019077
SAMD00019076
SAMD00019075
SAMD00019074
SAMD00019073
SAMD00019072
SAMD00019071
SAMD00019070
SAMD00019069
SAMD00019005
SAMD00015713

正如其他人提到的awk 'script' > 1.tmp2 ,在循环中使用awk 'script' > 1.tmp2会导致当前行的awk输出在循环的每次迭代中覆盖1.tmp2的内容。 您可以通过在循环内使用>> 1.tmp2或在循环外移动> 1.tmp2来解决此问题(请参见下文),但是正确执行所需操作的方法就是根本不使用循环,而只需执行:

awk -F'[:;]' '{print $3}' 1.tmp > 1.tmp2

仅供参考,尽管如果您要使用循环(不要!),那么这两个都会产生您期望的输出:

while IFS= read -r line; do
    echo "$line" | awk -F'[:;]' '{print $3}'
done < 1.tmp > 1.tmp2

while IFS= read -r line; do
    echo "$line" | awk -F'[:;]' '{print $3}' >> 1.tmp2
done < 1.tmp

有关在shell中编写读取循环的详细信息,请参见为什么使用shell循环来处理文本认为不好的做法

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM