在带有 csv 的文本文件上使用 sed
Using sed on text files with a csv
我一直在尝试使用 csv 对两个文本文件进行批量查找和替换。我已经看到了 SO 建议的问题,none 似乎回答了我的问题。
我已经为我要修改的两个文本文件创建了两个变量。 csv 有两列和数百行。第一列包含文本文件中已有的字符串(none 有空格),需要用第二列中同一行中的相应字符串替换。
作为测试,我尝试了脚本
#!/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},但还是不行。
我很欣赏这些解决方案,但我也很想知道为什么上述方法不起作用。 (另外,我会投票给所有人,但我缺乏这样做的名声。但是,我很感激你的回答,并且从你的回答中学到了很多东西!)
如果您要处理大量数据并且您的模式可以包含特殊字符,我会考虑使用 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 = ;
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 "[=10=]: 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{}/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
您应该使用以下命令将 CSV 文件转换为 sed.script:
cat replace.csv | awk -F, '{print "s/" "/" "/g";}' > sed.script
然后你就可以进行一次性替换:
sed -i -f sed.script longfilename.txt
这将更快地实现您想做的事情。
顺便说一句,对不起,但我不明白你的脚本有什么问题,除非你的 CSV 文件有超过 2 列,否则它应该可以工作。
我一直在尝试使用 csv 对两个文本文件进行批量查找和替换。我已经看到了 SO 建议的问题,none 似乎回答了我的问题。
我已经为我要修改的两个文本文件创建了两个变量。 csv 有两列和数百行。第一列包含文本文件中已有的字符串(none 有空格),需要用第二列中同一行中的相应字符串替换。
作为测试,我尝试了脚本
#!/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},但还是不行。
我很欣赏这些解决方案,但我也很想知道为什么上述方法不起作用。 (另外,我会投票给所有人,但我缺乏这样做的名声。但是,我很感激你的回答,并且从你的回答中学到了很多东西!)
如果您要处理大量数据并且您的模式可以包含特殊字符,我会考虑使用 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 = ;
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 "[=10=]: 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{}/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
您应该使用以下命令将 CSV 文件转换为 sed.script:
cat replace.csv | awk -F, '{print "s/" "/" "/g";}' > sed.script
然后你就可以进行一次性替换:
sed -i -f sed.script longfilename.txt
这将更快地实现您想做的事情。
顺便说一句,对不起,但我不明白你的脚本有什么问题,除非你的 CSV 文件有超过 2 列,否则它应该可以工作。