【问题标题】:Last item in array not displayed from within loop数组中的最后一项未从循环内显示
【发布时间】:2019-04-16 03:26:34
【问题描述】:

数组的最后一项不能从循环中正确打印

在学习 shell 文本过滤器时,我制作了一个小 awk 脚本,通过提供内联标题来格式化 CSV 文件的输出。

从命令行调用包装器 shell 脚本,它真正做的只是包装 awk 脚本并将参数作为变量 regex 传递,这是搜索字符串。

脚本将第一条记录 (NR==1) 的字段存储到数组 heading 中。在 CSV 文件的正文中找到包含搜索字符串 regex 的记录后,脚本会将标题连接到适当的值。

csv.sh:

#!/bin/bash

awk -f ~/Scripts/csv.awk -v "regex=$1" $2

csv.awk:

BEGIN {FS=",";}
NR==1 { 
  for (i=1; i<=NF; i++) { 
    heading[i]=$i; 
  } 
}
NR>1 {
  if ($0 ~ regex) {
    for (i=1; i<=length(heading); i++) {
      if(length($i) > 0) { 
        print(heading[i] ": " $i)
      } 
    }
    print("")
  }
}

演示

ships.csv:

name,country,displacement,length,beam,commissioned
Yamato,Japan,65027,256,38.9,16 December 1941
USS Enterprise,United States of America,19800,251.4,33.4,12 May 1938
Bismarck,Germany,41700,251,36,24 August 1940
HMS Dreadnought,United Kingdom,18120,160.6,25,2 December 1906
USS Iowa,United States of America,46000,270.43,32.97,22 February 1943
HMS Vanguard,United Kingdom,45200,248.2,32.9,12 May 1946

调用

$ csv Enterprise ships.csv

预期输出

name: USS Enterprise
country: United States of America
displacement: 19800
length: 251.4
beam: 33.4
commissioned: 12 May 1938

终端输出:

name: USS Enterprise
country: United States of America
displacement: 19800
length: 251.4
beam: 33.4
: 12 May 1938

在我的 Linux 计算机 (Manjaro) 上,输出非常相似。但实际上,如果我在我的 Mac 上将输出通过管道传输到 pbcopy,则标题在粘贴后会显示:

name: USS Enterprise
country: United States of America
displacement: 19800
length: 251.4
beam: 33.4
commissioned
: 12 May 1938

【问题讨论】:

  • i&lt;=length(heading) 看起来很可疑。我猜你需要i&lt;=NF
  • 您的数据文件具有 DOS 风格的 \r\n 行结尾。 “commissioned”这个词实际上是commissioned\r,因此将光标发送到冒号之前的行首并打印值。
  • 格伦我不能为你的评论投票
  • 如果length(heading) 不等于NF 那么你就有问题所以i&lt;=length(heading) 可以简单地写成i&lt;=NF。同样if(length($i) &gt; 0) 相当于只写if ($i!="")

标签: awk


【解决方案1】:

根据 Glenn 的评论,请您尝试关注一下。

awk 'BEGIN {FS=",";}
{gsub(/\r/,"")}
NR==1 { 
  for (i=1; i<=NF; i++) { 
    heading[i]=$i; 
  } 
}
NR>1 {
  if ($0 ~ regex) {
    for (i=1; i<=length(heading); i++) {
      if(length($i) > 0) { 
        print(heading[i] ": " $i)
      } 
    }
    print("")
  }
}'  Input_file

【讨论】:

    【解决方案2】:

    将数组传递给length 函数是less portable

    如果您怀疑一个混乱的 csv 文件,其中您的标题可能与实际列不匹配,那么,一个可移植的选项是在开始时存储总列的计数,然后像下面这样重复使用它:

    NR==1 { 
      headercount=NF; # store the count
      for (i=1; i<=NF; i++) { 
        heading[i]=$i; 
      } 
    }
    NR>1 {
      if ($0 ~ regex) {
        for (i=1; i<=headercount; i++) { #Use the count
          if(length($i) > 0) {
            print(heading[i] ": " $i)
          }
        }
        print("")
      }
    }
    

    此外,如果您必须处理 dos 样式的文件结尾,您可以将脚本更改为:

    #!/bin/bash
    dos2unix -q "$2" # This makes sure that the input file has unix style file endings
    awk -f ./csv.awk -v "regex=$1" $2
    

    测试

    $ ./csv.sh HMS ships.csv 
    name: HMS Dreadnought
    country: United Kingdom
    displacement: 18120
    length: 160.6
    beam: 25
    commissioned: 2 December 1906
    
    name: HMS Vanguard
    country: United Kingdom
    displacement: 45200
    length: 248.2
    beam: 32.9
    commissioned: 12 May 1946
    

    如果您的标题被篡改,以至于它们没有委托列,那么这也可以在这种情况下无缝工作。

    测试

    $ ./csv.sh HMS ships.csv 
    name: HMS Dreadnought
    country: United Kingdom
    displacement: 18120
    length: 160.6
    beam: 25
    
    name: HMS Vanguard
    country: United Kingdom
    displacement: 45200
    length: 248.2
    beam: 32.9
    

    【讨论】:

    • 如果没有要连接的标头,我不希望循环生成格式化输出。因此,如果它是一个杂乱无章的 CSV 文件,其中只提供了 3 个标头,但数据有 5 列......但是 awk 的长度函数似乎可以理解上下文并给出数组长度的正确输出。
    • @JasperZanjani 我已修改答案以反映this 评论中提到的问题。
    猜你喜欢
    • 1970-01-01
    • 2021-08-31
    • 2021-03-03
    • 2013-02-07
    • 2020-10-23
    • 1970-01-01
    • 2017-03-31
    • 2016-01-31
    • 2013-01-30
    相关资源
    最近更新 更多