我编写了一个使用位置参数的“内联脚本”,例如
$ sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2
我实际上想使用 运行我的内联脚本sudo --login
。但这样位置参数就不起作用了。有办法让它们发挥作用吗?
(我也感兴趣该行为是否在某处被记录或标准化)。
$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=-bash
p1=
p2=
all=1 2
软件版本:
$ sh --version
GNU bash, version 5.0.7(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ sudo --version
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27
答案1
为了同样的原因
sudo --login echo '$HOME'
不输出$HOME
,而是输出变量的内容$HOME
(目标用户的,由 设定sudo
)。
就你而言,这不是因为sh -c 不扩展位置参数,但因为$0
, $1
,在启动$2
时已经展开(通过登录 shell) 。sh
这里更令人惊讶的是为什么你会看到all=1 2
.这和你看到$@
的原因是一样的sudo --login echo '$@'
。
-s
/--shell
和-i
/都--login
运行 shell 来运行命令。但sudo
以一种非常奇怪的方式做到了。
我希望sudo -s 'some shell code'
运行一个 shell 来解释some shell code
,但这不是它的作用。对于-s
,它仍然期望运行一个简单的命令,并尝试引用(用\
)一些字符,希望通过该 shell 实现它。
你不应该这样做sudo -s 'echo test'
,但是sudo -s echo test
。在第一种情况下,sudo 实际上echo\ test
作为 shell 代码传递。在类似 Bourne 的 shell 中,它会尝试运行名为 的命令'echo test'
。使用rc
shell,它将运行一个echo\
以test
参数为参数的命令。
除了SPC之外,还有一些sudo
转义字符。这包括反引号,,,,,,,,,,,,;
但奇怪的是,没有(或者也许这是这些/选项唯一存在的理由:让 shell 扩展变量)。|
(
)
*
&
=
\
$
-s
--login
但它确实包括@
。如果你看代码,它转义除 ASCII 算数、_
和-
之外的所有字节$
。
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$') *dst++ = '\\';
所以:
sudo -s echo '$@'
sudo
实际上运行"$SHELL" -c 'echo $\@'
,这解释了为什么在您的示例中,$@
不是由启动的 shell 扩展sudo --login
而是由您告诉它运行的 shell 扩展。
在你的
sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
sudo
运行root
的登录 shell(bash
在您的情况下)并告诉它解释:
sh -c echo\ \"p0\=$0\"\ \&\&\ echo\ \"p1\=$1\"\ \&\&\ echo\ \"p2\=$2\"\ \&\&\ echo\ \"all\=$\@\" sh 1 2
参数已用空格和所有 SPC、、、、、 字符连接,"
但尚未转义。因为那些没有被转义,所以 bash 登录 shell 会扩展, ,但不是因为反斜杠。=
&
@
$
$
$0
$1
$2
$\@
您可以通过以下方式查看它:
$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo\ \"p0\=$0\"\ \&\&\ echo\ \"p1\=$1\"\ \&\&\ echo\ \"p2\=$2\"\ \&\&\ echo\ \"all\=$\@\" sh 1 2
这样登录bash
shell 最终会sh
以-c
第一个参数运行,
echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"
作为第二个参数,sh
,1
作为2
第三、第四和第五个参数。
要解决此问题,您可以使用${0}
代替$0
,因为sudo
将其转换为$\{0\}
可以防止登录 shell 将其扩展为内容它是 $0
:
$ sudo --login sh -c 'echo "p0=${0}" && echo "p1=${1}" && echo "p2=${2}" && echo "all=${@}"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2
编辑:历史揭示了其背后的原因
查看代码时会发现更多发现。不可转义$
显然是在 2013 年引入的通过这个改变。指的是错误#564这是指错误#413。
看起来 bug#413 之前是“固定的”,sudo
表现如我所料。
那是:
sudo -i 'some shell code'
让登录 shell 解释该 shell 代码(并且sudo -s 'some shell code'
已$SHELL
解释该 shell 代码)。但 bug#413 决议打破了它,因为报告该 bug 的人不明白它是这样工作的。 bug#564 进一步破坏了它(试图仅恢复 bug#413 解决方案造成的部分破坏)。
我从 2009 年开始编译sudo
1.7.1,-s
/-i
选项就如我所期望的那样工作了。1.7.3
(bug#413 解决方案)的工作方式与您显然预期的一样:
$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory
$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2
在 bug#413 的修复中引入的转义很容易被愚弄,因为\
在所有字节(而不是字符)前面敲击 a 并不适用于所有字节。
rc
除了where甚至不是引号运算符这一明显情况之外\
,在大多数 shell 中,换行符不能用 引号\
:
$ SHELL=sh sudo -s echo $'a\nb'
ab
$ SHELL=csh sudo -s echo $'a\nb'
a b
这也意味着空参数被丢弃:
$ sudo printf '<%s>\n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>\n' a '' b
<a>
<b>
\
另外,通过在每个之前插入字节,它将字符转换为字符集中的其他字符,其中\
在其他字符中找到字节 0x5c( 的编码):
$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'\xa3``uname`\xa3`'
bash: line 2: Linuxα: command not found
α
0xa3 0x60 是该语言环境中的希腊语 Epsilon。 sudo 将 0x60s 更改为 0x5c 0x60 和 0xa3 0x5c 是希腊字母,这解释了uname
运行该命令的原因。sudo
改变了
ε`uname`ε
到
\α`\`uname\`\α`
当然,行为最终会令人惊讶,因为, $-
,$$
位置参数 和$varname
(其中varname
是有效的 POSIX 变量名)被扩展,但其他参数没有扩展(像$@
这里一样,还有$!
, $?
, $*
, $#
)或${varname}
or $var[1]
( csh
// ) 或(// ...)。tcsh
zsh
$var(1)
rc
es