gpt4 book ai didi

linux - 在 Bash 中用嵌套括号替换第一个文本实例的最佳方法?

转载 作者:太空狗 更新时间:2023-10-29 11:41:18 27 4
gpt4 key购买 nike

我是 Linux/Bash 的新手,我正在学习以下指南作为我自己的教程:

https://www.digitalocean.com/community/tutorials/how-to-install-elasticsearch-logstash-and-kibana-elk-stack-on-centos-7

虽然我不只是手动执行此操作,但我还尝试编写一个 bash 脚本来执行该指南中的所有步骤。

我坚持执行以下步骤:指南让您安装 nginx,然后从 nginx.conf 文件中删除以下文本 block :

    server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}enter code here

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

我试图使用 sed 来完成这个,使用如下命令:

sed -i '/server {/,/ location = /50x.html {            }        }/d' /etc/nginx/nginx.conf

但我无法让它成功匹配 block 的结尾(我无法弄清楚的正则表达式/特殊字符/空白错误,例如“sed:-e 表达式#1,字符 10:未终止地址正则表达式”)。我尝试转义特殊字符,但仍然卡住了。

我放弃了,而是选择在 block 开头的匹配项之后删除固定数量的行:

sudo sed -i '/server {/,+19d' /etc/nginx/nginx.conf

这有效,但也删除了另一个以 '/server{/' 开头的 block ,在文件其他地方注释掉了我不想要的文本。我进行了搜索,但无法找到仅在与此命令的第一次匹配后删除的方法。我看到了以下建议:

sudo sed -i '0,/server {/,+19d' /etc/nginx/nginx.conf

但是这些返回了错误:sed: -e expression #1, char 13: unknown command: `,'

总而言之,我的问题是:

如何使用 sed 删除嵌套的括号 block ?如果不可能,那么更好的工具是什么?我如何只在第一次匹配的情况下进行删除?

非常感谢您的阅读和任何帮助。

最佳答案

任何时候当你遇到涉及嵌套结构(圆括号、大括号等)的问题时,你可以立即知道这个问题对于sed来说太难了,或者任何仅适用于正则表达式的工具。这是计算理论的结果,对此有一个不错的背景解释 Wikipedia page : 基本上,正则表达式仅实现有限状态自动机,但您需要具有下推自动机计算能力的东西。

另一种表达方式是“正则表达式不能计数”:在通用正则表达式执行器中,任意数量的左括号“(((”(它们之间可能有任意数量的非括号字符)结束通过一个右括号“)”。你可以编写(非常复杂的)正则表达式来处理三括号组与双括号组不同的方式,并且两者都与单括号组不同,但是有人出现并写了“( ((("在你面前炸毁了你的复杂方案。所以你写了一个指数更复杂的方法来处理带有四个括号的组,然后有人给了你五个......:-)

无论如何,结果是您需要一种更强大的语言。这些确实存在,您可以在 bash 脚本中编写自己的脚本(因为 bash 实现了算术),但是对于这些没有标准的开箱即用的答案。大多数人使用他们认为方便的任何语言编写小型解析器——或者您可以使用 Python 和 ply 包,或者使用 C 或 C++ 的 bison 或 yacc(包含在 Linux 中),尽管这些实际上是成熟的工具用于编写编译器的解析部分。

您还可以使用 awk 编写完整的解析器,使用 awk 的正则表达式来实现分词器。我已经为玩具示例这样做了,但不推荐这样做:一旦您学习了如何使用 lex 和 yacc(或 Python 中的 ply),您可能会发现使用它们实际上更容易。由于它们功能齐全,您可以使用它们编写真正的工具。

我建议在这里使用 ply,因为 Python 以一种易于使用的方式提供了所有复杂的存储管理位。请注意,词法分析和解析是一个相当大的话题,然后进入编译器,这是一个更大的话题。标记化和解析的概念并不难,只是一系列令人难以置信的数学支持不同的方法,以及多么上下文无关的语法意味着和暗示(按照维基百科链接)。


编辑:这是一个完整的实现,仅使用 ply 的扫描器部分。它有点长,但它展示了如何使用 ply 构建词法分析器,然后以一种相当俗气的方式使用它。

我不知道我对字符串和其他标记的处理是否正确,因为关于 nginx 输入文件格式的文档相当薄,但是由于标记是由正则表达式定义的,如果需要的话应该很容易调整.我也不是说它是实现 nginx 输入文件解析器的特别好的方法:如果你真的想读取和解释文件,而不是粗暴地破解它,你会可能想要至少有一点不同的东西,也许包括正确的语法。

#! /usr/bin/env python

from __future__ import print_function

import argparse
import collections
import sys

import ply.lex

t_COMMENT = r'\#.*'
t_BACKSLASHED = r'\\([\\{}])'
t_WORD = '[A-Za-z0-9_]+'
t_STRING = '("[^"]*")|' "('[^']*)'"
t_LB = '{'
t_RB = '}'
t_WHITE = '[ \t]+'
t_REST = '.'

tokens = [
'COMMENT',
'BACKSLASHED',
'WORD',
'STRING',
'LB',
'RB',
'WHITE',
'REST',
'NEWLINE',
]

def t_NEWLINE(t):
r'\n+'
t.lexer.lineno += len(t.value)
return t

# This never happens because '.' matches anything but newline and
# we have a newline rule; but if we don't define it, ply complains.
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)

# Build the lexer
LEXER = ply.lex.lex()

def fill(tlist, howmany):
"build up token list - returns False if the list is all non-tokens"
while len(tlist) < howmany:
tlist.append(LEXER.token())
return tlist[0] is not None

def nth_is(tlist, offset, tok_type, tok_value=None):
"a sleazy kind of parser lookahead"
fill(tlist, offset + 1)
tok = tlist[offset]
if tok is None:
return False
if tok.type != tok_type:
return False
if tok_value is not None and tok.value != tok_value:
return False
return True

TEST_DATA = '''\
# a comment - gets copied
server {
stuff;
more { } stuff;
this is not a brace \{ because it is backslashed;
"and these strings }";
'do not close the server } either';
}
this gets copied;
'''

def main():
"main"

parser = argparse.ArgumentParser()
parser.add_argument('-t', '--test', action='store_true')
parser.add_argument('inputfile', nargs='?', type=argparse.FileType('r'),
default=sys.stdin)

args = parser.parse_args()
if args.test:
LEXER.input(TEST_DATA)
else:
LEXER.input(args.inputfile.read())

# Tokenize; copy lines through except when dealing
# with the first "server" definition
looking_for_server = True
copying = True
eat_white_space_and_newline = False
brace_depth = 0
tlist = collections.deque()
while fill(tlist, 1):
if tlist[0].type == 'LB':
brace_depth += 1
elif tlist[0].type == 'RB':
if brace_depth > 0:
brace_depth -= 1
# If we went from 1 to 0 and are in
# non-copy mode, resume copying, but eat
# one white-space-and-newline
if brace_depth == 0 and not copying:
copying = True
eat_white_space_and_newline = True
tlist.popleft() # eat the }
continue
if looking_for_server:
check = 0
if tlist[0].type == 'WHITE':
fill(tlist, 2)
check = 1
else:
check = 0
if nth_is(tlist, check, 'WORD', 'server'):
# server followed by spaces and {, or by { => stop copying
if nth_is(tlist, check + 1, 'LB') or (
nth_is(tlist, check + 1, 'WHITE') and
nth_is(tlist, check + 2, 'LB')):
copying = False
looking_for_server = False
if check > 0:
tlist.popleft() # toss white space at 0 now
# We'll increment brace-depth when we actually consume
# the brace.
if copying:
if not eat_white_space_and_newline or \
tlist[0].type not in ('NEWLINE', 'WHITE'):
print(tlist[0].value, end='')
if tlist[0].type == 'NEWLINE':
eat_white_space_and_newline = False
tlist.popleft()

if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit('\nInterrupted')

关于linux - 在 Bash 中用嵌套括号替换第一个文本实例的最佳方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48028973/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com