“sh -c”不会扩展位置参数,如果我从“sudo --login”运行它。有没有解决的办法?

sou*_*edi 6 sudo shell-script

我写了一个使用位置参数的“内联脚本”,例如

$ 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
Run Code Online (Sandbox Code Playgroud)

我实际上想使用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
Run Code Online (Sandbox Code Playgroud)

软件版本:

$ 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
Run Code Online (Sandbox Code Playgroud)

Sté*_*las 9

出于同样的原因

sudo --login echo '$HOME'
Run Code Online (Sandbox Code Playgroud)

不输出$HOME,而是输出$HOME变量的内容(目标用户的,由 设置sudo)。

在您的情况下,这不是因为sh -c 没有扩展位置参数,而是因为$0, $1,$2在启动时已经扩展(由登录shell)sh

更令人惊讶的是,您为什么会看到all=1 2. 这也是你看到$@on的相同原因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壳,将运行一个称为命令echo\test作为参数。

除了 SPC 之外,还有一些sudo转义字符。这包括反引号, ;, |, (, ), *, &, =\但奇怪的是,不是$(或者这可能是这些-s/--login选项的唯一存在理由:有一个 shell 扩展变量)。

它确实包括@。如果您查看代码,它会转义除 ASCII 编号_-和之外的所有字节$

 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
   *dst++ = '\\';
Run Code Online (Sandbox Code Playgroud)

所以:

sudo -s echo '$@'
Run Code Online (Sandbox Code Playgroud)

sudo实际上运行"$SHELL" -c 'echo $\@',这解释了为什么在您的示例中,$@不是由启动的外壳程序扩展,sudo --login 而是由您告诉它运行的外壳程序扩展。

在你的

sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
Run Code Online (Sandbox Code Playgroud)

sudo运行root的登录外壳(bash在您的情况下)并告诉它解释:

sh -c echo\ \"p0\=$0\"\ \&\&\ echo\ \"p1\=$1\"\ \&\&\ echo\ \"p2\=$2\"\ \&\&\ echo\ \"all\=$\@\" sh 1 2
Run Code Online (Sandbox Code Playgroud)

参数已加入空格和所有 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
Run Code Online (Sandbox Code Playgroud)

这样在登录bash外壳两端向上运行sh-c作为第一个参数,

echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"
Run Code Online (Sandbox Code Playgroud)

作为第二个参数和sh, 12作为第三、第四和第五。

要解决它,你可以使用${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
Run Code Online (Sandbox Code Playgroud)

编辑:历史揭示了背后的原因

查看代码时有更多发现。非转义 of$显然是在 2013 年通过此更改引入的。指的是bug#564指的是bug#413

看起来在 bug#413 被“修复”之前sudo表现得像我预期的那样。

那是:

sudo -i 'some shell code'
Run Code Online (Sandbox Code Playgroud)

让登录 shell 解释那个 shell 代码(并且sudo -s 'some shell code'已经$SHELL解释了那个 shell 代码)。但是错误#413 决议打破了它,因为报告错误的人不理解它是这样工作的。并且错误#564 进一步破坏了它(试图仅恢复错误#413 解决方案引入的部分破坏)。

sudo从 2009 年开始编译1.7.1 并且-s/-i选项按我当时的预期工作。1.7.3(带有错误#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
Run Code Online (Sandbox Code Playgroud)
$ 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
Run Code Online (Sandbox Code Playgroud)

在针对该错误#413 的修复程序中引入的转义很容易被愚弄,因为\在所有字节(不是字符)前敲击 a对所有人都不起作用。

除了rcwhere\甚至不是引用运算符的明显情况之外,在大多数 shell 中,不能用 引用换行符\

$ SHELL=sh sudo -s echo $'a\nb'
ab
$ SHELL=csh  sudo -s echo $'a\nb'
a b
Run Code Online (Sandbox Code Playgroud)

这也意味着空参数被丢弃:

$ sudo printf '<%s>\n' a '' b
<a>
<>
<b>
$ sudo -s printf '<%s>\n' a '' b
<a>
<b>
Run Code Online (Sandbox Code Playgroud)

此外,通过\在每个byte之前插入 a ,它将字符转换为字符集中的其他字符,其中\在其他字符中找到字节 0x5c ( 的编码):

$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'\xa3``uname`\xa3`'
bash: line 2: Linux?: command not found
?
Run Code Online (Sandbox Code Playgroud)

0xa3 0x60 是该语言环境中的希腊语 Epsilon。并且 sudo 将 0x60s 更改为 0x5c 0x60 和 0xa3 0x5c 是希腊字母,这解释了为什么要uname运行该命令。sudo改变了

?`uname`?
Run Code Online (Sandbox Code Playgroud)

\?`\`uname\`\?`
Run Code Online (Sandbox Code Playgroud)

当然行为最终被大惊小怪的了$-$$,位置参数和$varname(其中varname是一个有效的POSIX变量名)被扩展而不是其他参数(如$@位置,而且$!$?$*$#)或${varname}$var[1]csh/ tcsh/ zsh)或$var(1)rc/ es/ ... )。