【问题标题】:Vlookup-like function using awk in ksh在 ksh 中使用 awk 的类似 Vlookup 的函数
【发布时间】:2017-02-02 07:56:07
【问题描述】:

免责声明:

1) 英语是我的第二语言,所以请原谅您可能发现的任何语法错误。尽管有这些,我非常有信心您将能够理解我的需求。

2) 我在这个网站上找到了几个解决与我类似的问题/问题的示例,但遗憾的是我无法弄清楚需要进行哪些修改才能满足我的需求。

3) 你会在这里和那里找到一些大写字母的文本。这当然不是我对你“大喊大叫”,而只是让部分文本脱颖而出的一种方式。请不要认为这是不礼貌的行为。

4) 对于那些活着了解这部中篇小说的人,在此先感谢您的耐心,即使您无法/感觉不想帮助/帮助我。我在此声明的事实是,在浏览该网站一段时间后,我注意到愿意提供帮助的人最常见的“抱怨”似乎是这些人提供的信息不足(和/或质量欠佳)寻求帮助。然后,如果需要的话,我宁愿被指责措辞过多……至少,这不是一种常见的冒犯……


“问题”:

我有 2 个文件(a 和 b 用于简化)。文件 a 有 7 列,以逗号分隔。文件 b 有 2 列,以逗号分隔。

我需要什么:每当文件 a 的第 7 列中的数据匹配 -EXACT MATCHES ONLY- 文件 b 的第 1 列上的数据时,一个新行,包含文件 a 的整行加上文件 b 的第 2 列将被附加到一个新文件“c”中。

--- 底部注释中的更多信息 ---

文件:

Server Name,File System,Path,File,Date,Type,ID
horror,/tmp,foldera/folder/b/folderc,binaryfile.bin,2014-01-21 22:21:59.000000,typet,aaaaaaaa
host1,/,somefolder,test1.txt,2016-08-18 00:00:20.000000,typez,11111111
host20,/,somefolder/somesubfolder,usr.cfg,2015-12-288 05:00:20.000000,typen,22222222
hoster,/lol,foolie,anotherfile.sad,2014-01-21 22:21:59.000000,typelol,66666666
hostie,/,someotherfolder,somefile.txt,2016-06-17 18:43:12.000000,typea,33333333
hostile,/sad,folder22,higefile.hug,2016-06-17 18:43:12.000000,typeasd,77777777
hostin,/var,folder30,someotherfile.cfg,2014-01-21 22:21:59.000000,typo,44444444
hostn,/usr,foldie,tinyfile.lol,2016-08-18 00:00:20.000000,typewhatever,55555555
server10,/usr,foldern,tempfile.tmp,2016-06-17 18:43:12.000000,tipesad,99999999

文件 b:

ID,Size
11111111,215915
22222222,1716
33333333,212856
44444444,1729
55555555,215927
66666666,1728
88888888,1729
99999999,213876
bbbbbbbb,26669080

预期文件 c:

Server Name,File System,Path,File,Date,Type,ID,Size
host1,/,somefolder,test1.txt,2016-08-18 00:00:20.000000,typez,11111111,215915
host20,/,somefolder/somesubfolder,usr.cfg,2015-12-288 05:00:20.000000,typen,22222222,1716
hoster,/lol,foolie,anotherfile.sad,2014-01-21 22:21:59.000000,typelol,66666666,1728
hostie,/,someotherfolder,somefile.txt,2016-06-17 18:43:12.000000,typea,33333333,212856
hostin,/var,folder30,someotherfile.cfg,2014-01-21 22:21:59.000000,typo,44444444,1729
hostn,/usr,foldie,tinyfile.lol,2016-08-18 00:00:20.000000,typewhatever,55555555,215927
server10,/usr,foldern,tempfile.tmp,2016-06-17 18:43:12.000000,tipesad,99999999,213876

补充说明:

0) 请注意文件 a 中 ID 为“aaaaaaaa”的行如何没有进入文件 c,因为文件 b 中不存在 ID“aaaaaaaa”。同样,文件 b 中 ID 为“bbbbbbbb”的行不会进入文件 c,因为文件 a 中不存在 ID“bbbbbbbb”,因此从一开始就不会查找它。

1) 由于机密性问题,数据显然是完全制作出来的,尽管提供的示例与真实文件的外观非常相似。

2) 我添加标题只是为了更好地了解数据的性质。真正的文件没有它,所以不需要在源文件中跳过它们,也不需要在目标文件中创建它。

3) 这两个文件都默认排序,这意味着 ID 将在文件 b 中正确排序,而它们很可能在文件 a 中被打乱。文件 c 最好遵循文件 a 的顺序(尽管我可以稍后进行操作以满足我的需要,所以不用担心,只要代码能满足我的需要并且不会通过组合错误的行来弄乱数据)。

4) 非常非常非常重要:

4.a) 我已经有一个使用“cat”、“grep”、“while”和“if”来完成工作的“工作”ksh 代码(附在下面)。它具有 160K 行样本文件的魅力(好吧,可以接受)(它能够输出 60K 行 - 大约 - 一个小时,在投影中,这将产生可接受的“20 天”来产生 3000 万行 [KEEP ON READING]),但不知何故(我有足够的处理器和内存容量) cat 和/或 grep 似乎正在努力处理现实生活中的 500 万行文件(文件 a 和 b 每个都可以有多达 3000 万行,所以这是结果文件中最大可能的行数,即使假设文件 a 中 100% 的行都在文件 b) 中找到匹配,并且 c 文件现在每 24 小时只提供几百行。

4.b) 有人告诉我,awk 更强大,应该会成功,而我使用的更弱的命令似乎会失败。我还被告知使用数组可能是我的性能问题的解决方案,因为所有数据都会立即上传到内存并从那里工作,而不必 cat | grep 文件 b 的次数与文件 a 中的行数一样多,就像我目前正在做的那样。

4.c) 我在AIX上工作,所以我只有sh和ksh,没有bash,所以我不能使用后者提供的数组工具,这就是我想到AWK的原因,这也是我认为的事实AWK 可能“更强大”,尽管我可能(可能?)错了。

现在,我向你展示一段精彩的 ksh 代码(这里显然是讽刺,虽然我喜欢你在脑海中短暂地想象猴子举起并向所有其他丛林爬行者展示他们的形象的想法未来的狮子王)我已经成功地发展了(阅读这段代码时,你可以尽情大笑,反正我听不到你的声音,所以没有感情受到伤害:P):

cat "${file_a}" | while read -r line_file_a; do

    server_name_file_a=`echo "${line_file_a}" | awk -F"," '{print $1}'`
    filespace_name_file_a=`echo "${line_file_a}" | awk -F"," '{print $2}'`
    folder_name_file_a=`echo "${line_file_a}" | awk -F"," '{print $3}'`
    file_name_file_a=`echo "${line_file_a}" | awk -F"," '{print $4}'`
    file_date_file_a=`echo "${line_file_a}" | awk -F"," '{print $5}'`
    file_type_file_a=`echo "${line_file_a}" | awk -F"," '{print $6}'`
    file_id_file_a=`echo "${line_file_a}" | awk -F"," '{print $7}'`

    cat "${file_b}" | grep ${object_id_file_a} | while read -r line_file_b; do

        file_id_file_b=`echo "${line_file_b}" | awk -F"," '{print $1}'`
        file_size_file_b=`echo "${line_file_b}" | awk -F"," '{print $2}'`

        if [ "${file_id_file_a}" = "${file_id_file_b}" ]; then

            echo "${server_name_file_a},${filespace_name_file_a},${folder_name_file_a},${file_name_file_a},${file_date_file_a},${file_type_file_a},${file_id_file_a},${file_size_file_b}" >> ${file_c}.csv

        fi

    done

done

最后一个补充说明,以防万一您想知道:

“if”部分的构建不仅是为了表达输出行,而且它具有双重目的,同时防止任何可能源自 grep 的误报,IE 100 匹配 1000(请记住, ,正如我之前提到的,我在 AIX 上工作,所以我的 grep 没有 GNU 的 -m 开关,我需要完全匹配/绝对匹配)。

你已经到了终点。恭喜!你被授予耐心勋章。

【问题讨论】:

  • 我很感激在这个 Q 中付出的努力,我希望花时间把它写下来帮助你澄清你的想法,但是 3 小时后,没有人回复你的 Q(也许后来,是周末),你的 Q 可能太复杂了。您现在应该花时间把它变成Minimal, Complete and Verifiable Example。此外,您的问题听起来有点像join 实用程序问题。也许如果您对数据进行预处理以将第二行折叠到 tmp 副本中的第一行,然后您可以使用 join 创建输出和后期处理。
  • 如果您能抽出时间,请通过grymoire.com/Unix/Awk.html 上的 awk“教程”学习。您设计的过程将起作用,但效率非常低,可能会被 1 个 awk 程序替换(可能在与其他一些东西的管道中)(如果join 没有回答您的问题)。祝你好运。
  • 文件“b”的第一个字段是唯一的,还是可以找到多次出现的相同值?此外,将运行此程序的系统中有多少物理内存,如果包含所有文件“b”的内存数组超过您的默认限制,您是否能够影响每个进程的限制?
  • Shellter,我一定会考虑您的 cmets 以备将来参与。根据 AWK 如何解决我的问题,您可以打赌我会更深入地研究它,因为我目前仅将它用作“使用列的工具”并最终进行一些数学运算。一旦你驯服了它,它显然是一头强大的野兽。
  • Gothi,我认为现在已经回答了这个问题(有关详细信息,请参阅我对 Jas 的回复),无论如何,文件 b 中的第一个字段是唯一的,是的,受影响的系统有 20GB 的物理内存。至于限制,我不能肯定地说,因为我无法以 root 用户身份访问,尽管我敢说这些限制是无限的,或者是最大可能的价值。 IMO,我遇到的性能问题不是源于源文件的大小,也不是它们拥有的行/记录的数量,而是因为我的代码效率低下,调用 cat 和 grep 进程数百万次。

标签: arrays awk ksh vlookup aix


【解决方案1】:
$ cat stuff.awk
BEGIN { FS=OFS="," }
NR == FNR { a[$1] = $2; next }
$7 in a { print $0, a[$7] }

注意将文件提供给 awk 命令的顺序,首先是 b,然后是 a

$ awk -f stuff.awk b.txt a.txt
host1,/,somefolder,test1.txt,2016-08-18 00:00:20.000000,typez,11111111,215915
host20,/,somefolder/somesubfolder,usr.cfg,2015-12-288 05:00:20.000000,typen,22222222,1716
hoster,/lol,foolie,anotherfile.sad,2014-01-21 22:21:59.000000,typelol,66666666,1728
hostie,/,someotherfolder,somefile.txt,2016-06-17 18:43:12.000000,typea,33333333,212856
hostin,/var,folder30,someotherfile.cfg,2014-01-21 22:21:59.000000,typo,44444444,1729
hostn,/usr,foldie,tinyfile.lol,2016-08-18 00:00:20.000000,typewhatever,55555555,215927
server10,/usr,foldern,tempfile.tmp,2016-06-17 18:43:12.000000,tipesad,99999999,213876

【讨论】:

  • Jas,你不知道你在多大程度上让我的生活更轻松。你的小怪物在 4 小时内吃掉了 500 万行。我无语。我从心底向你和你快乐的舞熊致敬!
  • 感谢您的反馈,@StuffCompiler,熊和我非常高兴它对您有用!
  • @StuffCompiler 如果这解决了您的问题,您应该接受 jas 的回答 :)
【解决方案2】:

编辑:更新计算 您可以尝试预测您调用另一个程序的频率:
文件 a 中的每一行至少 7 awk + ​​1 cat + 1 grep 乘以文件 b 中每一行的 2 awk。 (9 * 160.000)。
对于文件 b:2 个 awk,每次命中打开一个文件,关闭一个文件。如果输出为 60K,则为 4 * 60.000。

代码中的一个小改动可以将其更改为“仅”160.000 次 grep:

cat "${file_a}" | while IFS=, read -r server_name_file_a \
   filespace_name_file_a folder_name_file_a file_name_file_a \
   file_date_file_a file_type_file_a file_id_file_a; do
   grep "${object_id_file_a}" "${file_b}" | while IFS="," read -r line_file_b; do
        if [ "${file_id_file_a}" = "${file_id_file_b}" ]; then
            echo "${server_name_file_a},${filespace_name_file_a},${folder_name_file_a},${file_name_file_a},${file_date_file_a},${file_type_file_a},${file_id_file_a},${file_size_file_b}" 
        fi
    done
done >> ${file_c}.csv

好吧,用你的 160K 文件试试这个,看看它有多快。
在我解释这仍然是错误的方式之前,我将进行另一个小的改进:我将把 while 循环的 cat 移到末尾(在 done 之后)。

while IFS=, read -r server_name_file_a \
   filespace_name_file_a folder_name_file_a file_name_file_a \
   file_date_file_a file_type_file_a file_id_file_a; do
   grep "${object_id_file_a}" "${file_b}" | while IFS="," read -r line_file_b; do
        if [ "${file_id_file_a}" = "${file_id_file_b}" ]; then
            echo "${server_name_file_a},${filespace_name_file_a},${folder_name_file_a},${file_name_file_a},${file_date_file_a},${file_type_file_a},${file_id_file_a},${file_size_file_b}" 
        fi
    done
done < "${file_a}" >> ${file_c}.csv

该解决方案的主要缺点是您正在使用 grep 一次又一次地读取文件 a 中每一行的完整 file_b。

这个解决方案在性能上是一个很好的改进,但是对于grep 仍然有很多开销。使用awk 可以找到另一个巨大的改进。
最好的解决方案是使用What is "NR==FNR" in awk? 中解释的awk,并在@jas 的答案中找到。 这只是一个系统调用,两个文件都只读取一次。

【讨论】:

  • Walter A,Jas 代码就像一个魅力。无论哪种方式,我衷心感谢您抽出宝贵时间阅读我的问题并提出改进我低效代码的建议。这是我第一次参与这个网站,它确实让我对人性产生了一点希望。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多