带有输入的 Bash 函数失败 awk 命令

Posted

技术标签:

【中文标题】带有输入的 Bash 函数失败 awk 命令【英文标题】:Bash function with input fails awk command 【发布时间】: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 *** 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脚本,然后仅输出相关文件。 【参考方案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==1print FILENAME; headerCount=NF; NF>headerCountprint ENDFILEprint "#######\n"' /path/here/*.csv

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

awk -F, 'FNR==1print FILENAME"\nLine#\tLine"; headerCount=NF; NF>headerCountprint FNR"\t"$0 ENDFILEprint "#######\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==1print FILENAME; headerCount=NF; NF&gt;headerCountprint ENDFILEprint "#######\n"' /$folder/*.csv done 好的,我找到了问题。文件夹名称前面有一个正斜杠。删除它后,它现在可以工作了。 (将/$folder/*.csv 更改为$folder/*.csv 当前代码会查找列数多于标题的行。您可能还需要考虑较少的列数并将条件更改为NF != headerCount

以上是关于带有输入的 Bash 函数失败 awk 命令的主要内容,如果未能解决你的问题,请参考以下文章

使用 bash 命令 awk sed 等从脚本中提取参数字段

使用 awk 解析 nm 命令的输出 - Linux Bash

Bash - Linux - 在一行中找到匹配并打印到SED / Awk / Grep行的末尾

如何编写AWK命令来查找差异和比较

bash 中的 awk 命令:如何停止添加新行?

如何在单个 ssh 命令中使用 bash $(awk)?