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
( 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'