sed 在一个命令中删除多个条目以删除数据

sed 在一个命令中删除多个条目以删除数据

我正在使用以下sed命令删除包含以下内容的电子邮件hotmail- 是否可以同时检查多个条目?最好从list.txt(每行一个条目)加载它。

sed -i '/^[^,]*,[^,]*hotmail/Id' data.txt

如果我无法从 .txt 加载它,有没有办法做类似的事情hotmail|gmail|yahoo

data.txt行示例:

"foxva****omes****","[email protected]","8*** Rd","Ne***ah","Wi***in","54***","*******"
"foxva****omes****","[email protected]","8*** Rd","Ne***ah","Wi***in","54***","*******"
"foxva****omes****","[email protected]","8*** Rd","Ne***ah","Wi***in","54***","*******"

答案1

如果sed您可以以脚本的形式格式化文件,sed则可以自动完成。以下内容应该适用于 GNU sed。对于 BSD,如果你第二次调用sed它就会工作......-i '' -esed

sed -ne's|[]\*&^.$/[]|\\&|g' \
     -e's|..*|/^@&",/d|p' <./list.txt |
sed -ie'h;s/[^,]*[^@]*//' -f- -eg ./data.txt

如果你这样做...

-e's|..*|/^@&",/Id|p' ...

...在第二行中,GNUsedd删除中任何行的匹配项list.txt不区分大小写,但这将相当于大多数其他语法错误。

它尝试通过删除第一个字段以及@在为每一行运行的脚本开头的第二个字段中第一个字段之前的所有内容来优化匹配,然后进行匹配检查,如果该行通过了所有匹配, ets 保存在旧空间g中脚本顶部的行的副本。h这样就sed不需要/^[^,]*,[^,]*.../每场比赛都这样做。如果list.txt虽然很长,但无论如何,这都不会是一个快速的过程。grep -F在这种情况下应该优先选择(可能在这种情况下)


两者sedgrep 表现得更好——在很多情况下显着地所以 - 如果使用的字符集大小减小。例如,如果您当前处于 UTF-8 语言环境中,则执行以下操作:

(   export LC_ALL=C
    sed -ne's|[]\*&^.$/[]|\\&|g' \
         -e's|..*|/^@&",/Id|p'   |
    sed -ie'h;s/[^,]*[^@]*//' -f-\
         -eg ./data.txt
)   <./list.txt

...可以使世界变得不同,因为正则表达式引擎不需要考虑数以万计的不同字符作为匹配,而只需要考虑 128 种可能性。它不应该以任何方式影响结果 - 每个字符都是 C 语言环境中的一个字节,并且所有字符都会得到适当的考虑。

sed -i在最好的情况下,这并不是一个可靠的开关,因此应尽可能避免使用。


为此,请使用grep sed -i:

(   export LC_ALL=C
    cut -d\" -f4 | cut -d@ -f2    |
    grep -Fixnf ./list.txt        |
    sed -e's|:*\([0-9]*\).*|:\1|p'\
        -e's||\1!{p;n;b\1|p'      \
        -e's||};n|'               |
    sed -nif- -e:n -e'p;n;bn'     \
        ./data.txt
)   <./data.txt

sed这是我能想象的最快的方法,可以用's完成-i。它是这样分解的:

  1. cut | cut

    • 前两个cuts 减少了./data.txt从/到...的输入行

     "foxva****omes****","[email protected]","8*** Rd","Ne***ah","Wi***in","54***","*******"
    

    hotmail.com
    
  2. grep

    • grep然后可以将该输入与其模式文件中的每一行进行-f比较list.txt使用-i不区分大小写-F的固定字符串-x整行匹配并报告-n其输出的每行开头的行号。
  3. sed -e

    • sedgrep输出剥离为行号,并写出另一个sed脚本,如下所示(假设grep第 10 行和第 20 行匹配):

     :10
     10!{p;n;b10
     };n
     :20
     20!{p;n;b20
     };n
    
  4. sed -inf-

    • 最后一个将 stdinsed读取-为其脚本,并且只执行一次 - 它不会像通常使用sed脚本那样回溯并执行每个输入行的脚本,而是在第一次也是唯一一次执行脚本通过输入工作 - 它只需要对每个输入行尝试一次测试。

    • 鉴于我们之前的示例,第 1-9sed行将执行以下操作:

      • 如果当前行不是!10th 行,{p打印当前行,用next 输入行覆盖当前行,然后b回溯到:名为 的标签10
    • 最后一系列的行将sedp打印;然后用 ext 覆盖当前行nb牧场到:n标签,直到消耗完所有输入。


那不起作用,如果./data.txt非常大,因为sed在尝试处理远大于其可靠处理能力的脚本输入文件时会陷入困境。解决这个问题的方法是分块获取输入。这如果您使用正确类型的阅读器,即使在管道中,也可以可靠地完成。dd是那种合适的读者。

我创建了一个这样的测试文件:

sh -c ' _1=\"foxva****omes****\",\"scott@
        _2='\''","8*** Rd","Ne***ah","Wi***in","54***","*******"'\''
        n=0
        for m do printf "$_1%s$_2\n$_1$((n+=1))not_free.com$_2\n" "$m"
        done
'       $(cat ~/Downloads/list.txt) >/tmp/data.txt

...在哪里list.txt得到了这里根据你的其他问题。它对所有其他行都像...

"foxva****omes****","[email protected]","8*** Rd","Ne***ah","Wi***in","54***","*******"
"foxva****omes****","scott@1not_free.com","8*** Rd","Ne***ah","Wi***in","54***","*******"
"foxva****omes****","[email protected]","8*** Rd","Ne***ah","Wi***in","54***","*******"
"foxva****omes****","scott@2not_free.com","8*** Rd","Ne***ah","Wi***in","54***","*******"

然后我把它增加到 80mbs 多一点,比如......

while [ "$(($(wc -c <data.txt)/1024/1024))" -lt 80 ]
do    cat <<IN >./data.txt
$(    cat ./data.txt ./data.txt)
IN
done
ls -hl ./data.txt
wc -l <./data.txt

-rw-r--r-- 1 mikeserv mikeserv 81M Jul 19 22:22 ./data.txt
925952

...然后我就...

(   trap rm\ data.tmp 0;  export  LC_ALL=C
    <./data.txt dd bs=64k cbs=512 conv=block    |
    while       dd bs=64k cbs=512 conv=unblock  \
                count=24  of=./data.tmp
                [ -s ./data.tmp ]
    do          
    <./data.tmp cut -d\" -f4 |  cut -d@  -f2    |
                grep -Fixnf ./list.txt          |
                sed -e's|:*\([0-9]*\).*|:\1|p'  \
                    -e's||\1!{p;n;b\1|p'        \
                    -e's||};n|'                 |
                sed -nf- -e:n -e'p;n;bn' ./data.tmp
    done        2>/dev/null
)|  wc -l

1293+1 records in
7234+0 records out
474087424 bytes (474 MB) copied, 21.8488 s, 21.7 MB/s
462976

您可以看到整个过程花费了 22 秒,并且输出行计数至少是正确的 - 462976 是 925952 的一半,并且输入应该减半。

该技术之所以有效,是因为dd的读取和写入可以按字节进行计数 - 如果您知道自己在做什么,甚至可以通过管道进行读取和写入。你甚至可以中断输入按行conv如果您可以通过最大行长度block大小可靠地进行计算,则具有相同的精度(这里是 512,或者{_POSIX_LINE_MAX}

富有想象力的读者可能会正确地猜测,相同的技术可以应用于任何类型的流媒体 - 甚至是实时日志类型 - 只需在这里或那里稍加修改(也就是说,为了安全地做到这一点,第一个dd参数需要从bs=obs=。但在每种情况下,您都需要对最大输入行大小进行一定的保证,并且,如果一行可以合法地以 <space> 字符结尾,则在进程之前插入一些额外的过滤机制,dd以防止尾随 <spaces> 被剥夺了dd conv=unblock (其工作原理是去掉每个cbs-sizeconv版本块的所有尾随空白并附加一个\newline)tr(un|)expand想到了这种过滤器的可能候选者。

这不是最快的方法 - 为此你需要寻找-m厄尔格sort操作,我期望,但它非常快,并且它将与您的数据一起使用。不过,这确实有点破坏了sed -i事情——但我认为无论你走哪条路,这都是事实。

答案2

您可以通过几种不同的方式解决这个问题。其一,sed在一次运行中支持多个表达式:

sed -i -e '/^[^,]*,[^,]*hotmail/Id' -e '/^[^,]*,[^,]*gmail/Id' -e '/^[^,]*,[^,]*yahoo/Id' data.txt

您还可以在单​​个表达式中执行此操作:

sed -i -e '/^[^,]*,[^,]*\(hotmail\|gmail\|yahoo\)/Id' data.txt

请注意,()、 、|、 都需要转义。

相关内容