我如何开始使用'('作为用户输入而不必在整个等式中键入转义符号或引号?

时间:2018-05-23 16:15:36

标签: bash shell escaping

我使用递归下降解析器在C中编写了一个简单的计算器。这一切都很有效,但当我开始使用'('符号。

时出现问题

我从argv [1]获取用户输入,并将内容放在全局变量中以简化操作。比我简单地浏览字符串中的每个字符并查找它可能是什么模式,ex数字,添加符号或乘法(就像常规的递归下降解析器工作,没有真正奇特的工作)。

但是,如果我这样做:

./calculator (1+2)*0.5

我收到错误消息

  

bash:意外标记“1 + 2'

附近的语法错误

这是因为我必须逃避'('和')'所以

./calculator \(1+2\)*0.5

工作正常。

所以我的问题是:

如何解决这个问题,而不必考虑将单引号或双引号放在等式中还是逃避它们?

为什么0.5 *(1 + 2)仍在工作?我是否还必须逃避括号?

2 个答案:

答案 0 :(得分:4)

这里发生的是你的线看起来像一个函数定义:

$ ./calculator () {
>     echo "function called with arguments '$@'"
> }

定义了一个名为./calculator shell函数。然后可以像执行命令一样调用它:

$ ./calculator arguments go here
function called with arguments 'arguments go here'

你的错误源于这样一个事实:Bash希望(后跟),因为它是一个正确的函数定义,但你的父母不是空的!

Bash shell在命令行中保留了许多元字符,而不仅仅是括号。 *用于生成路径名。不同的炮弹有不同的作在Z shell(zsh)中,即使这样也行不通:

% ./calculator 1*2
zsh: no match

必须在命令行上给出所有这些元字符。不要学习“安全子集”,因为很快你会尝试另一个shell而且它会失败。或者这可能发生:

$ echo 1*2
1*2
$ touch 1-31337-2
$ echo 1*2
1-31337-2

有两种简单的解决方案可以避免反斜视

  1. 在所有内容周围使用单引号:

    $ ./calculator '(1+2)*0.5'
    
    如果您的字符串不包含'

    可以很好地工作。

    双引号也可以,但有more meta characters that are reserved by bash within double quotes,例如$

  2. 从标准输入读取计算,并提示

    $ ./calculator
    calculator> 1 + 2 * 0.5
    

    您也可以使用readline库进行简单的交互式编辑!

答案 1 :(得分:2)

摘要:引用您的表达式(最好使用单引号)或使用()之外的其他内容进行分组。

要回答“为什么0.5*(1+2)有效?”,请转到最后。 (提示:这是因为你没有名为0.5的文件。)

括号是bash手册所指的metacharacters。 (Posix不再使用这个术语;相反,它指的是诸如“运算符”之类的字符。但基本效果是相同的。)除非引用,元字符总是自己的标记(或者,如<<&& <{1}},以及他们开始的其余操作员),并且它们具有句法意义。

这与大括号({})不同,后者是保留字,而不是元字符,因此不会分隔标记。作为保留字,它们只有在它们自己作为标记时才具有特殊意义,并且是命令中的第一个标记:

{echo x      # The command to be executed is `{echo`, which probably doesn't exist
echo {x      # No problem. Prints the string '{x'
echo { }     # Also no problem. Prints '{ }'
{ echo x; }  # A compound command. The ; is necessary.
(echo x)     # Also a compound command but ; and whitespace are optional

[]有些相似。 [是一个命令(甚至不是保留字),而[[是一个保留字,用于启动条件复合命令,但前提是它只是命令中的第一个字。

因此,您可以使用括号或大括号作为分组运算符,而不必担心引用,因为函数的参数永远不会成为命令中的第一个单词。

作为旁注,[(命令)和[[(保留字)之间的差异由以下事实表明:只有第一个可以在变量赋值之前(在这种情况下,赋值没有任何有用的效果):

$ foo=3 [ -z "$foo" ] && echo yes
yes
$ foo=3 [[ -z "$foo" ]] && echo yes
[[: command not found
$ [[ -z "$foo" ]] && echo yes
yes

括号的精确句法意义通常取决于它们出现的语法。如果是(,则可能是:

  • 功能定义:

    func () { echo "$@"; }
    
  • 在子shell中执行的复合命令

    (sleep 1; echo "Hello..."; sleep 5; echo "World!")&
    
  • 围绕案例子句中的模式:

    case "$word"; in
      (Hello) echo "Hi" ;;
         Bye) echo "Seeya" ;;  # The open parenthesis is optional in this syntax
    esac
    
  • 在bash中,它也可以用作数组赋值的一部分:

    local numbers=(one two three)
    

    它可以构成((运算符的一部分,用于算术条件复合命令和算术for语句。

括号也可能显示为不以括号开头的较长构造的一部分,例如命令替换:$(。但是如果括号被识别为一个标记并且它不适合包含括号的任何句法结构,则会发出语法错误信号:

$ echo a b(c)
bash: syntax error near unexpected token `('

这给我们留下了一个小小的谜团:我们如何解释以下内容:

$ echo a+(b+4)
a+(b+4)
$ echo a-(b+4)
bash: syntax error near unexpected token `('
$ echo a*(b+4)
a*(b+4)
$ echo a/(b+4)
bash: syntax error near unexpected token `('

答案是我的bash启动文件中有shopt -s extglob。而且你也可能这样做,因为许多发行版默认为你做这件事。如果“扩展的glob”模式可用,则以下是模式:

?(pattern-list)
       Matches zero or one occurrence of the given patterns
*(pattern-list)
       Matches zero or more occurrences of the given patterns
+(pattern-list)
       Matches one or more occurrences of the given patterns
@(pattern-list)
       Matches one of the given patterns
!(pattern-list)
       Matches anything except one of the given patterns

模式列表只能包含单个模式,因此b+4是有效模式,因此a+(b+4)将匹配名称以a开头的文件,后跟一个或多个字符实例b+4

$ touch ab+4b+4b+4
$ echo a+(b+4)
ab+4b+4b+4

与任何其他文件名模式一样,如果没有匹配的文件名,则不替换该模式:

$ rm ab+4b+4b+4
$ echo a+(b+4)
a+(b+4)

除非您设置了其他shell选项:

$ shopt -s failglob
$ echo a+(b+4)
bash: no match: a+(b+4)