替换多行标识结束字符

replace multiple lines identifying end character

我有以下代码

CREATE TABLE Table1(
        column1 double NOT NULL,
        column2 varchar(60) NULL,
        column3 varchar(60) NULL,
        column4 double NOT NULL,
 CONSTRAINT Index1 PRIMARY KEY CLUSTERED
(
        column2 ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON PRIMARY
) ON PRIMARY

GO
GO

我想替换

 CONSTRAINT Index1 PRIMARY KEY CLUSTERED
(
        column2 ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON PRIMARY
) ON PRIMARY

GO

)

您不能假设 GO 是文件的最后一个字符。 Go 之后可以有另一个 table 脚本。 我怎样才能用单个 sed 或 awk 做到这一点。

更新:

您可以使用以下 sed 命令替换 CONSTRAINT 块之前的最后一个 ,

sed -r '/,/{N;/CONSTRAINT/{:a;N;/GO/!ba;s/([^,]+).*/\n)/};/CONSTRAINT/!n}' input.sql

让我用多行脚本来解释一下:

# Search for a comma
/,/ {
  # If a command was found slurp in the next line
  # and append it to the current line in pattern buffer
  N
  # If the pattern buffer does not contain the word CONSTRAINT
  # print the pattern buffer and go on with the next line of input
  # meaning start searching for a comma
  /CONSTRAINT/! n

  # If the pattern CONSTRAINT was found we loop until we find the 
  # word GO
  /CONSTRAINT/ {
    # Define a start label for the loop 
    :a
    # Append the next line of input to the pattern buffer
    N
    # If GO is still not found in the pattern buffern
    # step to the start label of the loop
    /GO/! ba

    # The loop was exited meaning the pattern GO was found.
    # We keep the first line of the pattern buffer - without
    # the comma at the end and replace everything else by a )
    s/([^,]+).*/\n)/
  }
}

您可以将上面的多行脚本保存在一个文件中并使用

执行它
sed -rf script.sed input.sql

您可以使用以下 sed 命令:

sed '/CONSTRAINT/{:a;N;/GO/!ba;s/.*/)/}' input.sql

该模式搜索包含 /CONSTRAINT/ 的行。如果找到模式,则开始在 { } 之间包装一个命令块。在块中,我们首先定义一个标签 a:a。我们通过 N 获得下一行输入并将其附加到模式缓冲区。除非我们找到模式 /GO/!,否则我们将使用分支命令 b 在标签 a 处继续。如果找到模式 /GO/,我们只需用 ).

替换缓冲区

另一种方法是使用 FredPhil 建议的范围:

sed '/CONSTRAINT/,/GO/{s/GO/)/;te;d;:e}'

使用 GNU awk 进行多字符 RS 并假设您想去掉 "CONSTRAINT":

之前的逗号
$ cat tst.awk
BEGIN{ RS="^$"; ORS="" }
{
    gsub(/\<GO\>/,"4")
    gsub(/,\s*CONSTRAINT[^4]+4/,")")
    gsub(/4/,"GO")
    print
}
$ gawk -f tst.awk file
CREATE TABLE Table1(
        column1 double NOT NULL,
        column2 varchar(60) NULL,
        column3 varchar(60) NULL,
        column4 double NOT NULL)
GO

上面的工作是将每个独立 "GO" 替换为不太可能出现在您的输入中的控制字符(在这种情况下,我使用与默认 SUBSEP 相同的值),因此我们可以使用该字符在中间 gsub() 的否定字符列表中创建一个以 "CONSTRAINT" 之后的第一个 "GO" 结尾的正则表达式。这是在 awk 中进行 "non-greedy" 匹配的一种方法。

如果没有您知道不能出现在输入中的字符,您可以这样创建一个:

$ cat tst.awk
BEGIN{ RS="^$"; ORS="" }
{
    gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/\<GO\>/,"b")
    gsub(/,\s*CONSTRAINT[^b]+b/,")")
    gsub(/b/,"GO"); gsub(/aB/,"b"); gsub(/aA/,"a")
    print
}
$ 
$ gawk -f tst.awk file
CREATE TABLE Table1(
        column1 double NOT NULL,
        column2 varchar(60) NULL,
        column3 varchar(60) NULL,
        column4 double NOT NULL)
GO

上面最初将所有 "a" 转换为 "aA" 并将 "b" 转换为 "aB" 以便

  1. 记录中不再有任何 "b",并且
  2. 因为所有原来的 "a" 后面都有一个 "A",唯一出现的 "aB" 表示 "bs" 最初所在的位置

这意味着我们现在可以将所有 "GO" 转换为 "b",就像我们在上面的第一个脚本中将它们转换为“\034”一样。然后我们执行主要的 gsub(),然后展开我们的初始 gsub()。

这种 gsub() 创建以前不存在的字符,使用这些字符,然后展开初始 gsub()s 的想法是一个非常有用的习语,可以学习和记忆,例如参见 了解另一个应用程序。

要看到它一次运行一个步骤:

$ cat file                                                                                                   
foo bar Hello World World able bodies

$ awk '{gsub(/a/,"aA")}1' file                                                                               
foo baAr Hello World World aAble bodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB")}1' file                                                               
foo aBaAr Hello World World aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b")}1' file                                            
foo aBaAr Hello b b aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The")}1' file                         
foo aBaAr We Are The b aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The"); gsub(/b/,"World")}1' file 
foo aBaAr We Are The World aAaBle aBodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The"); gsub(/b/,"World"); gsub(/aB/,"b")}1' file
foo baAr We Are The World aAble bodies

$ awk '{gsub(/a/,"aA"); gsub(/b/,"aB"); gsub(/World/,"b"); gsub(/Hello[^b]+b/,"We Are The"); gsub(/b/,"World"); gsub(/aB/,"b"); ; gsub(/aA/,"a")}1' file
foo bar We Are The World able bodies

这可能看起来很可怕,但通过一些解释就不难理解:

SED_DELIM=$(echo -en "[=10=]1")
START=' CONSTRAINT Index1 PRIMARY KEY CLUSTERED'
END='GO' 
sed -n $'\x5c'"${SED_DELIM}${START}${SED_DELIM},"$'\x5c'"${SED_DELIM}${END}${SED_DELIM}{s${SED_DELIM}GO${SED_DELIM})${SED_DELIM};t a;d;:a;};p" test2.txt

sed 具有以下形式,您可能更熟悉:
sed /regex1/,/regex2/{commands}

首先它使用不可打印的SOH作为分隔符[=12=]1
为 sed 多行匹配设置 START 和 END 标签
然后执行sed命令:
-n 默认不打印
$'\x5c' 是一个 Bash 字符串文字,对应于反斜杠 \
反斜杠是转义多行范围匹配中不可打印的定界符所必需的。
{s${SED_DELIM}GO${SED_DELIM})${SED_DELIM};t a;d;:a;};p:
s${SED_DELIM}GO${SED_DELIM})${SED_DELIM} 将匹配 GO 的行替换为 )
t a; 如果前面的语句中有一个成功的替换,则分支到 :a 标签
d 如果没有替换则删除行
p 打印命令后的任何结果 分支到

我在发布之前没有看到他们的答案 - 这个答案与 FredPhil/hek2mgl 相同 - 除了以这种方式你有一个机制可以在 LHS 上更加动态,因为你可以更改分隔符到一个不太可能出现在数据集中的字符。