一般问题:
在 Bash 中,我知道使用变量myvar
可以通过两种方式完成:
# Define a variable:
bash$ myvar="two words"
# Method one to dereference:
bash$ echo $myvar
two words
# Method two to dereference:
bash$ echo "$myvar"
two words
在上述情况下,行为是相同的。这是因为echo
工作方式不同。在其他 Unix 实用程序中,单词是否用双引号组合在一起将产生巨大的差异:
bash$ myfile="Cool Song.mp3"
bash$ rm "$myfile" # Deletes "Cool Song.mp3".
bash$ rm $myfile # Tries to delete "Cool" and "Song.mp3".
我想知道这种差异的深层含义是什么。最重要的是,我如何才能准确查看将传递给命令的内容,以便查看它是否被正确引用?
具体奇数示例:
我将仅根据观察到的行为编写代码:
bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate" # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.
为什么我需要双引号?在没有双引号的情况下取消引用变量后,git-log 到底会看到什么?
但现在看看这个:
bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace # Now THIS works great.
bash$ git log "$nospace" # This kind of works, here is a snippet:
# From git-log output:
Date: "2018-04-12"
真恶心,为什么现在打印输出中会有双引号?看起来,如果双引号是不必要的,它们就不会被删除,当且仅当它们不是必需时,它们才会被解释为文字引号字符。
Git 传递的参数是什么?我希望我知道如何找出答案。
为了使事情变得更加复杂,我编写了一个 Python 脚本,argparse
它只打印所有参数(正如 Bash 解释它们一样,因此使用双引号文字,Bash 认为它们是参数的一部分,并且根据 Bash 认为合适的方式对单词进行分组或不分组),并且 Pythonargparse
脚本的行为非常合理。遗憾的是,我认为argparse
它可能在默默地修复 Bash 的一个已知问题,从而掩盖了 Bash 传递给它的混乱内容。这只是一个猜测,我不知道。也许 git-log 正在秘密地搞砸 Bash 传递给它的内容。
或者我可能根本就不知道到底发生了什么。
谢谢。
已编辑编辑:在得到任何答案之前,让我先说一句:我知道我可以或许用单引号将整个内容括起来,然后不要转义双引号。这实际上对我最初使用 git-log 时遇到的问题有一定帮助,但我在其他一些情况下对其进行了测试,发现它几乎同样不可预测且不可靠。在变量内部引用时会发生一些奇怪的事情。我甚至不会发布所有使用单引号时发生的奇怪事情。
编辑 2-这也不起作用:我刚刚有了一个绝妙的想法,但它根本行不通:
bash$ mydate="--date=format:%Y-%m-%d\ T%H"
bash$ git log "$mydate"
# Git log output has this:
Date: 2018-04-12\ T23
所以它没有引号括起来,但日期字符串中有一个文字反斜杠字符。此外,git log $mydate
变量中没有引号和反斜杠空格,也会出现错误。
答案1
不同的方法:
当你运行 时git log --format="foo bar"
,这些引号不会被 git 解释——它们会被 shell 删除(并保护引用的文本不被分割)。这会产生一个参数:
--format=foo bar
然而,当不引用时变量被扩展,结果经过分词,但不是通过取消引号。因此,如果您的变量包含--format="foo bar"
,它将扩展为以下参数:
--format="foo
bar"
可以使用以下方法验证:
printf'%s \ n'$变量
...以及任何打印其收到的参数的简单脚本。
/usr/bin/env perl #! 对于$i(0..$#ARGV){ 打印($i+1)。" = ".$ARGV[$i]."\n"; }
/usr/bin/env python3 导入系统 对于 i,枚举中的 arg(sys.argv): 打印(i,“=”,arg)
如果你总是有 bash 可用,首选的解决方法是使用大批变量:
myvar=( --format="foo bar" )
这样,通常的解析是在赋值期间完成的,而不是在扩展期间完成的。您可以使用此语法来扩展变量的内容,每个元素都有自己的参数:
git log "${myvar[@]}"
答案2
为什么你原来的命令不起作用?
bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate" # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.
你问:
为什么我需要双引号?在没有双引号的情况下取消引用变量后,git-log 到底会看到什么?
如果不使用双引号$mydate
,变量将被逐字扩展,并且在执行之前 shell 行将如下所示:
git log --date=format:"%Y-%m-%d T%H"
^————————————^—————— literal quotes
在这里,您(不必要地)通过\"
在变量赋值中使用文字引号来添加文字引号。
由于命令将接受单词拆分,git
将收到三个参数,,log
和--date-format:"%Y-%m%-d
,T%H"
因此抱怨找不到任何名为的提交或对象T%H"
。
正确的方法是什么?
如果要将参数放在一起,如果该参数包含空格,则必须将参数括在引号中。一般来说,总是将变量括在双引号中。
即使变量内部有空格,此方法仍然有效:
mydate="--date=format:%Y-%m-%d T%H"
git log "$mydate"
现在的第三个参数git
将是$mydate
,包括您最初指定的空格。所有引号在传递给 之前都会被 shell 删除git
。
您根本就不需要额外的引用 - 如果您想要的只是git
看到一个参数,那么在传递变量时将该参数用引号引起来"$mydate"
。
另外,你问:
bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace # Now THIS works great.
bash$ git log "$nospace" # This kind of works, here is a snippet:
# From git-log output:
Date: "2018-04-12"
你的问题:
呸,为什么现在打印的输出中有双引号?
因为你又加入了文字参数中的引号(通过转义),当您忘记在实际命令中引用变量时,这些引号将变成“真正的”引号。我说“忘记”是因为在 shell 命令中使用未加引号的变量通常只会给您带来麻烦 — 而这里它正在撤销您在最初指定变量时犯下的错误。
PS:我知道这很令人困惑,但这就是 Bash,它遵循一些明确的规则。这里没有错误。相关文章有关 shell 中的文件名的内容也非常具有启发性,因为它涉及到 Bash 中空格处理的问题。