如何使用 AWK 命令打印 csv 的第 1 列、第 3 列和第 9 列到第 12 列?

How to print column1,column3 and column 9 to column12 of the csv using AWK command?

下面这个命令完美地打印了 csv 文件的 9-12 列

grep -i introd *.csv | awk 'BEGIN { first = 9; last = 12 }{ for (i = first; i < last; i++) {printf("%s ", $i) } print $last }

但我还想打印第 1 列、第 3 列和第 30-35 列。

grep -i introd *.csv | awk 'BEGIN { first = 9; last = 12 }{ print ,  for (i = first; i < last; i++) {printf("%s ", $i) } print $last

对于少量的字段,很容易明确地完成它们,比如:

{ print ","","","","","","","","","",""," }

如果字段数不太大,这是首选解决方案。


如果 的字段数量更多(您可能与我不同,考虑将 12 个字段包含在其中),您始终可以创建一个关联数组,指示 应该打印哪个字段,包括允许像n-m这样的范围。答案的这一部分中的代码说明了,首先在 BEGIN 部分:

  • 用逗号分隔 COLS 变量,每个元素要么是单个值,要么是范围。
  • 对于每一个:
    • 拆分连字符以便建立范围界限。
    • 如果只有一个值,请将其添加到所需的字段列表中。
    • 如果超过一个,使用第一个和第二个作为包含边界并添加该范围内的每个字段。
  • 计算出所需的最低和最高字段。

然后,在每个输入行部分:

  • 打印出每个字段(从最低到最高)当且仅当它是标记为需要的字段之一时。

执行此操作的代码是:

BEGIN {
    split(COLS, ranges, ",")
    for (range in ranges) {
        split(ranges[range], values, "-")
        if (length(values) == 1) {
            use[values[1]] = 1
        } else {
            for (value = values[1]; value <= values[2]; value++) {
                use[value] = 1
            }
        }
    }

    hi = -1
    for (val in use) {
        if (hi  < 0) {
            lo = val
            hi = val
        } else if (val < lo) {
            lo = val
        } else if (val > hi) {
            hi = val
        }
    }
}
{
    sep = ""
    for (idx = lo; idx <= hi; idx++) {
        if (use[idx] == 1) {
            printf "%s%s", sep, $idx
            sep = ","
        }
    }
    print ""
}

此输出显示其工作原理:

pax> echo 101 202 303 404 505 606 707 808 909 | awk -v COLS=2-4,7 -f myprog.awk
202,303,404,707

就两个音符。首先,您不能使用该脚本来打印重复 个字段,例如1-5,3-7。我不允许这样做,因为您的问题中没有任何内容表明这是一项要求,但是,如果您确实需要该功能,则需要使用不同的方法。

其次,如果您不提供 COLS 或提供的格式不符合预期,则可能会产生意想不到的结果或失败。如果 reader 想让它真正防弹的话,我会把它留作练习 :-)

这两点都没有解决,因为我仍然认为第一个选项最适合您的特定情况。

这是一个通用解决方案,您可以在其中传递由 , 分隔的多个范围,并且范围值应在其内部由 - 分隔。另外,如果您想传递多个 .csv 文件,是的,您也可以将它们全部传递给该程序:*.csv

awk -v range="9-12,30-35" '
BEGIN{
  FS=OFS=","
  num=split(range,arr,",")
}
{
  delete arr1
  value=""
  for(i=1;i<=num;i++){
    split(arr[i],arr1,"-")
    for(start=arr1[1];start<=arr1[2];start++){
       value=(value?value OFS:"")$start
    }
  }
  print value
}
'  Input_file

说明:为以上添加详细说明。

awk -v range="9-12,30-35" '         ##Starting awk program from here and setting range variable which has different ranges, in this case 9 to 12 AND 30 to 35 field numbers here.
BEGIN{                              ##Starting BEGIN section of this program from here.
  FS=OFS=","                        ##Setting FS and OFS as comma here.
  num=split(range,arr,",")          ##Splitting range to arr array with separator of comma here.
}
{
  delete arr1                       ##Deleting arr1 for safer side.
  value=""                          ##Nullifying value here.
  for(i=1;i<=num;i++){              ##Running for loop till value of num here.
    split(arr[i],arr1,"-")          ##Splitting arr[i[ value into arr1 here with separator of - here.
    for(start=arr1[1];start<=arr1[2];start++){ ##Running for loop from arr[1] value to arr1[2] value.
       value=(value?value OFS:"")$start  ##Creating value which has fields value in it.
    }
  }
  print value                       ##printing value here.
}
'  Input_file                       ##Mentioning Input_file name here.

像这样的东西(由于没有 input/output 提供测试依据而未经测试)应该这样做:

awk -v r='1 3 9-12 30-35' '
    BEGIN {
        numRanges = split(r,ranges)
        for (rangeNr=1; rangeNr<=numRanges; rangeNr++) {
            n = split(ranges[rangeNr],begEnd,/-/)
            for (inFldNr=begEnd[1]; inFldNr<=begEnd[n]; inFldNr++) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for (outFldNr=1; outFldNr<=numOutFlds; outFldNr++) {
            inFldNr = out2in[outFldNr]
            printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file