여러 줄 문자열 삭제

여러 줄 문자열 삭제

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='(이 줄 사이에는 0개 이상의 줄이 있을 수 있으며 항상 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

사용 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'

관련 정보