这里有几个关于使用 Unix shell 替换多行字符串的问题,但我还没有找到适合这种情况的问题。
我正在尝试从某些 MySQL DDL 中删除键和约束,如下所示(一个示例):
CREATE TABLE `access_group` (
`GROUP_ID` int(10) NOT NULL AUTO_INCREMENT,
`PARENT_GROUP_ID` int(10) DEFAULT NULL,
`GROUP_NAME` varchar(45) NOT NULL,
`GROUP_DESC` varchar(45) NOT NULL DEFAULT '',
PRIMARY KEY (`GROUP_ID`),
KEY `testkey` (`PARENT_GROUP_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=latin1;
我想删除以 'PRIMARY KEY' 之前的逗号结尾的所有内容,但不包括 ') ENGINE=' (这些行之间可以有零行或多行,并且它们并不总是以 KEY 开头或具有括号,但 ') ENGINE=' 是一致的)。结果应该是这样的:
CREATE TABLE `access_group` (
`GROUP_ID` int(10) NOT NULL AUTO_INCREMENT,
`PARENT_GROUP_ID` int(10) DEFAULT NULL,
`GROUP_NAME` varchar(45) NOT NULL,
`GROUP_DESC` varchar(45) NOT NULL DEFAULT ''
) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=latin1;
我愿意使用任何标准命令行实用程序(例如 sed、perl、awk),但由于这些文件可能相当大(有些文件约为数十或数百 GB),因此它们需要高效。由于文件通常以 gzip 形式存储(或者有时我直接处理 mysql dump 实用程序的输出而不是首先写入磁盘),所以我需要一些可以通过管道输入和输出的东西。
答案1
使用ex
(又名vim
Ex 模式):
ex +'%s/,\n *PRIM\_.*\ze\n) ENGINE//' +wq file
只是 Vim 替换删除(空替换)的“批量”版本,//
它与 进行多行匹配\_.*
并排除模式的最后部分\ze
。
这会就地修改文件。如果您不想这样做,可以保存到新文件file2
:
ex +'%s/,\n *PRIM\_.*\ze\n) ENGINE//' +'w file2' +q! file
更新:要通过管道输入文件...这有点不寻常,但添加了/dev/stdin
但可以解决问题:
cat file | ex +'%s/,\n *PRIM\_.*\ze\n) ENGINE//' +'%p|q!' /dev/stdin
答案2
保留是否打印上一行的状态,编辑表示必要时删除逗号。此方法仅将文件的一两行保留在内存中。
#!/usr/bin/env perl
use strict;
use warnings;
my $printing = 1;
my $previous;
# reads from standard input (optionally with the conventional -) or from
# the named files
shift @ARGV if @ARGV == 1 and $ARGV[0] eq '-';
while ( my $line = readline ) {
if ( $line =~ m/^\s+PRIMARY KEY/ ) {
$previous =~ s/,[ \t]*$//;
$printing = 0;
} elsif ( $line =~ m/^\) ENGINE/ ) {
$printing = 1;
} elsif ( !$printing ) {
undef $previous;
}
print $previous if defined $previous;
$previous = $line if $printing;
}
# don't forget last line after fall off the end of input (eof)
print $previous if defined $previous;
答案3
基于流的 GNU sed 解决方案:
#Unless on the last line, read the next line and append it to the pattern space
$!N
#If the current pair of lines in buffer, matches the "/,\nPRIMARY KEY/" pattern
/,\n\?\s*PRIMARY KEY/ {
#Read the following lines, until "/) ENGINE/" pattern is encountered
:loop
/) ENGINE/ b exit
N
b loop
}
#Strip away everything between ", PRIMARY KEY" and ") ENGINE"
:exit
s/,\n\?\s*PRIMARY KEY.*\() ENGINE\)/\n\1/
#Print the content of the pattern space up to the first newline (i.e. the first line out of two)
P
#Delete everything up to the first newline (leaving the second line in pattern space buffer)
#and restart the cycle
D
运行如下:
cat data.txt|sed -nf script.sed
(您可以通过删除注释并将换行符替换为 来将其压缩为单行";"
)。
@Philippos 的版本:
经过一些简化和更便携:
sed -e '$!N;/,\n *PRIMARY KEY/!{P;D;};s/,//;:loop' -e 'N;s/ *PRIMARY KEY.*\() ENGINE\)/\1/;T loop'