【问题标题】:Bash function with input fails awk command带有输入的 Bash 函数失败 awk 命令
【发布时间】:2021-09-02 23:52:57
【问题描述】:

我正在 BASH shell 脚本中编写一个函数,它应该从带有标题的 csv 文件返回行,逗号比标题多。这可能会发生,因为这些文件中有可能包含逗号的值。为了质量控制,我必须识别这些线以便以后清理它们。我目前拥有的:

#!/bin/bash

get_bad_lines () {
    local correct_no_of_commas=$(head -n 1 $1/$1_0_0_0.csv | tr -cd , | wc -c)
    local no_of_files=$(ls $1 | wc -l)
    for i in $(seq 0 $(( ${no_of_files}-1 )))
    do
        # Check that the file exist
        if [ ! -f "$1/$1_0_${i}_0.csv" ]; then
            echo "File: $1_0_${i}_0.csv not found!"
            continue
        fi
        # Search for error-lines inside the file and print them out
        echo "$1_0_${i}_0.csv has over $correct_no_of_commas commas in the following lines:"
        grep -o -n '[,]' "$1/$1_0_${i}_0.csv" | cut -d : -f 1 | uniq -c | awk '$1 > $correct_no_of_commas {print}'
    done
}

get_bad_lines products
get_bad_lines users

这个程序的输出现在是所有文件中所有行号的所有逗号计数, 我怀疑这是由于输入$1(文件夹名,即产品和用户)与参考$1awk 的调用冲突(我希望获取第一列是逗号的计数)循环中当前文件中的那一行)。

这是问题吗?如果是这样,是否可以通过使用不同的变量名而不是使用$1 引用第一列或文件夹名称来解决?

示例,当前输出:

      5 6667
      5 6668
      5 6669
      5 6670

(应该只显示包含超过 5 个逗号的文件的行)。

在 awk 调用中也尝试了变量声明,效果相同 (如Awk field variable clash with function argument 接受的答案) :

get_bad_lines () {
    local table_name=$1
    local correct_no_of_commas=$(head -n 1 $table_name/${table_name}_0_0_0.csv | tr -cd , | wc -c)
    local no_of_files=$(ls $table_name | wc -l)
    for i in $(seq 0 $(( ${no_of_files}-1 )))
    do
        # Check that the file exist
        if [ ! -f "$table_name/${table_name}_0_${i}_0.csv" ]; then
            echo "File: ${table_name}_0_${i}_0.csv not found!"
            continue
        fi
        # Search for error-lines inside the file and print them out
        echo "${table_name}_0_${i}_0.csv has over $correct_no_of_commas commas in the following lines:"
        grep -o -n '[,]' "$table_name/${table_name}_0_${i}_0.csv" | cut -d : -f 1 | uniq -c | awk -v table_name="$table_name" '$1 > $correct_no_of_commas {print}'
    done
}

【问题讨论】:

  • 谢谢,@Zilog80。我有点不清楚:CSV 文件中的值不包含引号,但单个值可能包含一个或多个逗号。如果没有人工检查,很难知道哪个值对应哪个字段。
  • 在格式正确的 CSV 文件中,字段内的逗号(或换行符)不是问题,因为 CSV 格式为此类情况提供了引用规则,它们应该不会造成任何麻烦。例如,CSV 行 FOO,"BAR,BAZ",BOOM 有 3 个字段,第二个字段是 BAR,BAZ
  • @user1934428 ,你是对的。不幸的是,我收到格式不正确的 CSV 文件,没有引号,例如FOO,BAR,BAZ,BOOM,其中BAR,BAZ对应单个字段
  • @GustavRasmussen:如果您知道此类字段的格式总是不正确,您可以简单地计算行中的逗号并选择那些数字不正确的行。如果某些行可能格式不正确,而其他行的逗号字段格式正确,则 IMO 最简单的方法是使用 CSV 解析器。
  • grep -o -n '[,]' 命令将为您期望该行中所有匹配逗号的每一行返回第一个逗号匹配。由于您需要严格计算逗号的数量,您应该将awkawk -v table_name="$table_name" -v num_comma=$correct_no_of_commas '/,/ {if (gsub(/,/, ",")>num_comma) print($0);}' "$table_name/${table_name}_0_${i}_0.csv" 结合起来。如果文件遇到不正确的逗号数,您也可以签入awk脚本,然后仅输出相关文件。

标签: linux string bash function awk


【解决方案1】:

您可以完全使用awk 来实现:

get_bad_lines () {
  find "$1" -maxdepth 1  -name "$1_0_*_0.csv" | while read -r my_file ; do
    awk -v table_name="$1" '
        NR==1 { num_comma=gsub(/,/, ""); }  
        /,/ { if (gsub(/,/, ",", $0) > num_comma) wrong_array[wrong++]=NR":"$0;}
        END { if (wrong > 0) {
                print(FILENAME" has over "num_comma" commas in the following lines:");
                for (i=0;i<wrong;i++) { print(wrong_array[i]); }
              } 
            }' "${my_file}"
  done
}

为什么你原来的 awk 命令不能只给出带有太多逗号的行,那是因为你在单引号 awk 语句 ('$1 &gt; $correct_no_of_commas {print}') 中使用了一个 shell 变量 correct_no_of_commas。因此,shell 没有替换,awk 按原样读取 "$correct_no_of_commas",并将其视为未定义的变量。更准确地说,awk 查找在 awk 脚​​本中未定义的变量 correct_no_of_commas,因此它是一个空字符串。 awk 将执行 $1 &gt; $"" 作为匹配条件,并且由于 $""$0 等价物,awk 会将 $1 中的计数与完整输入进行比较线。从数字的角度来看,完整的输入行具有&lt;tab&gt;&lt;count&gt;&lt;tab&gt;&lt;num_line&gt;, 的形式,因此awk 为0。因此,$1 &gt; $correct_no_of_commas 将始终为真。

【讨论】:

  • 谢谢,@Zilog80,这就是我想要的。似乎有一些语法问题,通过https://www.shellcheck.net/ 运行,输出:$ shellcheck myscript Line 1: get_bad_lines () { ^-- SC1009: The mentioned syntax error was in this function. ^-- SC1073: Couldn't parse this brace group. Fix to allow more checks. Line 2: awk -v table_name="$1" ( ^-- SC1036: '(' is invalid here. Did you forget to escape it? ^-- SC1056: Expected a '}'. If you have one, try a ; or \n in front of it. ^-- SC1072: Missing '}'. Fix any mentioned problems and try again.
  • 我的错,有一个错字,括号应该是第一行的单引号。您还可以使用 function get_bad_lines () { 来表示 bash 或保留 get_bad_lines () { 以符合 POSIX 标准。
  • 谢谢@Zilog80,这解决了掉毛问题。现在我得到了另一个文件名通配符:awk: fatal: cannot open file medications/medications_0_*_0.csv' 用于读取(没有这样的文件或目录)`
  • 用 for 循环遍历函数体周围的所有文件来修复它。
  • @GustavRasmussen 更新了关于多个文件的答案。
【解决方案2】:

您可以使用单个 awk 命令识别所有坏行

awk -F, 'FNR==1{print FILENAME; headerCount=NF;} NF>headerCount{print} ENDFILE{print "#######\n"}' /path/here/*.csv

如果您还想打印行号,请使用此

awk -F, 'FNR==1{print FILENAME"\nLine#\tLine"; headerCount=NF;} NF>headerCount{print FNR"\t"$0} ENDFILE{print "#######\n"}' /path/here/*.csv

【讨论】:

  • 收到错误:awk: can't open file /my_path/*.csv source line number 1。通配符* 不会扩展为文件名。为什么会这样?如何解决?
  • 您能提供您尝试运行的完整命令吗?我已经检查过它在 MacOS 和 Ubuntu 上的工作情况。
  • folders=(my_path my_path_2); for folder in $folders do awk -F, 'FNR==1{print FILENAME; headerCount=NF;} NF&gt;headerCount{print} ENDFILE{print "#######\n"}' /$folder/*.csv done
  • 好的,我找到了问题。文件夹名称前面有一个正斜杠。删除它后,它现在可以工作了。 (将/$folder/*.csv 更改为$folder/*.csv
  • 当前代码会查找列数多于标题的行。您可能还需要考虑较少的列数并将条件更改为NF != headerCount
猜你喜欢
  • 2022-08-12
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
  • 2016-10-23
  • 2018-01-16
  • 1970-01-01
相关资源
最近更新 更多