【问题标题】:Using sed on text files with a csv在带有 csv 的文本文件上使用 sed
【发布时间】:2015-05-18 05:44:45
【问题描述】:

我一直在尝试使用 csv 对两个文本文件进行批量查找和替换。我已经看到了 SO 建议的问题,但似乎没有人回答我的问题。

我为我要修改的两个文本文件创建了两个变量。 csv 有两列和数百行。第一列包含文本文件中已有的字符串(没有空格),需要用第二列同一行中的相应字符串替换。

作为测试,我尝试了脚本

#!/bin/bash

test1='long_file_name.txt'
find='string1'
replace='string2'

sed -e "s/$find/$replace/g" $test1 > $test1.tmp && mv $test1.tmp $test1

这是成功的,除了我需要对 csv 中的每一行执行一次,使用每行中 csv 给出的值。我的预感是我的 while 循环被错误地使用了,但我找不到错误。当我执行下面的脚本时,我得到了命令行提示符,这让我觉得发生了一些事情。当我检查文本文件时,什么都没有改变。

这两个文本文件、这个脚本和 csv 都在同一个文件夹中(当我这样做时,它也是我的工作目录)。

#!/bin/bash

textfile1='long_file_name1.txt'
textfile2='long_file_name2.txt'

while IFS=, read f1 f2
do
    sed -e "s/$f1/$f2/g" $textfile1 > $textfile1.tmp && \
         mv $textfile1.tmp $textfile1
    sed -e "s/$f1/$f2/g" $textfile2 > $textfile2.tmp && \
         mv $textfile2.tmp $textfile2
done <'findreplace.csv'

在我看来,这段代码应该做我想让它做的事情(但不是);也许我误解了一些基本的东西(我是 bash 脚本的新手)?

csv 看起来像这样,但有数百行。所有 a_i 都应替换为下一列中对应的 b_i。

a_1 b_1
a_2 b_2
a_3 b_3

注意事项:所有字符串实际上都包含下划线,以防万一这会影响某些内容。我试过用大括号将变量名包裹在 ${var} 中,但它仍然不起作用。

我很欣赏这些解决方案,但我也很想知道为什么上述方法不起作用。 (另外,我会投票给每个人,但我缺乏这样做的声誉。但是,我很感激你的回答,并且我从你的回答中学到了很多东西!)

【问题讨论】:

  • 您可以粘贴您传递的 csv 文件的样本吗?否则您的代码应该按原样工作..另外注意:您可以使用 sed -i "s/$f1/$f2/g" $textfile1 来避免创建中间 .tmp 文件。使用选项-ised 将查找、替换和覆盖修改后的文件
  • @sa77 除了sed -i 确实创建了一个中间文本文件!另外,并不是所有的sed都有效,经常是语法错误。
  • 原来我使用 TextEdit 创建的 csv 有问题;使用 Sublime 再次创建 csv 后,该脚本有效。再次感谢大家!我知道-i 标签,但更喜欢使用第二个答案here 给出的解决方案
  • 对于任何可能感兴趣的人,上面给出的原始代码可以很好地完成我描述的任务。

标签: bash csv text replace sed


【解决方案1】:

如果您要处理大量数据并且您的模式可以包含特殊字符,我会考虑使用 Perl。特别是如果您将在findreplace.csv 中有很多对。您可以使用以下脚本作为过滤器或对大量文件进行就地修改。作为副作用,它只会在每次调用时加载替换并创建 Aho-Corrasic 自动机一次,这将使该解决方案非常高效(O(M+N) 而不是您的解决方案中的O(M*N))。

#!/usr/bin/perl
use strict;
use warnings;
use autodie;

my $in_place = ( @ARGV and $ARGV[0] =~ /^-i(.*)/ )
    ? do {
    shift;
    my $backup_extension = $1;
    my $backup_name      = $backup_extension =~ /\*/
        ? sub { ( my $fn = $backup_extension ) =~ s/\*/$_[0]/; $fn }
        : sub { shift . $backup_extension };
    my $oldargv = '-';
    sub {
        if ( $ARGV ne $oldargv ) {
            rename( $ARGV, $backup_name->($ARGV) );
            open( ARGVOUT, '>', $ARGV );
            select(ARGVOUT);
            $oldargv = $ARGV;
        }
    };
    }
    : sub { };

die "$0: File with replacements required." unless @ARGV;
my ( $re, %replace );
do {
    my $filename = shift;
    open my $fh, '<', $filename;
    %replace = map { chomp; split ',', $_, 2 } <$fh>;
    close $fh;
    $re = join '|', map quotemeta, keys %replace;
    $re = qr/($re)/;
};

while (<>) {
    $in_place->();
    s/$re/$replace{$1}/g;
}
continue {print}

用法:

./replace.pl replace.csv <file.in >file.out

还有

./replace.pl replace.csv file.in >file.out

或就地

./replace.pl -i replace.csv file1.csv file2.csv file3.csv

或有备份

./replace.pl -i.orig replace.csv file1.csv file2.csv file3.csv

或使用备用占位符

./replace.pl -ithere.is.\*.original replace.csv file1.csv file2.csv file3.csv

【讨论】:

  • 感谢您的代码!我也只是想知道我做错了什么,但你的回答非常有用。当我获得足够的声望时会投票。
【解决方案2】:

您应该使用以下命令将您的 CSV 文件转换为 sed.script:

cat replace.csv | awk -F, '{print "s/" $1 "/" $2 "/g";}' > sed.script

然后您就可以进行一次性替换:

sed -i -f sed.script longfilename.txt

这将更快地实现您想做的事情。

顺便说一句,抱歉,但我不明白你的脚本有什么问题,除非你的 CSV 文件有超过 2 列,否则它应该可以工作。

【讨论】:

  • 我听说过 awk(和 gawk),但从未使用过。在文本编辑器中查看 sed.script,它只包含一行;按照问题中的 csv 格式,该行是s/a_1/b_1 a_2/ga_ib_i 实际上确实包含下划线;这可能是为什么? (修改原帖。)
  • 也感谢您确认我的脚本的有效性!
  • 您的结果文件中应该有多行。您可以在 /g 之后添加 \n 以确保它...
  • 它实际上最终成为了 csv 的问题,尽管您的代码绝对方便(并且有效)。下次我需要做这样的任务时,我一定会尝试 awk 。再次感谢!
  • 感谢您的反馈。如果您希望我的回答使下一位观众受益,您可以接受它作为答案或给它 +1。谢谢。
猜你喜欢
  • 2016-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-25
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
相关资源
最近更新 更多