如何在'for'循环中用另一个csv清理csv?

时间:2013-08-25 10:54:30

标签: linux bash shell grep csv

我不是Linux专家,通常在这种情况下PHP会更合适......但是由于我在Bash中编写它的情况发生了:)

我有以下.sh,它运行在当前文件夹中的所有.csv文件中并执行一堆命令。 目标:清理.csv文件中的电子邮件列表(实际上不是.csv,但实际上只是.txt文件)。

for file in $(find . -name "*.csv" ); do
echo "====================================================" >> db_purge_log.txt
echo "$file" >> db_purge_log.txt
echo "----------------------------------------------------" >> db_purge_log.txt
echo "Contacts BEFORE purge:" >> db_purge_log.txt
wc -l $file | cut -d " " -f1 >> db_purge_log.txt
echo " " >> db_purge_log.txt
cat $file | egrep -v "xxx|yyy|zzz" | grep -v -E -i '([0-z])\1{2,}' | uniq | sort -u  > tmp_file
mv tmp_file $file ;
echo "Contacts AFTER purge:" >> db_purge_log.txt
wc -l $file | cut -d " " -f1 >> db_purge_log.txt
done

现在问题是:

我想在此循环中间的某处添加一个命令,使用另一个.csv文件作为抑制列表,这意味着 - 在该抑制列表中找到完美匹配的每一行 - 从$file删除。 / p>

此时我的大脑被卡住了,我想不出解决方案。说实话,我没有管理在2个不同的文件上使用sortgrep并导出到第3个文件而没有完全消除跨两个文件的重复行,所以我最终得到的数据少得多。

非常感谢任何帮助!

2 个答案:

答案 0 :(得分:4)

清理

在向脚本添加功能之前,需要清理现有脚本 - 很多。

I / O重定向 - 不要重复自己

当我看到像这样的墙到墙I / O重定向时,我想哭 - 这不是你怎么做的!你有三个选择来避免这一切:

for file in $(find . -name "*.csv" )
do
    echo "===================================================="
    echo "$file"
    echo "----------------------------------------------------"
    echo "Contacts BEFORE purge:"
    wc -l $file | cut -d " " -f1
    echo " "
    cat $file | egrep -v "xxx|yyy|zzz" | grep -v -E -i '([0-z])\1{2,}' | uniq | sort -u  > tmp_file
    mv tmp_file $file ;
    echo "Contacts AFTER purge:"
    wc -l $file | cut -d " " -f1
done  >> db_purge_log.txt

或者:

{
for file in $(find . -name "*.csv" )
do
    echo "===================================================="
    echo "$file"
    echo "----------------------------------------------------"
    echo "Contacts BEFORE purge:"
    wc -l $file | cut -d " " -f1
    echo " "
    cat $file | egrep -v "xxx|yyy|zzz" | grep -v -E -i '([0-z])\1{2,}' | uniq | sort -u  > tmp_file
    mv tmp_file $file ;
    echo "Contacts AFTER purge:"
    wc -l $file | cut -d " " -f1
done
}  >> db_purge_log.txt

甚至:

exec >>db_purge_log.txt   # By default, standard output will go to db_purge_log.txt
for file in $(find . -name "*.csv" )
do
    echo "===================================================="
    echo "$file"
    echo "----------------------------------------------------"
    echo "Contacts BEFORE purge:"
    wc -l $file | cut -d " " -f1
    echo " "
    cat $file | egrep -v "xxx|yyy|zzz" | grep -v -E -i '([0-z])\1{2,}' | uniq | sort -u  > tmp_file
    mv tmp_file $file ;
    echo "Contacts AFTER purge:"
    wc -l $file | cut -d " " -f1
done

第一个表单适用于此脚本,其中包含一个循环以提供I / O重定向。使用{}的第二种形式将处理更一般的命令序列。使用exec的第三种形式是“永久的”;您无法恢复原始标准输出,而使用{ ... }表单,您可以将脚本的不同部分写入不同的位置。

所有这些变化的另一个优点是,如果您想要的话,您可以轻松地将错误发送到您发送标准输出的相同位置。例如:

exec >>db_purge_log.txt 2>&1

其他问题

  • wc中取消文件名 - 而不是:

    wc -l $file | cut -d " " -f1
    

    使用:

    wc -l < $file
    
  • UUOC - 无用地使用cat - 代替:

    cat $file | egrep -v "xxx|yyy|zzz" | grep -v -E -i '([0-z])\1{2,}' | uniq | sort -u  > tmp_file
    

    使用:

    egrep -v "xxx|yyy|zzz" $file | grep -v -E -i '([0-z])\1{2,}' | uniq | sort -u  > tmp_file
    
  • UUOU - 无用地使用uniq

    您完全不清楚为什么需要uniqsort -u;在上下文中,sort -u就足够了,所以:

    egrep -v "xxx|yyy|zzz" $file | grep -v -E -i '([0-z])\1{2,}' | sort -u  > tmp_file
    
  • UUOG - 无用地使用grep

    egrep相当于grep -E,两者都能够处理多个正则表达式,第二个将匹配括号中表达式匹配的3次或更多次(我们实际上只需要匹配三次),所以事实上第二个表达式将完成第一个的工作。并且[0-z]匹配是可疑的。它可能匹配各种标点符号以及大写和小写数字,但由于-i,您已经在进行不区分大小写的搜索,因此我们可以将所有这些都规范化为:

    grep -Eiv '([0-9a-z]){3}' $file | sort -u > tmp_file
    
  • 带空格的文件名

    由于for file in $(find ...)表示法,代码不会使用空格,制表符或换行符处理文件名。现在可能没有必要处理 - 请注意这个问题。

最后清理

for file in $(find . -name "*.csv" )
do
    echo "===================================================="
    echo "$file"
    echo "----------------------------------------------------"
    echo "Contacts BEFORE purge:"
    wc -l < $file
    echo " "
    grep -Evi '([0-9a-z]){3}' | sort -u  > tmp_file
    mv tmp_file $file
    echo "Contacts AFTER purge:"
    wc -l <$file
done >> db_purge_log.txt

添加额外功能

  

我想在此循环中间的某处添加一个命令,以使用另一个.csv文件作为抑制列表 - 这意味着应该从{{1}删除在该抑制列表中找到的完美匹配的每一行}}

由于我们已经对输入文件($file)进行了排序,因此我们可以对抑制文件进行排序(如果尚未对其进行排序,则将其称为$file。鉴于此,我们将使用{{ 1}}消除suppfile='suppressions.txt'comm中出现的行。我们对仅出现在$file中的行感兴趣(或者,就像这里的情况一样,已编辑和排序的文件版本),因此我们要禁止$suppfile中未出现的$file中的公共条目和条目。$suppfile命令读取已编辑的,已排序的来自标准输入$file的文件,并从comm -23 - "$suppfile"

中删除条目
-

如果抑制文件未按排序顺序排列,只需将其排序为临时文件即可。注意在当前目录中的抑制文件上使用"$suppfile"后缀;它将捕获文件并清空它,因为抑制文件中的每一行都与抑制文件中的一行匹配,这对抑制文件后处理的任何文件都没有帮助。


哎呀 - 我过度简化了suppfile='suppressions.txt' # Must be in sorted order for file in $(find . -name "*.csv" ) do echo "====================================================" echo "$file" echo "----------------------------------------------------" echo "Contacts BEFORE purge:" wc -l < "$file" echo " " grep -Evi '([0-9a-z]){3}' | sort -u | comm -23 - "$suppfile" > tmp_file mv tmp_file "$file" echo "Contacts AFTER purge:" wc -l < "$file" done >> db_purge_log.txt 正则表达式。它应该(可能)是:

.csv

差异很大。我的原始重写将查找任意三个相邻的数字或字母(例如grepgrep -Evi '([0-9a-z])\1{2}' $file );修订版(实际上非​​常类似于原始命令之一)查找来自123的字符,后跟两次出现的相同字符(例如abz[0-9A-Za-z],但不是{{1 }或111)。

如果偶然的替代aaa实际上不是3个重复的字符,则可能需要按顺序进行两次123的调用。

答案 1 :(得分:1)

如果我理解正确,假设最近有一个&#39; nix,grep应该为你做大部分工作。命令grep -vf filterfile input.csv将输出input.csv中与filterfile中找不到的任何正则表达式不匹配的行。

其他几条评论...... uniq需要对输入进行排序才能删除重复项,因此您可能需要管道中的sort(除非输入数据已排序)。

或者,如果输入按开头排序,grep -u将忽略重复项。

小建议 - 您可以添加#!/bin/bash作为第一行,以确保脚本由bash而不是用户的登录shell运行(可能不是bash)。 HTH。 B'/ P>