【问题标题】:Bash escaping issue with $@$@ 的 Bash 转义问题
【发布时间】:2013-07-21 03:19:55
【问题描述】:

我编写了一个脚本来简化运行长启动命令:

# in ~/.bash_profile
function runProgram() { sbt "run-main com.longpackagename.mainclass $@ arg3"; };
export -f runProgram;

但是,当我尝试传递多个参数时它会失败:

$ runProgram arg1 arg2
...
[info] Running com.longpackagename.mainclass arg1

arg2 和 arg3 发生了什么?它们是被 bash 吃掉的还是被 sbt 吃掉的?

如果我这样运行,脚本会按预期工作:

$ runProgram "arg1 arg2"

--

另外:这种类型的问题一直发生在我身上。我也很感激有关如何在 bash 中正确转义的参考。我尝试的firstsecond 资源没有解决这种情况。

【问题讨论】:

  • 为什么是的,它们sbt吃掉了。在这里单独引用对您没有帮助。

标签: bash escaping quotes


【解决方案1】:

对于bash 的最佳参考(包括引用的工作原理)是 bash 手册本身,它几乎肯定安装在您的计算机上,您可以通过键入 man bash 在没有互联网连接的情况下阅读它。阅读量很大,但没有真正的替代品。

不过,我将尝试解释这个特定问题。有两件重要的事情需要知道:首先,bash 如何(以及何时)将命令行拆分为单独的“单词”(或命令行参数);第二,$@$* 是什么意思。这些并非完全无关。

分词部分由特殊参数IFS控制,但我只是提一下;我假设它没有被改变。详情请见man bash

下面,我称用双引号 ("...") 弱引用 引用字符串,以及用撇号 ('...') 强引用 引用字符串。反斜杠 (\) 也是一种强引用形式。

发生分词:

  1. 在参数(shell 变量)被其值替换后,

  2. 只要有空白字符序列,

  3. 除非以任何方式引用空格,(" "' '\ 是三种方式),

  4. 在引号被删除之前。

一旦命令被分割成单词,第一个单词用于查找要调用的程序或函数,其余单词成为程序的参数。 (我忽略了很多东西,比如 shell 元字符、重定向、管道等等。更多详细信息,请参阅man bash。)

如果参数名称前面有$,则参数将替换为它们的值(步骤1),除非$name 被强引用(即'$name'\$name)。还有更多形式的参数替换。详情请见man bash

现在,$@$* 都表示“当前命令/函数的所有位置参数”,如果它们不带引号使用,它们的作用完全相同。它们被所有位置参数替换,每个参数之间有一个空格。由于这是一种参数替换(如上所述),因此在替换之后会发生分词,除非替换是在引号中,如上面的列表。

如果替换在引号中,则根据上述规则,插入参数之间的空格不受分词的影响。这正是$* 的工作原理。 $* 替换为空格分隔的命令行参数,结果是分词; "$*" 被以空格分隔的命令行参数替换为一个单词。

"$@" 是一个例外。而且,事实上,这就是$@ 存在的原因。如果 $@ 位于弱引号 ("$@") 内,则引号将被删除,并且 每个位置参数 都被单独引用。然后将这些引用的位置参数隔开并替换$@。由于 $@ 本身不再被引用,因此插入的空格确实会导致分词。最终的结果是将各个参数保留为单个单词。

如果不完全清楚,这里有一个例子。 printf 的优点是可以重复提供的格式,直到参数用完为止,这样可以很容易地看到发生了什么。

showargs() { 
  echo -n '$*:   '; printf "<%s> " $*; echo
  echo -n '"$*": '; printf "<%s> " "$*"; echo
  echo -n '"$@": '; printf "<%s> " "$@"; echo
}

showargs one two three
showargs "one two" three

(在执行之前尝试弄清楚打印的内容。)

人们常说,您几乎总是想要"$@",而几乎从不想要"$@"$*。这通常是正确的,但您也几乎从不想要"something with $@ inside of it"。要理解这一点,您需要知道 "something with $@ inside of it" 的作用。这有点奇怪,但不应该出乎意料。我们将以调用 OP 中的sbt 为例:

sbt "run-main com.longpackagename.mainclass $@ arg3"

为函数提供了两个位置参数,因此$1arg1$2arg2

首先,bash 删除了 $@ 周围的引号。但是,它不能完全删除它们,因为那里也有引用的文本。所以它必须关闭引用的文本,然后重新打开引用,产生:

sbt "run-main com.longpackagename.mainclass "$@" arg3"

现在,它可以替换为带引号的、以空格分隔的参数:

sbt "run-main com.longpackagename.mainclass ""arg1" "arg2"" arg3"

现在是分词:

sbt
"run-main com.longpackagename.mainclass ""arg1"
"arg2"" arg3"

并且引号被删除:

sbt
run-main com.longpackagename.mainclass arg1
arg2 arg3

sbt 只需要一个位置参数。你给了它两个,它忽略了第二个。

现在,假设使用单个参数"arg1 arg2" 调用该函数。在这种情况下,$@ 的替换会导致:

sbt "run-main com.longpackagename.mainclass ""arg1 arg2"" arg3"

分词产生

sbt
"run-main com.longpackagename.mainclass ""arg1 arg2"" arg3"

不带引号:

sbt
run-main com.longpackagename.mainclass arg1 arg2 arg3"

sbt 只有一个位置参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-24
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 1970-01-01
    • 1970-01-01
    • 2012-06-20
    相关资源
    最近更新 更多