如果我sudo
在 bash 中的一行的开头输入,它是否适用于其余命令?
换句话说,是这样的:
sudo foo | foo2 | foo3
相当于这个:
sudo foo | sudo foo2 | sudo foo3
答案1
作为一种说服自己sudo
只使用提供的第一个命令运行的方法,并且第一个管道作为原始用户 ID 运行之后的所有其他内容,您可以使用这个无用的命令链来查看它。
例子#1
$ sudo whoami > file1 | whoami > file2 | whoami > file3
然后,当您查看cat
这些文件时,您将看到以下用户名:
$ cat file{1..3}
root
saml
saml
例子#2
但是,如果您运行子 shell:
$ sudo sh -c 'whoami > file4 | whoami > file5 | whoami > file6'
然后,当您查看cat
这些文件时,您将看到以下用户名:
$ cat file{4..6}
root
root
root
例子#3
您的评论sudo foo1 | sudo foo2 ...
永远不会起作用,因为 的输出sudo foo1
将被馈送到sudo foo2
.使用我的whoami
示例表明该命令链没有执行任何操作。
$ sudo whoami | sudo whoami | sudo whoami
root
第一个运行了,但第二个和第三个没有执行任何操作,因为它们没有能力接受输入。相反,我认为你的意思是写这样的东西:
$ sudo whoami;sudo whoami;sudo whoami
root
root
root
这相当于在 3 个不同的命令提示符下运行 3 次。或这个:
$ sudo whoami && sudo whoami && sudo whoami
root
root
root
但永远不要做最后一项。它属于下一节。
调用 sudo 的晦涩方法
这些不是我最好的工作,但您可能会看到我使用 执行多个命令的其他方式sudo
。我在这里展示它们只是为了教不让别人必然做他们!
方法#1
$ echo "echo 1 > /proc/sys/vm/drop_caches" | sudo sh
怎么运行的?
由于 sudo,双引号内的 echo 程序以 root 身份运行,但将 echo 的输出重定向到仅 root 文件的 shell 仍在以您的身份运行。您当前的 shell 在启动之前执行重定向sudo
。
方式#2
$ sudo tee /proc/sys/vm/drop_caches <<<1
怎么运行的?
该方法运行tee
以 root 身份运行程序并从 a 获取输入这里的字符串sudo
它在调用命令之前运行tee
。
方式#3
# this way
$ sudo -s -- 'whoami'
# or this way
sudo -s -- sh -c 'whoami;whoami'
怎么运行的?
这些可能看起来不同,但实际上他们在做同样的事情。使用-s
开关时,sudo
将运行单个命令。我永远不知道是否有办法摆脱它。像这些都行不通。
# this
$ sudo -s -- 'whoami;whoami'
# or this
$ sudo -s -- 'whoami\;whoami'
但在查看手册页时,该-s
开关表示它将向用户文件条目中定义的 shell 传递单个命令/etc/passwd
。因此,我们使用第二种形式的技巧,主要是传递 shell,另一个 shell ( sh -c
),我们在其中“后门”要运行的命令字符串。
还有更多,但我就到此为止。这些只是为了向您展示如果您理解事物可以做什么,但不一定只是将垃圾链接在一起,因为您可以,您应该尝试将代码编译指示保持在有意义的逻辑级别,以便其他人能够理解和支持他们在未来。
答案2
不,在您的示例中仅foo
由 执行sudo
。如果您想以升级的权限运行所有命令,您可以生成一个 shell:
sudo sh -c 'foo | foo2 | foo3'
答案3
不,但是为什么呢?
不,sudo
不适用于管道的其余部分。这个另一个答案给出了一些例子,但它没有解释这一切背后的机制。
最重要的是sudo
一个单独的可执行文件,与您运行它的任何 shell 无关。有问题的命令是:
sudo foo | foo2 | foo3
对于 shell 来说,这与cat foo | foo2 | foo3
. shell 识别由三个部分组成的管道,并在sudo
(或cat
) 启动之前设置管道。 shell 不知道这foo
最终将是一个命令。因为 shellfoo
只是一个参数,它应该传递给sudo
.
在上述情况下,以下两种情况都不会发生,但即使sudo
将foo
、|
、foo2
和|
作为foo3
参数,或者将其foo | foo2 | foo3
作为一个参数,那么整个命令无论如何都不会按您的预期工作,因为|
属于 shell 语法,并且sudo
两者都不是默认情况下,shell 的工作也不运行 shell。
sudo
使用;运行多个命令两种方法
你需要一个 shell 来解释|
。这导致了两种基本方法:
sudo foo | sudo foo2 | sudo foo3
主壳体处理管道;sudo sh -c 'foo | foo2 | foo3'
一个新的高架外壳处理管道。
它与&&
和/或||
替代 或 与类似|
。
请注意,在案例 2 中,主 shell 启动sudo
, whilefoo | foo2 | foo3
只是参数之一,可以是任何内容的字符串;然后稍后sudo
开始并作为它的参数sh
传递。foo | foo2 | foo3
只有内部sh
字符串才被解释为 shell 代码。
我写的sh -c
,但你可以使用另一个shell。如果您需要 plain 不支持的功能,这尤其有意义sh
。
做出明智的选择
一般来说,我更喜欢带有附加 shell 的方法(方法 2)。这些是我的理由:
只有一个
sudo
,它最多会要求提供一次凭据。在 的情况下sudo … | sudo …
,根据配置(中sudoers
),每个sudo
可能会单独询问。该工具足够智能,因此使用同一终端的多个实例不会互相干扰,并且会按顺序询问,即使 shell 并行运行它们也是如此。如果情况更糟,sudo … && sudo …
因为通常第二个sudo
可能(有条件地)在数小时后开始,所以即使sudo
配置为不过于频繁地询问您的密码,第二个也sudo
可能会询问,并且您可能无法预测何时会发生这种情况。如果涉及重定向,那么您将不需要欺骗与
tee
(也不sudo cat … | …
代替输入重定向)。让提升的 shell 处理重定向以最自然的方式解决问题。这假设您确实想以 root 身份打开一些文件。
另一方面,方法 2 也有缺点。在某些情况下,您可能需要选择方法 1。需要考虑的事项:
sudo some_shell -c …
运行一个新的高架some_shell
。可能存在错误some_shell
,或者您传递给它的 shell 代码中可能存在错误;错误可能会导致 shell 执行意外操作。提升进程做一些意想不到的事情是一件非常糟糕的事情。因此,您可能想要提升foo
、foo2
和foo3
,但仅此而已。传递给的 Shell 代码
sh -c
必须是单个参数。几乎在任何情况下(当然在您的情况下)这都需要引用或转义空格和其他字符。额外的引用/转义级别增加了复杂性。一般来说,您需要知道如何正确引用/转义已经引用或转义的内容。潜在有用的资源:如何在 Bash 中方便地使用单引号或转义整个命令行?
由 启动的 shell
sudo
无法访问主 shell 的变量。导出变量可能有帮助,也可能没有帮助;sudo
可以配置为将它们从环境中删除。将主 shell 扩展的变量嵌入到另一个 shell 的代码中是错误的嵌入{}
,除非您知道该值对此是安全的。我的意思是一般来说这是错误的:sudo sh -c "foo \"$var\" | foo2"
有多种方法可以安全地传递变量。我只列出它们(可能还有其他):
var="$var" sudo -E sh -c 'foo "$var" | foo2' # may or may not work depending on sudoers and security policy sudo var="$var" sh -c 'foo "$var" | foo2' # may or may not work depending on sudoers and security policy sudo env var="$var" sh -c 'foo "$var" | foo2' sudo sh -c "foo ${var@Q} | foo2" # if the main shell is Bash sudo sh -c 'foo "$1" | foo2' sh "$var"
第 1 号方法是:
sudo foo "$var" | sudo foo2
这既简单又安全。
假设您对退出状态感兴趣
foo
和/或foo2
等sudo sh -c 'foo | foo2 | foo3'
不会给您。即使您使用在运行管道后存储此信息的 shell(例如带有其 的 Bash$PIPESTATUS[@]
),将信息从运行的内 shell 可靠地传递sudo
到外部主 shell 也是一项不简单的任务,并且需要一些繁琐的代码(例如内壳和外壳分别)。同样,方法 1 很简单:
sudo foo | sudo foo2 | sudo foo3
每个
sudo
(只要它成功完成其工作)将中继相应foo*
命令的退出状态。现在您所需要的只是主 shell 能够告诉您管道中每个命令的退出状态($PIPESTATUS[@]
如果主 shell 是 Bash)。如果
sudoers
允许您运行有限数量的命令,sudo
那么它很可能不允许sh
(以及任何其他不受限制的 shell 或类似 shell 的实用程序,因为否则您无论如何都可以运行任何命令sudo
)。如果foo
、foo2
和foo3
允许使用sudo
,但 shell 不允许,则方法 1 是您唯一的选择。
更广阔的前景
还有其他命令的行为类似于sudo
:nohup
、、、、...nice
ionice
unshare
甚至echo
是这样的。你没想到echo
在
echo foo | foo2 | foo3
以|
和 其余部分作为其论据。从外壳中取出foo
,就是这样,几乎没有人感到惊讶。
在任何(?) shell 中echo
都是内置的,即 shell 的一部分。 “Builtin”意味着 shell 不运行外部echo
可执行文件,它自己处理 echo 的工作;但内置的行为就像外部echo
(不过,有一些涉及信号的微妙之处)。
至少有一个命令在某些情况下表现不同,它是time
.它是一个单独的可执行文件,当这样调用时,它的行为就像sudo
在您的问题的上下文中一样。例如这个:
command time -p sleep 2 | sleep 4
在空闲操作系统中应该实时报告 2 秒(time
打印到 stderr,因此尽管有管道,您仍会看到报告)。然而在 Bash 中time
是关键词这改变了 Bash 解析后续内容的方式。它是故意这样的,因此您可以测量整个管道(或包括 shell 循环等的 shell 代码片段),而不会因生成额外的 shell 而产生任何偏差。尝试这个:
# in Bash
time -p sleep 2 | sleep 4
您将在报告中看到 4 秒。
比较这个问题以及我的回答。
答案4
是的,但命令以;
、|
或结尾。&&
||
不,它不适用于所有复杂命令(例如a|b
)或脚本。