[英]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
。
我尝试的脚本:
while read line ; do echo $line | awk -F':' '{print $3}' > 1.tmp2 ; done < 1.tmp
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
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
我在这里阅读了帖子,看来我的问题与stdin
, stdout
和stderr
。
我尝试的解决方案,它给出了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.