Bash:使用 CSV 文件构建循环并保存结果
Posted
技术标签:
【中文标题】Bash:使用 CSV 文件构建循环并保存结果【英文标题】:Bash: Working with CSV file to build a loop and save the result 【发布时间】:2021-10-10 17:55:38 【问题描述】:使用 Bash,我想从 CSV 文件中获取电子邮件地址列表,以在其上执行递归 grep
搜索,以查找在特定元数据 XML 文件中寻找匹配项的一堆目录,然后还进行统计增加我在整个目录树中为每个地址找到多少个结果(即更新同一个 CSV 文件中的计数字段)。
accounts.csv
看起来像这样:
更新以更准确地反映真实数据
email,date,bar,URL,"something else",tally
address@somewhere.com,21/04/2015,1.2.3.4,https://blah.com/,"blah blah",5
something@that.com,17/06/2015,5.6.7.8,https://blah.com/,"lah yah",0
another@here.com,7/08/2017,9.10.11.12,https://blah.com/,"wah wah",1
例如,如果我们将address@somewhere.com
从列表中放入$email
,则运行
grep -rl "$email" --include=\*_meta.xml --only-matching | wc -l
然后将该结果添加到tally
列。
目前我可以使用
获取该 CSV 文件的第一列(减去标题/第一行)awk -F"," 'print $1' accounts.csv | tail -n +2
但我不知道如何进行循环和以及将结果写回 CSV 文件...
例如,如果我们运行another@here.com
grep -rl "$email" --include=\*_meta.xml --only-matching | wc -l
结果是17
,我怎样才能将那行更新为:
another@here.com,7/08/2017,9.10.11.12,https://blah.com/,"wah wah",17
awk
或 sed
是否有可能?
这就是我要做的:
#!/bin/bash
# make temporary list of email addresses
awk -F"," 'print $1' accounts.csv | tail -n +2 > emails.tmp
# loop over each
while read email; do
# count how many uploads for current email address
grep -rl "$email" --include=\*_meta.xml --only-matching | wc -l
done < emails.tmp
XML 元数据如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<identifier>SomeTitleNameGoesHere</identifier>
<mediatype>audio</mediatype>
<collection>opensource_movies</collection>
<description>example <br /></description>
<subject>testing</subject>
<title>Some Title Name Goes Here</title>
<uploader>another@here.com</uploader>
<addeddate>2017-05-28 06:20:54</addeddate>
<publicdate>2017-05-28 06:21:15</publicdate>
<curation>[curator]email@address.com[/curator][date]20170528062151[/date][comment]checked for malware[/comment]</curation>
</metadata>
【问题讨论】:
电子邮件地址的域部分可能包含逗号(请参阅here),因此我认为您不能简单地使用带有逗号的 awk 作为字段分隔符。 这是一个非常史诗般的边缘案例,不适用于我的数据,但可以肯定。 :+1: 【参考方案1】:如何进行循环以及将结果写回 CSV 文件
awk
自动循环。您可以通过分配来更改任何字段。因此,要更改计数字段(每行中的第 6 个),您需要执行 $6 = ...
。awk
是适用于许多场景的好工具。现在花几分钟时间学习一个简短的教程,您可能可以在未来节省大量时间。
唯一重要的部分是将grep
的输出转换为awk
。
以下脚本将每个计数增加包含给定电子邮件地址的 *_meta.xml
文件的计数:
awk -F, -v OFS=, -v q=\' 'NR>1
cmd = "grep -rlFw " q $1 q " --include=\\*_meta.xml | wc -l";
cmd | getline c;
close(cmd);
$6 = c
1' accounts.csv
为简单起见,我们假设文件名没有换行符,电子邮件地址没有'
。
为了减少可能的误报,我还在您的grep
命令中添加了-F
和-w
选项。
-F
搜索文字字符串;没有它,搜索a.b@c
会误报axb@c
和a-b@c
等内容。
-w
只匹配整个单词;没有它,搜索b@c
会误报ab@c
。这不是 100% 安全的,因为a-b@c
仍然会给出误报,但如果不了解您的 xml 文件结构的更多信息,我们无法解决此问题。
【讨论】:
我看到这成功计算了计数,但它是否也将这些更改写入accounts.csv
?它对我不起作用。旧数据保持不变。
不,它只是打印更新的 linux 内容。要么将该输出写入一个新文件awk -F, ... accounts.csv > newAccounts.csv
,然后将其重命名为mv newAccount.csv accounts.csv
,要么使用GNU awk 的就地选项gawk -i inplace -F, ... accounts.csv
PS你有什么好的awk
教程推荐吗?
不,抱歉。不知道哪个教程好。【参考方案2】:
减少 grep 数量的管道:
grep -rHo --include=\*_meta.xml -f <(awk -F, 'NR > 1 print $1' accounts.csv) \
| gawk -F, -v OFS=',' '
NR == FNR
# store the filenames for each email
if (match($0, /^([^:]+):(.+)/, m)) tally[m[2]][m[1]]
next
FNR > 1 $4 = length(tally[$1])
1
' - accounts.csv
【讨论】:
太棒了!不应该是$6 = length(tally[$1])
吗?
它used to be $4【参考方案3】:
这是一个使用单个awk
命令来实现此目的的解决方案。与其他解决方案相比,此解决方案将高性能,因为它对每个 XML 文件只扫描一次,以查找 CSV 文件第一列中找到的所有电子邮件地址。此外,它不会调用任何外部命令或在任何地方生成 sub0shell。
这应该适用于任何版本的awk
。
cat srch.awk
# function to escape regex meta characters
function esc(s, tmp)
tmp = s
gsub(/[&+.]/, "\\\\&", tmp)
return tmp
BEGIN FS=OFS=","
# while processing csv file
NR == FNR
# save escaped email address in array em skipping header row
if (FNR > 1)
em[esc($1)] = 0
# save each row in rec array
rec[++n] = $0
next
# this block will execute for eaxh XML file
# loop each email and save count of matched email in array em
# PS: gsub return no of substitutionx
for (i in em)
em[i] += gsub(i, "&")
END
# print header row
print rec[1]
# from 2nd row onwards split row into columns using comma
for (i=2; i<=n; ++i)
split(rec[i], a, FS)
# 6th column is the count of occurrence from array em
print a[1], a[2], a[3], a[4], a[5], em[esc(a[1])]
将其用作:
awk -f srch.awk accounts.csv $(find . -name '*_meta.xml') > tmp && mv tmp accounts.csv
【讨论】:
这将比反复递归地搜索目录树的性能要好得多。 您能解释一下这里发生了什么吗? 抱歉,您可能还需要更新它以反映对 CSV 的更改... 我已经添加了解释以及对我的答案的更新,以解决您更改的要求。请检查并告诉我。 大代码块是否在awk
代码块之上?至少它似乎不是 Bash ......我正在努力理解如何实现这一点。我是否将代码块的内容保存为srch.awk
或其他内容?我认为这个答案需要更多细节。【参考方案4】:
逐行处理accounts.csv
并替换accounts.new.csv
中的数据进行比较的脚本。
#! /bin/bash
file_old=accounts.csv
file_new=$file_old/csv/new.csv
delimiter=","
x=1
# Copy file
cp $file_old $file_new
while read -r line; do
# Skip first line
if [[ $x -gt 1 ]]; then
# Read data into variables
IFS=$delimiter read -r address foo bar tally somethingelse <<< $line
cnt=$(find . -name '*_meta.xml' -exec grep -lo "$address" \; | wc -l)
# Reset tally
tally=$cnt
# Change line number $x in new file
sed "$xs/.*/$address $foo $bar $tally $somethingelse/; $xs/ /$delimiter/g" \
-i $file_new
fi
((x++))
done < $file_old
输入输出:
# Input
$ find . -name '*_meta.xml' -exec cat \; | sort | uniq -c
2 address@somewhere.com
1 something@that.com
$ cat accounts.csv
email,foo,bar,tally,somethingelse
address@somewhere.com,bar1,foo2,-1,blah
something@that.com,bar2,foo3,-1,blah
another@here.com,bar4,foo5,-1,blah
# output
$ ./test.sh
$ cat accounts.new.csv
email,foo,bar,tally,somethingelse
address@somewhere.com,bar1,foo2,2,blah
something@that.com,bar2,foo3,1,blah
another@here.com,bar4,foo5,0,blah
【讨论】:
以上是关于Bash:使用 CSV 文件构建循环并保存结果的主要内容,如果未能解决你的问题,请参考以下文章
Jmeter-BeanShell PostProcessor提取请求及响应结果并保存到本地文件
R语言使用for循环绘制多个模型的DCA(Decision Curve Analysis)曲线并保存特定分辨率的DCA曲线的结果文件