模式匹配后交换连续多行的位置

模式匹配后交换连续多行的位置

我正在尝试编辑一个大文本文件。我想要实现的是将行从 3 到 14 之间的位置交换到 16 到 27 之间的位置。我已经从 MySQL bin-logs 中提取了 UPDATE 语句并将它们放入一个文件中。问题在于,WHERE 子句和 SET 子句在 bin-log 中的位置颠倒了。我需要将 SET 子句放在 WHERE 子句之前。目前,WHERE 子句位于 SET 子句之前。一条 UPDATE 语句中有 27 行,并且有数千条这样的 UPDATE 语句。因此,每个 UPDATE 语句中 SET 子句的所有 13 行(包括第 15 行到第 27 行的 SET 行)都应放在位置 2 和 14 处,反之亦然。

我不知道这是否可能。我试图从下面的文章中理解它,但根本不明白。我完全不理解正则表达式、SED 或 AWK。 仅在包含字符串的文本文件中使用 sed 或 ed 交换行?

以下是示例输出和所需的输出。更新语句示例

### UPDATE `db`.`tb`
### WHERE
###   @1=39741631
###   @2=49969113
###   @3=1
###   @4=34
###   @5='{"CustomerName":"S","CustomerEmail":"[email protected]","CustomerMobile":"9","VersionId":"5","InSrc":"3","Eagerness":"-1"}'
###   @6=NULL
###   @7=0
###   @8='2021-11-09 19:11:49'
###   @9=NULL
###   @10=1
###   @11=29
###   @12=NULL
### SET
###   @1=39741631
###   @2=49969113
###   @3=1
###   @4=34
###   @5='{\n  "CustomerName": "S",\n  "CustomerEmail": "[email protected]",\n  "CustomerMobile": "9",\n  "VersionId": "5",\n  "InSrc": "39",\n  "Eagerness": "-1"}'
###   @6='33195861'
###   @7=1
###   @8='2021-11-09 19:11:49'
###   @9='2021-11-09 19:11:50'
###   @10=1
###   @11=20
###   @12='Pushed to CVL panel'
--

所需输出

### UPDATE `db`.`tb`
### SET
###   @1=39741631
###   @2=49969113
###   @3=1
###   @4=34
###   @5='{\n  "CustomerName": "S",\n  "CustomerEmail": "[email protected]",\n  "CustomerMobile": "9",\n  "VersionId": "5",\n  "InSrc": "39",\n  "Eagerness": "-1"}'
###   @6='33195861'
###   @7=1
###   @8='2021-11-09 19:11:49'
###   @9='2021-11-09 19:11:50'
###   @10=1
###   @11=20
###   @12='Pushed to CVL panel'
### WHERE
###   @1=39741631
###   @2=49969113
###   @3=1
###   @4=34
### @5='{"CustomerName":"S","CustomerEmail":"[email protected]","CustomerMobile":"9","VersionId":"5","InSrc":"3","Eagerness":"-1"}'
###   @6=NULL
###   @7=0
###   @8='2021-11-09 19:11:49'
###   @9=NULL
###   @10=1
###   @11=29
###   @12=NULL
--

答案1

使用sed

#n

/^### WHERE/ {
        h
        :again
        n
        /^### SET/ !{
                H
                b again
        }
}

/^--$/ {
        H
        x
}

p

sed编辑脚本首先使用第 1 行在每个周期结束时关闭缓冲区的默认输出#n。这会阻止n命令输出任何内容(我们使用它将新行读入缓冲区)。

然后,该脚本打印读取的每一行,直到找到包含该字符串的行### WHERE。当该字符串位于行的开头时,它将将该行存储在“保留空间”(辅助缓冲区,其内容在编辑脚本的循环之间不会被修改)。然后,它将输入中的行追加到保留空间,直到### SET在该行的开头找到包含该字符串的行。这是使用显式循环(标签againb again条件分支指令)来完成的。

然后,该脚本继续读取并打印行,直到找到仅包含--.此时,它将该行添加到保留空间,并使用 将保留空间交换到主缓冲区中x。然后在末尾打印它p(它还打印不受其余代码直接影响的所有其他行)。

您可以从命令行运行它,如下所示:

sed -f script file

...script包含编辑脚本的文件在哪里sedfile包含数据的文件在哪里。


一个较短的sed脚本,它不是显式循环来读取需要保存以供以后使用的行,而是简单地使用范围地址并将该范围内的所有行(最后一行除外)存储在保留空间中:

/^### WHERE/,/^### SET/ {
        /^### SET/ !H
        /^### WHERE/ h
        d
}

/^--$/ { H; x; }

我不是真的建议您使用ed来解决这个问题,但是您可以,并且您用以下标记了您的问题,所以我们开始:

g/SET/ .,/--/-1 m ?WHERE?-1

此单个ed表达式将m命令应用于包含字符串的所有行SET

编辑器m中的命令ed动作一条或多条线到另一个位置。

要移动的行在命令本身的前面(左侧)寻址m,并且这些行的目的地在m.

在本例中,我们将从当前行(包含 的行SET)向下移动到包含字符串 的行之前的行--。写入该范围行的地址.,/--/-1.,/@12=/如果要移动的最后一行始终包含字符串@12=,或者.,+13如果您始终想要移动固定数量的行,则还可以使用。

包含该单词的最新行上方的行WHERE是移动这些行的位置。表达式?WHERE?在缓冲区中向后搜索与该表达式匹配的行,然后-1选择该匹配行之前的行。-14如果您始终移动固定数量的行,也可以将其用作目标地址。

您可以通过以下方式从命令行使用它:

printf '%s\n' 'g/SET/ .,/--/-1 m ?WHERE?-1' ',p' 'Q' | ed -s file

这添加了两个命令,p(打印/输出整个缓冲区)和Q(无条件退出),这意味着结果将打印到标准输出上。

如果将,pand更改Q为单个命令wq, or wand q,您将执行就地编辑。

始终在正确备份的数据上进行此类测试。

为了使表达式更加具体,请使用

g/^### SET/ .,/^--$/-1 m ?^### WHERE?-1

这会将m命令应用于以字符串开头的所有行### SET,并将行范围从那里移动到仅包含 的下一行--,到以字符串开头的最新行之前### WHERE

使用 进行此类编辑的问题ed在于编辑器会将文档读入内存。因此,使用ed不像使用sed或其他流编辑方法那样节省内存,如果数据太大,甚至可能无法使用。

答案2

尝试

gawk '
  BEGIN {RS = "--\n"; FS = "###[[:blank:]]+(WHERE|SET)\n"}
  {printf "%s### SET\n%s### WHERE\n%s%s", $1, $3, $2, RS}
' file

就地编辑文件(除非您确信此代码可以工作,否则请勿执行此操作)

  1. 使用临时文件:
    f=$(mktemp)
    gawk '...' file > "$f" && mv "$f" file
    
  2. moreutils包裹:
    gawk '...' file | sponge file
    
  3. 使用 GNU awk
    gawk -i inplace '...' file
    

相关内容