Bash awk ~ 如何向awk提供要打印的字段列表?

3

初来乍到这个网站:我在分析 CSV 文件的数据时遇到了问题。

我写了一个小脚本,从 CSV 文件中读取输入并仅打印所需的字段:

awk -F,-v _sourcefile=$i -v title="\"${k}\"" -v box="_${j}_" -v score="$dock_score_column" -v hbond="${xp_terms_columns[0]}" -v electro="${xp_terms_columns[1]}" -v phoben="${xp_terms_columns[2]}" -v phobenhb="${xp_terms_columns[3]}" -v lowmw="${xp_terms_columns[4]}" -v rotpenal="${xp_terms_columns[5]}" -v lipophilicevdw="${xp_terms_columns[6]}" -v phobenpairhb="${xp_terms_columns[7]}" -v sitemap="${xp_terms_columns[8]}" -v penalties="${xp_terms_columns[9]}" -v pistack="${xp_terms_columns[10]}" -v hbpenal="${xp_terms_columns[11]}" -v expospenal="${xp_terms_columns[12]}" -v picat="${xp_terms_columns[13]}" -v clbr="${xp_terms_columns[14]}" -v zpotr="${xp_terms_columns[15]}"
'BEGIN {format ="%-8s %s %9s %9s %8s %10s %7s %10s %16s %14s %9s %11s %9s %9s %12s %7s %6s %7s\n"} $title_column〜title&&$ source_column〜_sourcefile&&$ source_column〜box
{printf format,$score,"= ",$hbond,$electro,$phoben,$phobenhb,$lowmw,$rotpenal,$lipophilicevdw,$phobenpairhb,$sitemap,$penalties,$pistack,$hbpena l,$expospenal,$picat,$clbr,$zpotr}' $file


这东西一团糟,但暂时它能够满足我的需求。
问题是:如何通过将存储在{xptermscolumns[@]}中的字段传递给它来简化它呢?

该文件是一个普通的csv文件,awk脚本的第一部分只是查找要打印的正确记录,我唯一的问题是需要声明16个不同的变量来进行打印。

我尝试在awk中使用数组,像这样:

awk -F, -v _sourcefile=$i -v title="\"${k}\"" -v box="_${j}_" -v terms="$xp_terms_columns" 'BEGIN{split(terms, array, " ")} $title_column ~ title && $source_column ~ _sourcefile && $sour ce_column ~ box { n=asorti(array, sorted); for (i=1;i<=n;i++) printf " " $sorted[i] }' $file

但没有成功,因为我无法使asorti按正确的顺序打印字段。


这是第一个脚本,已经清晰地编写出来以帮助解决这个问题,并为OP提供一个示例以便日后跟随。
awk -F,                                         \
    -v _sourcefile="$i"                         \
    -v title="\"${k}\""                         \
    -v box="_${j}_"                             \
    -v score="$dock_score_column"               \
    -v hbond="${xp_terms_columns[0]}"           \
    -v electro="${xp_terms_columns[1]}"         \
    -v phoben="${xp_terms_columns[2]}"          \
    -v phobenhb="${xp_terms_columns[3]}"        \
    -v lowmw="${xp_terms_columns[4]}"           \
    -v rotpenal="${xp_terms_columns[5]}"        \
    -v lipophilicevdw="${xp_terms_columns[6]}"  \
    -v phobenpairhb="${xp_terms_columns[7]}"    \
    -v sitemap="${xp_terms_columns[8]}"         \
    -v penalties="${xp_terms_columns[9]}"       \
    -v pistack="${xp_terms_columns[10]}"        \
    -v hbpenal="${xp_terms_columns[11]}"        \
    -v expospenal="${xp_terms_columns[12]}"     \
    -v picat="${xp_terms_columns[13]}"          \
    -v clbr="${xp_terms_columns[14]}"           \
    -v zpotr="${xp_terms_columns[15]}"          \
'
    BEGIN {
        format = "%-8s %s %9s %9s %8s %10s %7s %10s %16s %14s %9s %11s %9s %9s %12s %7s %6s %7s\n"
    }
    ($title_column ~ title) && ($source_column ~ _sourcefile) && ($source_column ~ box) {
        printf format, $score, "= ", $hbond, $electro, $phoben, $phobenhb, $lowmw,              \
                        $rotpenal, $lipophilicevdw, $phobenpairhb, $sitemap, $penalties,        \
                        $pistack, $hbpenal, $expospenal, $picat, $clbr, $zpotr
    }
' "$file"

awk 可以轻松地读取 2(或更多)个文件,因此您可以将所有参数和字段更简单地放在第二个(参数)文件中。https://stackoverflow.com/a/21875931/2836621 - Mark Setchell
@MarkSetchell 这看起来是个不错的想法,我从没想过。您能否详细解释一下?这对我来说有点太高级了。 - Gioele
在SO上有很多例子,只需在搜索框中输入[awk] FNR即可。这里有一个不错的例子https://stackoverflow.com/a/42851648/2836621 - Mark Setchell
当前代码引用了 $title_column$source_column 两列,但我没有看到 title_columnsource_column 的任何定义/赋值;这只是一个笔误吗(例如,在此代码副本中错误地省略了 -v title_column=...-v source_column=...)? - markp-fuso
@markp-fuso 是的,脚本中有其他变量声明用于查找要查找的额外字段,可能有更简洁的方法来实现这一点,但我的问题集中在16个不同变量的声明上。 - Gioele
3个回答

7

尝试这个(未经测试),使用任何awk:

awk -F,                                         \
    -v _sourcefile="$i"                         \
    -v title="\"${k}\""                         \
    -v box="_${j}_"                             \
    -v score="$dock_score_column"               \
    -v xp_terms_columns="${xp_terms_columns[*]}" \
'
    BEGIN {
        split(xp_terms_columns,xp," ")
        hbond           = xp[1]
        electro         = xp[2]
        phoben          = xp[3]
        phobenhb        = xp[4]
        lowmw           = xp[5]
        rotpenal        = xp[6]
        lipophilicevdw  = xp[7]
        phobenpairhb    = xp[8]
        sitemap         = xp[9]
        penalties       = xp[10]
        pistack         = xp[11]
        hbpenal         = xp[12]
        expospenal      = xp[13]
        picat           = xp[14]
        clbr            = xp[15]
        zpotr           = xp[16]

        format = "%-8s %s %9s %9s %8s %10s %7s %10s %16s %14s %9s %11s %9s %9s %12s %7s %6s %7s\n"
    }
    ($title_column ~ title) && ($source_column ~ _sourcefile) && ($source_column ~ box) {
        printf format, $score, "= ", $hbond, $electro, $phoben, $phobenhb, $lowmw,              \
                        $rotpenal, $lipophilicevdw, $phobenpairhb, $sitemap, $penalties,        \
                        $pistack, $hbpenal, $expospenal, $picat, $clbr, $zpotr
    }
' "$file"

以上假设您有某种原因要打印16个单独的字段,而不是范围、所有字段或输入后的字段或其他内容。

它还假设您尝试解决的问题是使用-v从shell数组设置16个awk变量,而不是拥有16个awk变量。

这可能实际上是您所需要的全部内容(再次未经测试):

awk -F,                                         \
    -v _sourcefile="$i"                         \
    -v title="\"${k}\""                         \
    -v box="_${j}_"                             \
    -v score="$dock_score_column"               \
    -v xp_terms_columns="${xp_terms_columns[*]}" \
'
    BEGIN {
        nxp  = split(xp_terms_columns,xp," ")
        nfmt = split("%-8s %s %9s %9s %8s %10s %7s %10s %16s %14s %9s %11s %9s %9s %12s %7s %6s %7s",fmt," ")
        if ( nxp != nfmt ) {
            print "field vs format count mismatch" | "cat>&2"
            exit 1
        }
    }
    ($title_column ~ title) && ($source_column ~ _sourcefile) && ($source_column ~ box) {
        printf "%-8s =", $score
        for ( i=1; i<=nxp; i++ ) {
            printf ("%s" fmt[i]), OFS, $(xp[i])
        }
        print ""
    }
' "$file"

我认为你的答案实际上完全解决了我的问题,因为我的问题是按照那个顺序精确地打印这16个字段,所以如果CSV因某种原因发生更改,则打印的数字将保持在精确的顺序中。您能否解释一下为什么要将数组分成xp和nxp? - Gioele
我正在将一个字符串传递给awk(将shell数组 'xp_terms_columns []'的内容作为空格分隔的“单词列表”),因为无法将shell数组传递给awk,所以我使用'split()'将该字符串转换为awk数组'xp', 以便稍后循环处理其内容。'nxp'不是另一个数组,而是'xp'中元素的数量。因此,'-v xp_terms_columns="${xp_terms_columns [*]}"'将shell数组'xp_terms_columns []'转换为字符串,并将其存储在awk变量'xp_terms_columns'中,然后'nxp = split(xp_terms_columns,xp," ")'将该字符串转换为awk数组'xp'。 - Ed Morton
这解决了我的问题,太神奇了!只是有一个跟进的快速问题:我根据自己的需求进行了修改,如果我想修改您的if检查以检查特定元素是否等于数字而不是空格,您会如何处理?if (16在xp中==“”)\ {printf“%-8s”,$score \ 退出0 \ } - Gioele
在这个论坛上,强烈不建议使用变色龙问题(Chameleon Questions),请提出一个新的问题。 - Ed Morton

1

我想解释一下为什么你的尝试失败了,即

-v terms="$xp_terms_columns"

没有像你想象的那样工作,请注意观察

arr=("Able" "Baker" "Charlie")
echo $arr

输出结果

Able

1

除非你真的有某种原因需要这些字段名称,假设shell数组包含一堆你想要打印出来的列数,那么一个简单的子进程可以让生活变得轻松得多:

xp_terms_columns=( $( jot 127 | rev | shuf | rev | head -n 16 ) )

echo "\n\t ${xp_terms_columns[*]} | ${#xp_terms_columns[*]}\n"

date | gawk -p- -be '

BEGIN {
        format = "%-8s %s %9s %9s %8s %10s %7s %10s %16s %14s %9s %11s %9s %9s %12s %7s %6s %7s\n"
}
($title_column  ~ title)       && 
($source_column ~ _sourcefile) && 
($source_column ~ box) {

    printf( format, $score, "= ", $'"$( 

      awk NF=NF OFS=', $' ORS= <<< "$xp_terms_columns[*]" 

                                           )"') }'
     99 66 25 62 72 16 12 108 69 117 8 22 98 19 61 93 | 16
Tue Mar 14 23:27:03 EDT 2023 =                                                                                                                                                                          
    # gawk profile, created Tue Mar 14 23:27:03 2023

    # BEGIN rule(s)

    BEGIN {
     1      format = "%-8s %s %9s %9s %8s %10s %7s %10s %16s %14s %9s %11s %9s %9s %12s %7s %6s %7s\n"
    }

    # Rule(s)

     1  ($title_column ~ title) && ($source_column ~ _sourcefile) && ($source_column ~ box) { # 1
     1      printf format, $score, "= ", $99, $66, $25, $62, $72, $16, $12, $108, $69, $117, $8, $22, $98, $19, $61, $93
    }

从那个 shell 数组中动态生成干净的代码而不必担心它们。


我不需要那些字段名称,我需要它们按照我想要的顺序打印出来,所以你的答案真的帮了我很大忙!您能否解释一下 $'"$(awk NF=NF OFS=', $' ORS= <<< "$xp_terms_columns[*]") }' 的含义? - Gioele
@Gioele: <<< ….some….stuff…., 大体上只是 echo ….some….stuff…. 的简写。* 告诉它按照 IFS 的第一个字符(默认为单个空格,除非用户自定义)来分割数组,这意味着每个数组元素将按顺序放置在其自己的“字段”(即列)中,以供 awk 使用…… - RARE Kpop Manifesto
@Gioele: "...ORS= 表示我不想在输出中添加尾随换行符。OFS=….请求输出由此新字符串分隔而不是默认的单个空格。最后,NF=NF指示awk将所有输入分隔符(或“seps”在awk术语中)转换为OFS,并输出它。这种方法大体上要快得多,因为不需要循环,所需的列号现在已经硬编码到脚本代码中。我曾经测试过相同的概念,将1 GB文件分成10亿个字段,每个字段占据1字节,并打印出所选列(如$892391)。效果非常好。" - RARE Kpop Manifesto
1
@Gioele :哦,还有一点:前面的$' "$(-第一个$应该属于外层awk代码中,因为这种方法是1短的,那就是它的作用。紧接着的'是为了结束第一部分的外部awk代码,它是单引号的,并且转换为双引号进行替换。这并不是所谓的ANSI C风格引用,比如$'\357' - RARE Kpop Manifesto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接