我正在尝试编辑一个大文本文件。我想要实现的是将行从 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
在该行的开头找到包含该字符串的行。这是使用显式循环(标签again
和b again
条件分支指令)来完成的。
然后,该脚本继续读取并打印行,直到找到仅包含--
.此时,它将该行添加到保留空间,并使用 将保留空间交换到主缓冲区中x
。然后在末尾打印它p
(它还打印不受其余代码直接影响的所有其他行)。
您可以从命令行运行它,如下所示:
sed -f script file
...script
包含编辑脚本的文件在哪里sed
,file
包含数据的文件在哪里。
一个较短的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
(无条件退出),这意味着结果将打印到标准输出上。
如果将,p
and更改Q
为单个命令wq
, or w
and 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
就地编辑文件(除非您确信此代码可以工作,否则请勿执行此操作)
- 使用临时文件:
f=$(mktemp) gawk '...' file > "$f" && mv "$f" file
- 与
moreutils
包裹:gawk '...' file | sponge file
- 使用 GNU awk
gawk -i inplace '...' file