【问题标题】:print output of user-defined function in awk gives unexpected token errorawk 中用户定义函数的打印输出给出了意外的令牌错误
【发布时间】:2018-08-10 13:59:26
【问题描述】:

我想灵活地将两个小 awk 的输出打印到 bash 管道,它们使用变量(它们最初工作)。我最初认为我可以将整个命令存储为变量本身,但对于一个它不起作用并且显然 (store awk command in a variable of bash script) 这不是一个好主意。所以我写了两个函数,但我在“完成”附近得到了一个“意外令牌”,但它的格式如上面的链接所示。

我的错误在哪里?

for coverage_file in */*.cov
do
    #gene_count=$(awk '{print $5}' $coverage_file |sort | uniq -c | wc -l) #this is apparently not a good idea
    #contig_count=$(awk '{print $1}' $coverage_file |sort | uniq -c | wc -l) #this is apparently not a good idea
    cmd_gene() { awk '{print $5}' $coverage_file |sort | uniq -c | wc -l }
    cmd_contig() { awk '{print $1}' $coverage_file |sort | uniq -c | wc -l }
    cmd_gene $coverage_file
    cmd_contig $coverage_file
    #print "we found", $gene_count, "genes on ",$contig_count" contigs
done

cov 文件如下所示:

k141_85332.3 4119 19 A5 phnM_031
k141_85332.3 4119 19 A5 phnM_031
k141_85332.3 4119 28 A1 phnM_031
k141_85332.3 4119 28 A1 phnM_031
k141_85332.3 4119 8 A2 phnM_031
k141_85332.3 4119 8 A2 phnM_031
k141_88684 267 5 B10 phnM_032
k141_88684 268 5 B10 phnM_032
k141_88684 269 5 B10 phnM_032
k141_88684 270 5 B10 phnM_032
k141_88684 271 5 B10 phnM_032
k141_88684 272 5 B10 phnM_032

编辑:这包括接受的答案 + 一种可能的方式来清楚地打印它:

#!/bin/bash

#define variables
gene="phnM"
threshold="5"

#define functions
cmd_gene() { awk '{print $5}' $1 |sort | uniq -c | wc -l ; } #semicolon is important here!
cmd_contig() { awk '{print $1}' $1 |sort | uniq -c | wc -l ; } #semicolon is important here!

#loop over files and print results (would be prettier with printf)
for coverage_file in */*.cov
do
    echo $gene" was found" $(cmd_gene "$coverage_file") "times on" $(cmd_contig "$coverage_file")" contigs with minimum coverage of" $threshold in $coverage_file
done

输出:

phnM was found 67 times on 65 contigs with minimum coverage of 5 in phnm/test.cov
phnM was found 3 times on 2 contigs with minimum coverage of 5 in test/test.cov

【问题讨论】:

  • 出现了意外的令牌错误,因为当您定义一个函数时,} 必须在它自己的行上或前面有 ;。例如:cmd_contig() { awk '{print $1}' $coverage_file |sort | uniq -c | wc -l; }
  • 嗯,很尴尬 ;-) 谢谢。你想把它作为答案发布吗?
  • 还不算尴尬;当然,我会添加作为答案,谢谢。

标签: function variables awk printing


【解决方案1】:

出现意外的标记错误是因为在定义函数时,} 必须在它自己的行上或前面有 ;。

另外,由于您在函数定义中使用了$coverage_file,因此您不必传递它。

for coverage_file in */*.cov
do
    cmd_gene() { awk '{print $5}' $coverage_file |sort | uniq -c | wc -l; }
    cmd_contig() { awk '{print $1}' $coverage_file |sort | uniq -c | wc -l; }
    cmd_gene 
    cmd_contig 
    #print "we found", $gene_count, "genes on ",$contig_count" contigs
done

如果您想在 for 循环之外定义函数,您可以使用 $1(不要与 awk 的 $1 混淆)并像以前一样传递 $coverage_file

编辑:以上示例

$ cat a.sh
cmd_gene() { awk '{print $5}' $1 |sort | uniq -c | wc -l; }
cmd_contig() { awk '{print $1}' $1 |sort | uniq -c | wc -l; }

for coverage_file in */*.cov
do
    cmd_gene $coverage_file
    cmd_contig $coverage_file
done

$ ls */*.cov
bf/a.cov

$ cat */*.cov
k141_85332.3 4119 19 A5 phnM_031
k141_85332.3 4119 19 A5 phnM_031
k141_85332.3 4119 28 A1 phnM_031
k141_85332.3 4119 28 A1 phnM_031
k141_85332.3 4119 8 A2 phnM_031
k141_85332.3 4119 8 A2 phnM_031
k141_88684 267 5 B10 phnM_032
k141_88684 268 5 B10 phnM_032
k141_88684 269 5 B10 phnM_032
k141_88684 270 5 B10 phnM_032
k141_88684 271 5 B10 phnM_032
k141_88684 272 5 B10 phnM_032

$ sh a.sh
       2
       2

【讨论】:

  • 考虑awk '{genes[$5]; contigs[$1]} END{print length(genes) ORS length(contigs)}' "$coverage_file" - 它会提供与两个函数相同的输出,而无需多次调用外部命令、管道等,当然您实际上根本不需要shell循环,只需调用一个小的、琐碎的 awk 脚本,您就可以完成所有工作。
【解决方案2】:

@jas 回答了你的问题,所以坚持下去,以下只是一种更好的方法来做你想做的事情,它太大/格式不适合发表评论:

awk '
BEGIN {
    gene = "phnM"
    threshold = "5"
}
{
    genes[$5]
    contigs[$1]
}
ENDFILE {
    printf "%s was found %d times on %d contigs with minimum coverage of %d in %s\n",
        gene, length(genes), length(contigs), threshold, FILENAME
    delete genes
    delete contigs
}
' */*.cov

上面将 GNU awk 用于 ENDFILE,但如果需要,它可以用于其他 awk,这是一个微不足道的更改:

awk '
BEGIN {
    gene = "phnM"
    threshold = "5"
}
FNR==1 { prt() }
{
    genes[$5]
    contigs[$1]
}
END { prt() }
function prt() {
    if (fname != "") {
        printf "%s was found %d times on %d contigs with minimum coverage of %d in %s\n",
            gene, length(genes), length(contigs), threshold, fname
        delete genes
        delete contigs
    }
    fname = FILENAME
}
' */*.cov

请参阅https://unix.stackexchange.com/questions/169716/why-is-using-a-shell-loop-to-process-text-considered-bad-practice 了解一些在处理文本时避免 shell 循环的原因。

【讨论】:

  • 感谢 Ed,这令人印象深刻,但我不明白它是如何工作的?为什么要使用 prt() 函数两次,一次只在第一个文件的第一条记录处?
  • 在 GNU awk 中,我们有 ENDFILE 构造,当我们到达每个输入文件的末尾时,该构造是正确的。因此,如果我们想在处理完每个文件后打印一些信息,我们只需将其放在 ENDFILE 块中。在其他 awk 中我们没有,所以我们需要模拟它。
  • FNR==1 是在读取每个文件的第一行时为真的条件,因此我可以在 FNR==1 块中打印有关 previous 文件的信息当前文件。这使我们在读取最终输入文件后仍然需要做一些事情,因此我们在 FNR==1 部分中所做的事情必须在 END 部分中复制,通常是通过一个公共函数(在本例中为prt())调用FNR==1 和 END 块。
  • 啊我明白了,所以如果 */*.cov 部分会找到 20 个输入文件,那么前 19 个文件的结果将由 FNR==1 { prt() } 打印,第 20 个结果来自 END { prt() } , 正确的?非常感谢您的解释
  • btw 请注意,在极端情况下,使用 ENDFILE 和非 gawk FNR==1/END 解决方法不会产生相同的输出,那就是如果您可以在中间有空输入文件的名单。 ENDFILE 将产生有关空文件的信息,其他方法不会。
猜你喜欢
  • 2014-12-26
  • 1970-01-01
  • 2020-04-21
  • 2013-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-07
  • 1970-01-01
相关资源
最近更新 更多