使用 Bash 变量时,$myvar 和 "$myvar" 有什么区别?(特定奇怪的行为)

使用 Bash 变量时,$myvar 和 "$myvar" 有什么区别?(特定奇怪的行为)

一般问题:

在 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%-dT%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 中空格处理的问题。

相关内容