awk 在 for 循环中采用文件名而不是文件

awk 在 for 循环中采用文件名而不是文件

好的,所以我需要使用 awk 从某个文件中提取特定列,将其放入数组中然后对其进行排序,然后我还需要使用 awk 在这些提取的排序列中查找一些值,但现在我有我的 for 循环有一些问题:

for var in $1 $2
do
myarr=($(awk -v row=$3 -F';' '$row!="" {print $row}' $var))
sorted_array=( $( printf "%s\n" "${myarr[@]}" | sort -n ) )
echo "${sorted_array[@]} $var"
done

输出是:

 dbdump.csv
 dbdump2.csv

这是我想要从中提取列的两个 csv 文件的名称。如果有人可以提供某种解决方案,我将不胜感激,因为我需要这个脚本来搜索内容。另外,如果您可以建议一种算法更快的方法,请这样做,这只是我学习一些 bash 脚本并尝试组合一些代码。

输入文件包含这样的记录,其中有两个文件在第 3 列中没有匹配的值(这就是我的经理所说的):

1101590479;Frank Haemers;;20060310;1;RESI;;01;06;0007;0000000000;;CRM000;
1101590473;Van KetsmJan;;20060310;2;PROF;;01;08;;0000000000;75;CRM000;0686143950

这两个文件包含大约 500 万条记录。我有另一个文件,其中包含一定数量的模式,必须查找这两个巨大的 csv 文件,如果其中一个模式在任一文件中匹配,我需要输出到另一个文件中,例如:

echo "$pattern has been found in $file"

我需要对模式文本文件中找到的所有模式执行此操作

答案1

编写 shell 脚本时,最好先指定已验证的变量,最后指定文件名,这样您就可以改变指定的文件数量。在您的情况下,您有列号、其中包含模式的文件以及要处理的两个(或可能更多)文件名。因此,启动你的 Bash 脚本

#!/bin/bash
if [ $# -lt 2 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    echo ""
    echo "Usage: $0 [ -h | --help ]"
    echo "       $0 COLUMN PATTERNFILE [ FILE(s) ... ]"
    echo ""
    exit 0
fi

上面的子句if使用旧式 POSIX shell 格式,并且适用于dash(和其他 POSIX shell)以及大多数旧式shshell。其目的是,如果用户未指定任何命令行参数,或者仅指定-h--help,则脚本仅打印简短的帮助文本。

顺便说一句,您应该扩展帮助文本,因为在您忘记自己编写的内容后,两三个月内可以更轻松地了解它的用途。 (这种事经常发生在我身上,而且我也有过地段这样的脚本,所以我发现这种做法非常值得付出一点努力。)

接下来,提取所需的参数(只有一个,上面),并将shift它们取出,以便我们可以用来"$@"引用命令行上指定的所有文件名:

column=$1
patternfile="$2"
shift 2

请注意,我喜欢在我想要在 shell 中扩展的内容周围加上双引号,即使没有明确必要。这是因为我在使用 shell 脚本时遇到的大多数实际问题都是由于遗忘在必要时引用扩展。这种做法很容易记住,除了用烦人的鼻音“你实际上不需要那些双引号”之外,它们没有任何害处。

然后让我们用来awk处理输入文件:

awk -v column=$column \
  'BEGIN {
       RS = "[\t\v\f ]*(\r\n|\n\r|\r|\n|)[\t\v\f ]*"
       FS = "[\t\v\f ]*;[\t\v\f ]*"
   }

上面第一行末尾的反斜杠只是告诉 shell 该命令在下一行继续。另请注意,没有结束单引号',因此下面的行实际上是我们提供给的命令行字符串参数的延续awk

awk 中的规则BEGIN在处理文件之前执行。上面RS将记录分隔符设置为任何换行符约定,并在每行上包含任何前导或尾随空格。同样,字段分隔符是分号,但包括其周围的任何空格。因此,a ; b有两个字段,第一个字段a和第二个字段b都没有任何空格。

我使用以下习惯用法来跟踪正在处理哪个输入文件:

    FNR==1 { ++filenum }

如果只是意味着对于我们处理的每个输入文件中的第一条记录,我们会递增变量filenum。增加未初始化的变量与增加零相同,因此我们得到1第一个输入文件,依此类推。

我们只想记住第一个输入文件(我们的模式文件)中每一行的内容:

    filenum==1 { pattern[$0] }

awk 数组是关联数组,因此我们可以使用关联数组来保存已知模式。上面,我们使用了一个有趣的 awk 功能来发挥我们的优势:如果您尝试访问尚不存在的关联数组条目,awk 会创建它!

对于其余文件,我们只检查字段$column(提供给 awk 变量中的 awk scriptlet column)是否(完全)匹配第一个文件中看到的任何模式,如果是,我们打印整个记录:

    filenum > 1 && ($column in pattern) { printf "%s\n", $0 }

$column与 shell 脚本相比,上面的含义有所不同。这里,column是一个变量,并扩展到当前记录中第'个字段$column的值(但是,第零列是整个记录)。column语法foo in array是 awkism,用于检查是否array包含 key foo。因此,总的来说,对于第二个及更多输入文件,如果column第一个输入文件中列出了第一个字段值,则打印该记录。到标准输出。

我们仍然在awk命令行参数字符串中,并且需要关闭单引号字符串。我们还想为其提供文件名:

    ' "$patternfile" "$@"

这个 awk 脚本就结束了。

答案2

如果您只想获取模式列表和一组文件并打印出与特定列中的每个模式匹配的所有文件的名称,那么您只需要 GNU awk(Linux 上的默认设置):

awk -F';' '{
                if(NR==FNR){ 
                    p[$0]++; 
                    next
                } 
                if($3 in p){
                    printf "%s found in %s\n", $3,FILENAME; 
                    nextfile
                }
            }' patterns file1.csv file2.csv fileN.csv

解释

  • awk -F';':将字段分隔符设置为;
  • if(NR==FNR){ p[$0]++;next}:NR是当前输入行号,也是FNR当前文件的行号。仅当处理第一个文件时,两者才相等。因此,这会将模式文件(第一个文件)的每一行保存在数组中p并转到该next行。它只会针对模式文件运行。
  • if($3 in p){printf "%s found in %s\n", $3,FILENAME; nextfile:现在我们正在查看 csv 文件。如果第三个字段是数组中的元素之一p(如果它位于模式文件中),则打印第三个字段(模式)及其在其中找到的文件名。然后,跳到下一个文件。该FILENAME变量保存当前正在处理的文件的路径。这nextfile是一个 gawk 功能,正如它所说的那样:它跳到下一个要处理的文件。

例如,给定这些文件:

$ cat patterns 
foo
bar
baz

$ cat file1.csv 
blah;blah;foo;blah
blah;blah;foo;blah
blah;blah;foo;blah

$ cat file2.csv 
blah;blah;bar;blah

$ cat file3.csv 
blah;blah;baz;blah

您将得到以下输出:

$ awk -F';' '{if(NR==FNR){p[$0]++; next} if($3 in p){printf "%s found in %s\n", $3,FILENAME; nextfile}}' patterns file*csv 
foo found in file1.csv
bar found in file2.csv
baz found in file3.csv

如果每个模式可以存在于多个文件中,则可以使用稍微不同的方法:

awk -F';' '{
            if(NR==FNR){ 
                p[$0]++; 
                next
            } 
            if($3 in p && !seen[FILENAME][$3]){
                printf "%s found in %s\n", $3,FILENAME; 
                seen[FILENAME][$3]++
            }
        }' patterns file1.csv file2.csv fileN.csv

这次,没有,nextfile因为我们需要处理整个文件,并且每次在给定文件中找到模式时都会增加一个计数器,因此我们不会多次报告相同的模式。

因此,将file1.csv上面的内容改为:

$ cat file1.csv 
blah;blah;foo;blah
blah;blah;baz;blah
blah;blah;bar;blah
blah;blah;foo;blah

我们得到:

$ awk -F';' '{if(NR==FNR){p[$0]++; next} if($3 in p && !seen[FILENAME][$3]){printf "%s found in %s\n", $3,FILENAME; seen[FILENAME][$3]++}}' patterns file*csv 
foo found in file1.csv
baz found in file1.csv
bar found in file1.csv
bar found in file2.csv
baz found in file3.csv

如果这太慢(对于大文件可能是这样),您可以修改它,以便在已在文件中找到所有模式时停止读取文件:

awk -F';' '{
            if(NR==FNR){ 
                p[$0]++; 
                next
            } 
            if($3 in p && !seen[FILENAME][$3]){
                printf "%s found in %s\n", $3,FILENAME; 
                seen[FILENAME][$3]++
            }
            if( length(seen[FILENAME]) == length(p) ){
                nextfile
            }
           }' patterns file1.csv file2.csv fileN.csv

相关内容