为什么sudo cat会提示权限被拒绝,但sudo vim却可以正常工作?

101

我尝试在我的arch的pacman.conf文件中使用shell脚本中的echo命令自动添加存储库源,但是它失败了,就像这样:

sudo echo "[archlinuxfr]" >> /etc/pacman.conf
sudo echo "Server = http://repo.archlinux.fr/\$arch" >> /etc/pacman.conf
sudo echo " " >> /etc/pacman.conf

-bash: /etc/pacman.conf: Permission denied

如果我使用vim手动更改/etc/pacman.conf文件,操作如下:

sudo vim /etc/pacman.conf

使用:wq退出vim后,一切都正常工作,我的pacman.conf已经手动更新,没有出现“权限被拒绝”的投诉。

为什么会这样?我该如何让sudo echo起作用?(顺便说一下,我也尝试过使用sudo cat,但同样出现了“权限被拒绝”的错误)

6个回答

154
正如@geekosaur所解释的那样,Shell在运行命令之前进行重定向。当您输入以下内容时:
sudo foo >/some/file

您当前的Shell进程会复制自身,首先尝试打开/some/file进行写入,如果成功,则将该文件描述符设置为其标准输出,仅当这一步成功后才执行sudo。但是,此过程在第一步失败了。

如果允许(sudoer配置通常不允许运行Shell),您可以尝试以下操作:

sudo bash -c 'foo >/some/file'

总的来说,我发现一个好的解决方案是使用| sudo tee代替>,以及使用| sudo tee -a代替>>。尤其是当重定向是我需要sudo的唯一原因时,这个方法特别有用;毕竟,不必要地以root用户身份运行进程正是sudo被创建以避免的。而以root用户身份运行echo就很傻了。

echo '[archlinuxfr]' | sudo tee -a /etc/pacman.conf >/dev/null
echo 'Server = http://repo.archlinux.fr/$arch' | sudo tee -a /etc/pacman.conf >/dev/null
echo ' ' | sudo tee -a /etc/pacman.conf >/dev/null

我在结尾添加了> /dev/null,因为tee命令会将其输出发送到命名文件和自己的标准输出,而我不需要在终端上看到它。( tee 命令的作用类似于物理管道中的“T”连接器,这也是它得名的原因。) 我使用单引号 ('...') 替换双引号 ("..."),这样所有内容都是字面量,我不必在 $arch 中加反斜杠。(如果没有引号或反斜杠,$arch 将被 shell 参数 arch 的值替换,该参数可能不存在,此时 $arch 被替换为空并消失。)
所以,这就解决了使用sudo以root身份写入文件的问题。现在,让我们详细讨论一下在shell脚本中输出包含换行符文本的各种方法。我的首选解决方案是将here-document输入到上述sudo tee命令中;这样就不需要使用catechoprintf或任何其他命令了。单引号移到了sentinel introduction <<'EOF',但它们在那里具有相同的效果:正文被视为字面文本,因此$arch保持不变。
sudo tee -a /etc/pacman.conf >/dev/null <<'EOF'
[archlinuxfr]
Server = http://repo.archlinux.fr/$arch

EOF

但是,虽然这是我会做的方式,但还有其他选择。以下是几个例子:
您可以坚持每行一个echo,但将它们全部分组在子shell中,这样您只需要一次附加到文件中:
(echo '[archlinuxfr]'
 echo 'Server = http://repo.archlinux.fr/$arch'
 echo ' ') | sudo tee -a /etc/pacman.conf >/dev/null

如果你在echo后面加上-e(并且你使用的是支持非POSIX扩展的shell),你可以使用\n将换行符直接嵌入字符串中:
# NON-POSIX - NOT RECOMMENDED
echo -e '[archlinuxfr]\nServer = http://repo.archlinux.fr/$arch\n ' | 
  sudo tee -a /etc/pacman.conf >/dev/null

但是正如上面所说,这不是POSIX指定的行为;你的shell可能只会回显一个带有大量字面值\n的字符串,而紧随其后的是一个字面值-e。POSIX的做法是使用printf而不是echo;它自动将其参数视为echo -e一样处理,但不会自动在末尾添加换行符,因此你还需要再加一个\n

printf '[archlinuxfr]\nServer = http://repo.archlinux.fr/$arch\n \n' | 
  sudo tee -a /etc/pacman.conf >/dev/null

使用这两种解决方案之一,命令所获得的参数字符串包含两个字符的序列\n,而命令程序本身(在printfecho内部的代码)将其转换为换行符。在许多现代Shell中,您可以使用ANSI引号$'...',它会在命令程序看到字符串之前将诸如\n之类的序列转换为文字换行符。这意味着这样的字符串适用于任何命令,包括普通旧式的无-eecho命令:

echo $'[archlinuxfr]\nServer = http://repo.archlinux.fr/$arch\n ' | 
  sudo tee -a /etc/pacman.conf >/dev/null

但是,虽然比echo -e更便携,ANSI引号仍然是一个非POSIX扩展。

再次强调,虽然这些都是选项,但我更喜欢上面直接使用tee <<EOF的解决方案。


有趣!你能添加一条说明,解释为什么还要将文件分块到 /dev/null 吗? - knite
在我的OSX上,sudo bash -c 'foo >/some/file'可行 :-) - kayochin
我更倾向于使用这个答案,因为它适用于多行文本。cat <<EOF | sudo tee -a /some/file > /dev/null ... - ltvan
这基本上就是我的最后答案,除了你添加了一个多余的 cat 进程。 :) - Mark Reed

60
问题在于重定向是由您的原始Shell处理的,而不是由sudo处理的。 Shell无法读取思想,并且不知道特定的>>是为sudo而不是其他用途。
您需要:
  1. 引用重定向(以便将其传递给sudo
  2. 并且使用sudo -s(以便sudo使用Shell处理引用的重定向)。

1
这样分两步来做是可以的: (1) sudo -s (2) echo "# test" >> /etc/pacman.conf但是有没有可能在一行命令中执行呢? - Calvin Cheng
16
我尝试告诉你的是 sudo -s 'echo "# test" >>/etc/pacman.conf'。我会翻译成:这条命令意思是在/etc/pacman.conf文件中添加# test这一行,需要使用sudo权限才能执行。 - geekosaur
2
实际上,在阅读了您上面的答案后,我尝试过了,但是出现了一个奇怪的错误,如下所示:sudo -s 'echo "# test" >> /etc/pacman.conf' /bin/bash: echo "# test" >> /etc/pacman.conf: No such file or directory,这就是为什么我随后尝试了两步手动过程。 - Calvin Cheng
21
这种方式的最后一次尝试成功了 - echo 'echo "# test" >> /etc/pacman.conf' | sudo -s - Calvin Cheng
1
我在哪里可以找到有关禁用“-s”选项的sudo配置信息?是通过visudo吗? - Calvin Cheng
显示剩余3条评论

18

http://www.innovationsts.com/blog/?p=2758

作为上述指令不是很清晰,我将使用那篇博客文章中的指令,附带示例以便更容易看到您需要做什么。
注意,在管道中的第二个命令(gzip 命令)导致了错误。这就是我们使用 bash -c 选项的技巧所在。
我们可以从 ls 命令的输出中看到压缩文件已成功创建。
第二种方法与第一种类似,因为我们正在通过 sudo 将命令字符串传递给 bash,但我们是通过管道来完成的。

1
对于这样一个简单的命令,使用sh而不是bash可能会稍微更好。例如:sudo sh -c 'cat /root/example.txt | gzip > /root/example.gz'。但除此之外,解释得很好,加1。 - bryce

7
sudo bash -c 'echo "[archlinuxfr]" >> /etc/pacman.conf'

1

步骤1 在一个bash文件中创建一个函数(write_pacman.sh

#!/bin/bash

function write_pacman {
 tee -a /etc/pacman.conf > /dev/null << 'EOF'
  [archlinuxfr]
  Server = http://repo.archlinux.fr/\$arch
EOF
}

"

'EOF'不会解释$arch变量。

STE2源码bash文件。

"
$ source write_pacman.sh

步骤三 执行函数
$ write_pacman

EOF引号的作用是什么? - bilabila

0

追加文件(sudo cat):

cat <origin-file> | sudo tee -a <target-file>

将 echo 追加到文件中(sudo echo):

echo <origin> | sudo tee -a <target-file>

(额外)忽略输出:

echo >origin> | sudo tee -a <target-file> >/dev/null

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接