删除多行字符串

删除多行字符串

这里有几个关于使用 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(又名vimEx 模式):

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'

相关内容