我有一个脚本,希望它接收一些变量参数并将它们传递给另一个脚本的执行,但我只能传递第一次调用设置的第一个参数。
我的脚本(nodetool_improved
)如下所示:
sudo su cassandra -s /bin/bash -c "/usr/local/cassandra/bin/nodetool -u cassandra -pwf /opt/apps/cassandra/resources/security/jmxremote.password $@"
如果我这样称呼它:
$ nodetool_improved status
然后/usr/local/cassandra/bin/nodetool
接收status
,输出看起来不错。但如果我像这样调用我的脚本:
$ nodetool_improved help status
然后输出不显示选项的帮助status
,而只显示所有选项的帮助,这实际上与调用相同:
$ /usr/local/cassandra/bin/nodetool help
这意味着help status
不会被传递给/usr/local/cassandra/bin/nodetool
。只有help
。
我怎样才能更改nodetool_improved
脚本以便将所有参数传递给/usr/local/cassandra/bin/nodetool
?
其他一些信息
- Nodetool 是一个管理 Apache Cassandra 的工具:https://cassandra.apache.org/doc/latest/tools/nodetool/help.html
cassandra
用户尚未登录
我尝试过一些方法,但没有效果:
sudo su cassandra -s /bin/bash -c "/usr/local/cassandra/bin/nodetool -u cassandra -pwf /opt/apps/cassandra/resources/security/jmxremote.password -- '$@'"
错误:
status': -c: line 0: unexpected EOF while looking for matching `''
status': -c: line 1: syntax error: unexpected end of file
sudo su cassandra -s /bin/bash -c '/usr/local/cassandra/bin/nodetool -u cassandra -pwf /opt/apps/cassandra/resources/security/jmxremote.password "$@"' -- "$@"
错误:
只有status
被拾取。输出与我这样调用时相同:
/usr/local/cassandra/bin/nodetool status
答案1
总结
sudo su cassandra -s /bin/bash -c \
'exec /usr/local/cassandra/bin/nodetool \
-u cassandra \
-pwf /opt/apps/cassandra/resources/security/jmxremote.password \
"$@"' \
-- my-inner-shell -- "$@"
甚至
sudo su cassandra -s /usr/local/cassandra/bin/nodetool -- \
-u cassandra \
-pwf /opt/apps/cassandra/resources/security/jmxremote.password \
"$@"
分析
首先,让我们明确说明,在您的命令中sudo su
运行su
并逐字传递所有剩余的参数。然后su cassandra -s /bin/bash
运行/bin/bash
并逐字传递所有剩余的参数。这些步骤与您的问题无关。重要的是内部 Bash 得到了什么。
你的第一个命令
sudo su cassandra -s /bin/bash -c "/usr/local/cassandra/bin/nodetool -u cassandra -pwf /opt/apps/cassandra/resources/security/jmxremote.password $@"
非常有缺陷。在完成其操作后sudo
,su
运行bash
情况就像用户cassandra
执行了以下操作一样:
# flawed
/bin/bash -c "something $@"
导致问题的直接原因是,在(和和内部)开始$@
之前,外壳就已经将双引号扩展了。双引号扩展为(可能)单独的参数。其中第一个与双引号中的字符串(如果有)连接;最后一个与双引号中的字符串(如果有)连接。当您传递和时,就好像用户运行:sudo
su
bash
$@
$@
$@
help
status
cassandra
/bin/bash -c "something help" "status"
和仅有的 something help
是代码。这就是status
被忽略的原因。
使用的版本也通过外壳"something '$@'"
进行扩展($@
外部引号很重要)并产生如下命令:
/bin/bash -c "something 'help" "status'"
这甚至更糟,因为something 'help
解释为代码在语法上无效;那里有不匹配的单引号,因此unexpected EOF
。
一般来说,允许一些外部工具扩展字符串中的任何内容(这些内容将成为内部工具的代码)只有在您完全控制扩展结果的情况下才是安全的。如果事先不知道或没有清理(这可能很难),那么您将面临代码注入漏洞。例如,当您运行此
bash -c "echo $variable"
并且$variable
恰好扩大到; rm -f important_file
然后你将有效地运行
bash -c "echo; rm -f important_file"
你不能通过引用来解决这个问题。例如这个
bash -c "echo '$variable'"
$variable
如果扩展为'; rm -f important_file'
(其中单引号属于变量),将会彻底失败。
您的代码存在缺陷,不仅因为$@
可以扩展到多个参数,导致除了一个参数之外的所有参数都被忽略。未被忽略的参数能够注入代码。
这是一个普遍的问题,不仅仅存在于shell+shell场景中。外部工具例如可以是find
。
寻求解决方案
正确的做法是将外壳的位置参数作为位置参数(而不是代码)传递给内壳。您可以尝试以下方法:
sudo su cassandra -s /bin/bash -c '/usr/local/cassandra/bin/nodetool -u cassandra -pwf /opt/apps/cassandra/resources/security/jmxremote.password "$@"' -- "$@"
几乎是正确的事情。
这就像用户cassandra
运行:
/bin/bash -c 'something "$@"' -- "$@"
在引用和避免代码注入方面,这非常好。代码是单引号,因此外壳不会扩展第一个$@
。在内壳解释的代码中$@
是双引号理所应当。這部分很好。
问题出在-- "$@"
。Bash 遵循本公约其中--
option 表示选项的结束。如果从第二个扩展$@
(由外壳执行)中出现看起来像选项的东西,最好使用它。后面的第一个非选项参数-c 'shell code'
变成$0
内壳。接下来的内容变成$1
,$2
等等。在你的情况下,这些来自第二个 的扩展$@
,但外部$1
变成内部$0
,外部$2
变成内部$1
等等。这就是为什么当你传递help
和时status
,会help
“消失”。
解决方案是提供一个将成为内部的“虚拟”参数$0
。这样外部$1
将成为内部$1
等等。这样的“虚拟”参数并不是真正的虚拟参数,它有其用途。请阅读中的第二个 sh 是什么sh -c 'some shell code' sh
?
因此代码片段应该是这样的:
/bin/bash -c 'something "$@"' -- some-name "$@"
现在你想sudo su …
在前面添加。我测试过sudo
:一般来说它支持--
,但在看起来像命令(例如)之后su
它既sudo su …
不需要也不会解释--
。它与su
自身不同。起初我以为su
使用后面的所有内容-s
来构建命令。然后我认为它使用后面的所有内容-c
作为参数。在任何这些情况下--
都不需要,我期望su
它的行为有点像sudo
。
但不行。在我的 Debian 10 中,这个命令:
su kamil A -c B -s /bin/echo C -c D E -- -c F G
就像用户kamil
运行一样:
/bin/echo -c D A C E -c F G
的选项参数可以-s
是任何可执行文件。su
它会搜索自身的选项,直到遇到--
,无论超出-s
还是超出。之前-c
的最新选项将“获胜”。然后,该工具将在其后找到的自己的选项参数连同前导参数一起传递给可执行文件,后跟它未识别为选项或自身选项参数的任何参数。-c
--
-c
-c
这意味着您需要将--
destined forsu
实际传递--
给 shell。您需要类似以下内容的内容:
sudo su cassandra -s /bin/bash -c 'something "$@"' -- -- some-name "$@"
我遇到过su
这种情况,在某些情况下(并非所有情况下)是“consumed”-- --
而不是--
。这可能是一个奇怪的错误。用另一个参数分隔两个双破折号(如果可能)似乎是一种解决方法。所以这可能更好一点:
sudo su cassandra -s /bin/bash -c 'something "$@"' -- some-name -- "$@"
并且由于内壳仅用于运行单个命令,因此可能是他的:
sudo su cassandra -s /bin/bash -c 'exec something "$@"' -- some-name -- "$@"
但是由于-s
接受任何可执行文件并且您可以向它传递任何参数(即使它不支持-c
),您可以完全摆脱/bin/bash
:
sudo su cassandra -s something -- "$@"
答案开头的两个解决方案是对上述两个“模板”的改编。
答案2
我想我找到了另一个可能的解决方案。以防您不能或不想使用 sudo/su。这个解决方案对我有用。
我知道 eval 是邪恶的。但有时你需要邪恶力量的帮助 :)
#!/usr/bin/env bash
$command='ls'
$args='-lah'
eval $(printf "bash -c '%q %q'" "${command}" "${args}")