0

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

# 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 中正确转义的参考。我尝试的第一个第二个资源没有解决这种情况。

4

1 回答 1

3

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

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

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

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

分词发生:

  1. 在参数(shell 变量)被替换为它们的值之后,

  2. 只要有一系列空白字符,

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

  4. 在引号被删除之前。

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

如果参数的名称以 a 开头,则参数将替换为它们的值(步骤 1),$除非$name被强烈引用(即,'$name'或,例如,\$name)。还有更多形式的参数替换。有关详细信息,请参阅man bash

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

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

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

如果不完全清楚,这里有一个例子。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

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

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

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

将两个位置参数提供给函数,即$1isarg1$2is arg2

首先,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

于 2013-07-21T05:18:05.077 回答