使用Shell脚本按“自定义顺序”重新排列文本文件的列

tp5buhyn  于 2022-11-16  发布在  Shell
关注(0)|答案(2)|浏览(156)

我有一个制表符分隔的文本文件,名为“data.txt”,看起来像
data.txt

col2   col3   col4   col1
val1   val5   val9   val13
val2   val6   val10  val14
val3   val7   val11  val15
val4   val8   val12  val16
...

有一个数组col_order = [col1, col2, col3, col4]
目标是使用shell脚本**根据数组“col_order”**中的顺序重新排列“data.txt”中的列。
最终输出

col1   col2   col3   col4
val13  val1   val5   val9
val14  val2   val6   val10
val15  val3   val7   val11
val16  val4   val8   val12

我目前的进度

awk 'BEGIN{ORS=RS="\n"; S=OFS="\t"}{for (i=1; i<=NF; i++) {f[$i] = i}{ print $(f["col1"]),$(f["col2"]),$(f["col3"]),$(f["col4"])}}' data.txt> data_corrected.txt

上面的语句按预期工作,但顺序是硬编码在语句中的,无法弄清楚如何从数组中获取顺序并将其传递到语句中。
我也愿意接受任何其他方法。

vjrehmav

vjrehmav1#

你能不能试一下。

cat script.bash
List=( col1 col2 col3 col4 )
##echo ${List[*]}
awk -v bash_arr_val="${List[*]}" '
BEGIN{
  num=split(bash_arr_val,array," ")
  for(i=1;i<=num;i++){
    array_with_bash_values_as_index[array[i]]=i
  }
}

FNR==1{
 for(i=1;i<=NF;i++){
   if($i in array_with_bash_values_as_index){
     actual_array[array_with_bash_values_as_index[$i]]=i
   }
 }
}
{
 for(i=1;i<=num;i++){
   printf("%s%s",$actual_array[i],i==NF?ORS:OFS)
 }
}
'  Input_file

输出如下。

col1 col2 col3 col4
val13 val1 val5 val9
val14 val2 val6 val10
val15 val3 val7 val11
val16 val4 val8 val12

EDIT by Ed Morton提供变量名称建议:

$ cat tst.awk
BEGIN{
    numOutFlds = split(bash_arr_val,outNr2name)
    for ( outNr=1; outNr<=numOutFlds; outNr++ ) {
        fldName = outNr2name[outNr]
        name2outNr[fldName] = outNr
    }
}

FNR==1 {
    for ( inNr=1; inNr<=NF; inNr++ ) {
        fldName = $inNr
        outNr = name2outNr[fldName]
        outNr2inNr[outNr] = inNr
    }
}
{
    for ( outNr=1; outNr<=numOutFlds; outNr++ ) {
        inNr = outNr2inNr[outNr]
        fldValue = $inNr
        printf "%s%s", fldValue, (outNr<numOutFlds ? OFS : ORS)
    }
}

$ awk -v bash_arr_val='col1 col2 col3 col4' -f tst.awk file
col1 col2 col3 col4
val13 val1 val5 val9
val14 val2 val6 val10
val15 val3 val7 val11
val16 val4 val8 val12

如果我只是为自己创建一个脚本,而不是使用这么多临时变量等来尽可能清楚地让其他人理解,那么我实际上是这样写的:

$ cat tst.awk
BEGIN{
    numOutFlds = split(bash_arr_val,outNr2name)
    for ( outNr=1; outNr<=numOutFlds; outNr++ ) {
        name2outNr[outNr2name[outNr]] = outNr
    }
}

FNR==1 {
    for ( inNr=1; inNr<=NF; inNr++ ) {
        f[name2outNr[$inNr]] = inNr
    }
}
{
    for ( outNr=1; outNr<=numOutFlds; outNr++ ) {
        printf "%s%s", $(f[outNr]), (outNr<numOutFlds ? OFS : ORS)
    }
}

$ awk -v bash_arr_val='col1 col2 col3 col4' -f tst.awk file
col1 col2 col3 col4
val13 val1 val5 val9
val14 val2 val6 val10
val15 val3 val7 val11
val16 val4 val8 val12
ygya80vv

ygya80vv2#

这不是最终解决方案,而是PO代码的改进,其中列顺序不是硬编码的:

#!/bin/bash

col_order=$1

awk -v col_order_string="$col_order" -v OFS='\t' '
  BEGIN {
    split(col_order_string, col_order, ",");
  }
  NR == 1 {
    for (i=1; i<=NF; i++) {
      f[$i] = i
    }
  }
  {
    s = ""
    for (i=1; i <= NF; i++) {
      printf "%s%s", s, $(f[col_order[i]]);
      s = OFS
    }
    printf "\n"
  }
' data.txt

调用为

./script 'col1,col2,col3,col4'

相关问题