3

我正在尝试使用参数扩展来转义括号。虽然如果我extglob启用了,以下代码不起作用:

#!/usr/bin/env bash

shopt -s extglob

foo='file(2)'
foo=${foo//(/\\(}
foo=${foo//)/\\)}

printf '%s\n' "$foo"

# Expected:  file\(2\)
# Actual:    file(2\)

file\(2\)当我像这样禁用extglob或明确转义左括号时,它会正确输出:

foo=${foo//\(/\\(}  

为什么extglob会这样?我在那里看不到任何extglob模式。此外,右括号在没有反斜杠的情况下也能正常工作。

在tutorialspoint.com上在线测试,并在本地使用:

GNU bash, version 4.3.30(1)-release (x86_64-unknown-linux-gnu)
GNU bash, version 4.4.18(1)-release (x86_64-unknown-linux-gnu)
GNU bash, version 5.0.0(2)-alpha (x86_64-pc-linux-gnu)
4

2 回答 2

3

这是由于 bash 中的优化而导致的错误。

替换模式时,bash 首先检查该模式是否匹配字符串中的任何位置。如果没有,那么进行任何搜索和替换就没有意义了。它的方法是通过*..*根据需要围绕它构建一个新模式:

  /* If the pattern doesn't match anywhere in the string, go ahead and
     short-circuit right away.  A minor optimization, saves a bunch of
     unnecessary calls to strmatch (up to N calls for a string of N
     characters) if the match is unsuccessful.  To preserve the semantics
     of the substring matches below, we make sure that the pattern has
     `*' as first and last character, making a new pattern if necessary. */
  /* XXX - check this later if I ever implement `**' with special meaning,
     since this will potentially result in `**' at the beginning or end */
  len = STRLEN (pat);
  if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*')
    {
      int unescaped_backslash;
      char *pp;

      p = npat = (char *)xmalloc (len + 3);
      p1 = pat;
      if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))
    *p++ = '*';

它尝试与字符串匹配的模式最终成为*(*

现在无意中将开头*(识别为 extglob 的开头,但是当 bash找不到结尾时),它会将模式匹配为字符串:

 prest = PATSCAN (p + (*p == L('(')), pe, 0); /* ) */
  if (prest == 0)
    /* If PREST is 0, we failed to scan a valid pattern.  In this
       case, we just want to compare the two as strings. */
    return (STRCOMPARE (p - 1, pe, s, se));

这意味着除非要在其中进行替换的字符串是字面意思*(*的,否则优化会无效地拒绝认为无事可做的字符串。当然,这也意味着它自己可以正常工作*(*

$ f='*(*'; echo "${f//(/\\(}"
*\(*

如果您要在源代码中伪造此优化检查:

diff --git a/subst.c b/subst.c
index fc00cab0..f063f784 100644
--- a/subst.c
+++ b/subst.c
@@ -4517,8 +4517,6 @@ match_upattern (string, pat, mtype, sp, ep)
   c = strmatch (npat, string, FNMATCH_EXTFLAG | FNMATCH_IGNCASE);
   if (npat != pat)
     free (npat);
-  if (c == FNM_NOMATCH)
-    return (0);

   len = STRLEN (string);
   end = string + len;

那么它会在你的情况下正常工作:

$ ./bash -c 'f="my string(1) with (parens)"; echo "${f//(/\\(}"'
my string\(1) with \(parens) 
于 2018-07-13T20:20:08.987 回答
2

引用搜索字符串可以防止它被解释为一个 glob,从而解决这个问题:

shopt -s extglob
foo='file(2)'
foo=${foo//'('/'\('}
foo=${foo//')'/'\)'}
printf '%s\n' "$foo"

(引用替换也避免了加倍反斜杠的需要)。

于 2018-07-13T17:53:12.913 回答