我对 Linux/Bash 比较陌生,我正在将以下指南作为我自己的教程:
尽管如此,我还尝试编写一个 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 表达式 #1, char 13: 未知命令: `,'
总而言之,我的问题是:
如何使用 sed 删除嵌套括号 block ?如果不可能,那么有什么更好的工具可以使用?如何仅在第一个匹配的出现时进行删除?
非常感谢您的阅读和帮助。
最佳答案
任何时候你遇到涉及嵌套结构(括号、大括号等)的问题时,你可以立即知道这个问题对于sed
来说太难了,或者实际上任何仅适用于正则表达式的工具。这是计算理论的结果,对此有一个不错的背景解释Wikipedia page :基本上,正则表达式仅实现有限状态自动机,但您需要具有下推自动机计算能力的东西。
另一种说法是“正则表达式无法计数”:在通用正则表达式执行器中,任意数量的左括号“(((”(之间可能有任意数量的非括号字符)结束您可以编写(极其复杂的)正则表达式,该正则表达式可以以不同于双括号组的方式处理三括号组,并且这两者都不同于单括号组,但随后有人出现并编写“( ((("对你来说,破坏了你复杂的方案。因此,你写了一个指数级复杂的方法来处理带有四个括号的组,然后有人给了你五个......:-)
无论如何,结果是您需要一种更强大的语言。这些确实存在,并且您可以在 bash 脚本中编写自己的脚本(因为 bash 实现算术),但这些没有标准的现成答案。大多数人用他们认为方便的任何语言编写小型解析器,或者您可以使用 Python 和 ply 包,或者使用 C 或 C++ 的 bison 或 yacc(包含在 Linux 中),尽管这些实际上是成熟的工具用于编写编译器的解析部分。
您还可以在 awk 中编写完整的解析器,使用 awk 的正则表达式来实现分词器。我已经为玩具示例完成了此操作,但不推荐这样做:一旦您学习如何使用 lex 和 yacc(或 Python 中的 ply),您可能会发现使用它们实际上更容易。由于它们功能非常齐全,您可以用它们编写真正的工具。
我建议在这里使用 ply,因为 Python 以易于使用的方式提供了所有复杂的存储管理位。请注意,词法分析和解析是一个相当大的主题,而编译器则是一个更大的主题。标记化和解析的概念并不难,只是有一系列令人难以置信的数学支撑着不同的方法,以及什么上下文无关语法意思和暗示(点击维基百科链接)。
编辑:这是仅使用层的扫描仪部分的完整实现。它有点长,但它展示了如何使用 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/