我正在尝试编写一个简单的 bash 解析器。我正在按照此步骤操作维基百科。我所做的假设之一是,如果我正确转义字符,我可以遍历整个输入字符串并删除所有单引号和双引号。在 bash 中运行时,这两个字符串应该产生相同的输出。
我的解析器的这一部分接受任何给定的字符串并从中删除单引号和双引号(除了转义并解释为文字的引号之外)。在 bash 中执行时,两个字符串仍应产生相同的结果。
我的解析器将原始内容转换为我的解析器,如下所示。然而,原版可以工作,但 My Parse 不行。
# Original
$ node -p "console.log($(echo \"hello world\"))"
hello world
# My Parse: Escape everything within double quotes except command substitution
v v
$ node -p \c\o\n\s\o\l\e\.\l\o\g\($(echo \"hello world\")\)
[eval]:1
console.log("hello
^^^^^^
SyntaxError: Invalid or unexpected token
对于我的解析错误的原因,我有几个想法。
我不理解双引号内的命令替换如何工作的一些基本方面。我的理解是首先发生命令替换,然后处理引号。
我不理解命令替换实际输出方式的一些基本方面。我的理解是
$(echo \"hello world\")
应该产生一个字符串"hello world"
而不是命令"hello
并且world"
该命令有一些特殊性
echo
(可能是因为它是可变的)。我实际上很幸运,这在原始场景中有效,但实际上,更改命令替换中的命令可能会破坏它......我的节点/javascript 问题有问题。这是非常简单的js,所以我不认为这就是它......
最后一件有趣的事情:当我将命令替换用双引号引起来时它会起作用。也许以不同的方式问整个问题,我如何在没有双引号的情况下编写与下面相同的输入(不包括转义的)。
# Escape everything but keep command substitution in double quotes
v v
$ node -p \c\o\n\s\o\l\e\.\l\o\g\("$(echo \"hello world\")"\)
hello world
注意:这个问题有点后续这个关于转义双引号的问题
答案1
这是分词在行动中。在我们开始之前,请仔细阅读外壳扩展注意它们的执行顺序。
看着node -p "console.log($(echo \"hello world\"))"
支撑扩张?不
波形符扩展?不
参数扩展?这里没有
命令替换?是的,留给你
node -p "console.log("hello world")"
算术扩展?不
流程替代?不
分词?的参数用
-p
引号括起来,所以不是。文件名扩展?不
报价删除完成
bash 生成node
并传递 2 个参数,-p
并且console.log("hello world")
现在看看node -p console.log($(echo \"hello world\"))
命令替换后,我们有
node -p console.log("hello world")
当我们进行分词时, to 的参数
-p
没有引号来保护它。对于当前命令,bash 有 4 个标记:node -p console.log("hello world") ^^^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^
bashnode
通过它产生3argument: -p
,console.log("hello
和world")
--console.log("hello
显然是一个 javascript 语法错误,你会看到会发生什么。
更多详细信息请参见忘记在 bash/POSIX shell 中引用变量的安全隐患
答案2
我不知道你的例子中的“node -p”应该做什么。但尝试重现您的示例结果如下:
[user@c0n1 ~]# echo "hello world"
hello world
[user@c0n1 ~]# echo $(\"hello world\")
-bash: "hello: command not found
最后一个示例中的空格打破了引号内的字符串,&bash 认为“hello”是一个命令。用反斜杠保护空白证实了这个解释:
[user@c0n1 ~]# echo $(\"hello\ world\")
-bash: "hello world": command not found
如果您尝试在命令替换中生成字符串,则需要一个命令来配合它,如下所示:
[user@c0n1 ~]# echo $(echo \"hello world\")
"hello world"
答案3
回答标记:@glen-jackman 的回答很棒并且启发了我
我的 bash 脚本如下所示:
cmds="git commit -m \"$(date)\""
echo $cmds
$cmds
当我运行脚本时,我得到了这些:
git commit -m "Fri Jan 13 10:27:05 CST 2023"
error: pathspec 'Jan' did not match any file(s) known to git
error: pathspec '13' did not match any file(s) known to git
error: pathspec '10:27:05' did not match any file(s) known to git
error: pathspec 'CST' did not match any file(s) known to git
error: pathspec '2023"' did not match any file(s) known to git
set -x
为了调试脚本,得到了这些:
++ date
+ cmds='git commit -m "Fri Jan 13 10:34:30 CST 2023"'
+ echo git commit -m '"Fri' Jan 13 10:34:30 CST '2023"'
git commit -m "Fri Jan 13 10:34:30 CST 2023"
+ git commit -m '"Fri' Jan 13 10:34:30 CST '2023"'
error: pathspec 'Jan' did not match any file(s) known to git
error: pathspec '13' did not match any file(s) known to git
error: pathspec '10:34:30' did not match any file(s) known to git
error: pathspec 'CST' did not match any file(s) known to git
error: pathspec '2023"' did not match any file(s) known to git
实际运行的命令是git commit -m '"星期五' Jan 13 10:34:30 CST '2023"',但我期望的是git commit -m "2023 年 1 月 13 日星期五 10:34:30 CST"。
这是分词在行动
我的解决方案是使用eval
修改后的脚本,如下所示:
cmds="git commit -m \"$(date)\""
echo $cmds
eval $cmds
它有效,输出如下:
++ date
+ cmds='git commit -m "Fri Jan 13 10:43:56 CST 2023"'
+ echo git commit -m '"Fri' Jan 13 10:43:56 CST '2023"'
git commit -m "Fri Jan 13 10:43:56 CST 2023"
+ eval git commit -m '"Fri' Jan 13 10:43:56 CST '2023"'
++ git commit -m 'Fri Jan 13 10:43:56 CST 2023'
[main b76daa9] Fri Jan 13 10:43:56 CST 2023
2 files changed, 21 insertions(+), 22 deletions(-)