为什么 sudo cat 给出的许可被拒绝,但 sudo vim 工作正常?

我试图在 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,但是失败了,同时也拒绝了许可)

93418 次浏览

The problem is that the redirection is being processed by your original shell, not by sudo. Shells are not capable of reading minds and do not know that that particular >> is meant for the sudo and not for it.

You need to:

  1. quote the redirection ( so it is passed on to sudo)
  2. and use sudo -s (so that sudo uses a shell to process the quoted redirection.)

As @geekosaur explained, the shell does the redirection before running the command. When you type this:

sudo foo >/some/file

Your current shell process makes a copy of itself that first tries to open /some/file for writing, then if that succeeds it makes that file descriptor its standard output, and only if that succeeds does it execute sudo. This is failing at the first step.

If you're allowed (sudoer configs often preclude running shells), you can do something like this:

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

But I find a good solution in general is to use | sudo tee instead of > and | sudo tee -a instead of >>. That's especially useful if the redirection is the only reason I need sudo in the first place; after all, needlessly running processes as root is precisely what sudo was created to avoid. And running echo as root is just silly.

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

I added > /dev/null on the end because tee sends its output to tee2 the named file tee3 its own standard output, and I don't need to see it on my terminal. (The tee command acts like a "T" connector in a physical pipeline, which is where it gets its name.) And I switched to single quotes ('...') instead of doubles ("...") so that everything is literal and I didn't have to put a backslash in front of the $ in $arch. (Without the quotes or backslash, $arch would get replaced by the value of the shell parameter tee0, which probably doesn't exist, in which case the $arch is replaced by nothing and just vanishes.)

So that takes care of writing to files as root using sudo. Now for a lengthy digression on ways to output newline-containing text in a shell script. :)

To BLUF it, as they say, my preferred solution would be to just feed a here-document into the above sudo tee command; then there is no need for cat or echo or printf or any other commands at all. The single quotation marks have moved to the sentinel introduction <<'EOF', but they have the same effect there: the body is treated as literal text, so $arch is left alone:

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


EOF

But while that's how I'd do it, there are alternatives. Here are a few:

You can stick with one echo per line, but group all of them together in a subshell, so you only have to append to the file once:

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

If you add -e to the echo (and you're using a shell that supports that non-POSIX extension), you can embed newlines directly into the string using \n:

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

But as it says above, that's not POSIX-specified behavior; your shell might just echo a literal -e followed by a string with a bunch of literal \ns instead. The POSIX way of doing that is to use printf instead of echo; it automatically treats its argument like echo -e does, but doesn't automatically append a newline at the end, so you have to stick an extra \n there, too:

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

With either of those solutions, what the command gets as an argument string contains the two-character sequence \n, and it's up to the command program itself (the code inside printf or echo) to translate that into a newline. In many modern shells, you have the option of using ANSI quotes $'...', which will translate sequences like \n into literal newlines before the command program ever sees the string. That means such strings work with any command whatsoever, including plain old -e-less echo:

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

But, while more portable than echo -e, ANSI quotes are still a non-POSIX extension.

And again, while those are all options, I prefer the straight tee <<EOF solution above.

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

As the instructions are not that clear above I am using the instructions from that blog post. With examples so it is easier to see what you need to do.

$ sudo cat /root/example.txt | gzip > /root/example.gz
-bash: /root/example.gz: Permission denied

Notice that it’s the second command (the gzip command) in the pipeline that causes the error. That’s where our technique of using bash with the -c option comes in.

$ sudo bash -c 'cat /root/example.txt | gzip > /root/example.gz'
$ sudo ls /root/example.gz
/root/example.gz

We can see form the ls command’s output that the compressed file creation succeeded.

The second method is similar to the first in that we’re passing a command string to bash, but we’re doing it in a pipeline via sudo.

$ sudo rm /root/example.gz
$ echo "cat /root/example.txt | gzip > /root/example.gz" | sudo bash
$ sudo ls /root/example.gz
/root/example.gz

STEP 1 create a function in a bash file (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' will not interpret $arch variable.

STE2 source bash file

$ source write_pacman.sh

STEP 3 execute function

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

append files (sudo cat):

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

append echo to file (sudo echo):

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

(EXTRA) disregard the ouput:

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