“须藤-s" 在 shell 中运行命令,但通配符或元字符不起作用

“须藤-s" 在 shell 中运行命令,但通配符或元字符不起作用

使用时sudo -s(简称“--shell”选项),可以向“sudo”传递命令,在这种情况下,它将在由“sudo”作为目标用户启动的 shell 中运行该命令。

(类似地sudo -i, 也可用作“--登录”选项,也会启动一个 shell 并类似地接受一个命令,其行为方式相同。)

在许多情况下,通过 shell 在 sudo 中运行命令可能很重要:

  • 当在当前用户无权访问但 root 可以访问的目录下使用通配符时,该命令需要在 root shell 下运行才能正确扩展通配符。
  • 运行整个管道,许多命令链接在管道(|)中。
  • 当运行 shell 内置程序时,例如forif等。在单个脚本下运行一个完整的小型内联“脚本”sudo可能会很有用。

的文档“-s”选项说(强调我的):

运行由环境变量指定的 shell SHELL(如果已设置)或由调用用户的密码数据库条目指定的 shell。如果指定了命令,则该命令将通过 shell 的-C选项。如果未指定命令,则执行交互式 shell。请注意,与交互式会话相比,大多数 shell 在指定命令时的行为有所不同;有关详细信息,请参阅 shell 手册。

换句话说,当传递sudo -s命令时,它使用选项传递到 shell -c,该选项采用带有“script”的字符串,然后继续将其作为 shell 脚本执行。

该文档并没有进一步介绍如何使用此选项,也没有提供示例,只是说“请参阅 shell 手册以获取详细信息”。这意味着收到的命令已通过直接地到 shell 的-c选项。然而,事实证明,事实并非如此。

向其传递包含多个单词的 shell 脚本会失败:

$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory

错误消息意味着它正在尝试将整个字符串作为一个简单的命令运行...嗯,好吧,所以也许添加空格会起作用?是的,情况就是这样:

$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty

但这实际上并不是 shell 的-c工作原理...好吧,让我们尝试使用一些元字符,例如~,它是主目录的快捷方式,来看看它的行为方式。请注意~需要加引号,以防止非 sudo shell 扩展它(在这种情况下,它将扩展到非 root 用户的主目录,而不是/root预期的那样):

$ sudo -s ls '~'
ls: cannot access '~': No such file or directory

好吧,所以这是行不通的,错误输出似乎意味着扩展没有发生,因为它~在那里保留了一个文字。

通配符怎么样?也不工作:

$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory

在这两种情况下,运行命令$SHELL -c都可以正常工作。在本例中,$SHELL是 bash,所以:

$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg

一个例外是变量似乎适用于sudo -s,例如:

$ sudo -s echo '$HOME'
/root

所以:

  • 这里发生了什么?
  • 为什么通配符和元字符(例如)对传递给or~的命令不起作用?sudo -ssudo -i
  • 鉴于$SHELL -c脚本采用单个字符串,但sudo -s采用多个参数,脚本如何从参数组装而成?
  • 通过 在 shell 上运行命令的可靠方法是什么sudo

答案1

长话短说:当在“--shell”或“--login”选项上执行命令时,sudo将对大多数字符进行转义(使用反斜杠转义),包括所有元字符(除了$),还包括空格。

这会破坏您想要使用 shell 的每个用例,这使得sudo -s大多数情况下不适合运行 shell 命令需要作为 shell 命令运行。

而不是sudo -s,请使用sudo sh -c '...'or sudo bash -c '...'(或任何预期的$SHELL内容。)而不是sudo -i,请使用sudo bash -l -c '...'(假设 root 的 shell 再次是 bash。)


看着sudo源代码的相关部分,有这个片段:

/* quote potential meta characters */
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
    *dst++ = '\\';
*dst++ = *src;

该片段遍历每个参数的每个字符。如果字符不是字母数字、下划线、破折号或美元符号,则会使用反斜杠进行转义。

这意味着通配符*, ?,[...]等将被转义为\*, \?, \[...\]

此外,诸如~, ;,|等元字符将被转义为\~, \;, \|

带有空格的字符串ls /root将被转义为ls\ /root.

由于某种原因,$转义是一个例外,这就是为什么sudo -s echo '$HOME'有效,因为$将保持未转义。 (请注意,这仅适用于变量,甚至不适用于表单${HOME},在这种情况下,花括号将被转义,这将破坏表达式。)


这段引用的后果是,大多数情况下要求以 root 身份运行 shell 并不能真正从以下sudo -s <command>格式中受益:

  • 通配符不会被扩展,因为它们会被转义并且不能用作通配符。
  • 管道将无法工作,因为管道|将被逃逸并且也将无法工作。
  • for像和这样的命令if通常需要使用;,这将被转义并且也不起作用。换行符是一种选择,但似乎也没有办法引入未转义的换行符。

简而言之,该表单sudo -s <command>似乎只适用于不涉及通配符、管道、脚本等的情况。但是,您通常并不真正需要 shell 来运行这些命令!对于这些情况,只需运行sudo <command>而不使用通常就足够了。-s

目前尚不清楚 sudo 实施的动机是什么。也许是为了在添加或删除-s.也许还因为-i先行-s,在这种情况下,环境变量的设置方式存在一些细微的差异......


解决方法:解决方法是显式运行 shell -c

这涉及到了解$SHELL目标用户的内容(可能与当前用户不匹配,因此$SHELL直接使用并不总是正确的。)

例如,代替sudo -s ls -l '/root/*.cfg',使用:

$ sudo bash -c 'ls -l /root/*.cfg'

sudo -i ls -l '~'使用:

$ sudo bash -l -c 'ls -l ~'

(bash 的-l参数创建一个“登录”shell,它相当于 创建的 shell sudo -i。)

相关内容