複数行文字列の削除

複数行文字列の削除

Unixシェルを使用して複数行の文字列を置き換える方法に関するいくつかの質問がありますが、この状況に合った質問は見つかりませんでした。

次のように、いくつかの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 ='あります)。結果は次のようになります。

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ダンプユーティリティの出力を直接処理することもある)、入力と出力をパイプできることが必要です。

答え1

使用exvimExモードとも呼ばれる):

ex +'%s/,\n *PRIM\_.*\ze\n) ENGINE//' +wq file

//複数行の一致を実行し、\_.*パターンの最後の部分を除外するVim代替削除(NULL置換)の「一括」バージョン\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

前の行を印刷するかどうかの状態をそのままにして、必要に応じてコンマを削除するように編集します。この方法では、ファイルの 1 行または 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

(コメントを削除して改行文字を改行文字に置き換えると、それを1行に圧縮できます";"。)

@Philipposのバージョン:

少し単純化し、より移植性の高い後:

sed -e '$!N;/,\n *PRIMARY KEY/!{P;D;};s/,//;:loop' -e 'N;s/ *PRIMARY KEY.*\() ENGINE\)/\1/;T loop'

関連情報