-->

Why sudo cat gives a Permission denied but sudo vi

2020-05-11 07:43发布

问题:

I am trying to automate the addition of a repository source in my arch's pacman.conf file but using the echo command in my shell script. However, it fails like this:-

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

If I make changes to /etc/pacman.conf manually using vim, by doing

sudo vim /etc/pacman.conf

and quiting vim with :wq, everything works fine and my pacman.conf has been manually updated without "Permission denied" complaints.

Why is this so? And how do I get sudo echo to work? (btw, I tried using sudo cat too but that failed with Permission denied as well)

回答1:

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.)


回答2:

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 makes that file descriptor its standard output, and only then executes sudo.

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 both the named file and 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.

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. :)

First, you can just group all of the echo's together in a subshell, so you only have to do the redirection once:

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

Or use printf instead of echo, so you can embed newlines directly into the string using \n. And need to do so at the end of the string, since printf, unlike echo, doesn't automatically append a newline:

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

In bash, you can get the same result with echo -e:

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

But most shells will just output the -e when you try that, so it's not recommended.

With both printf and echo -e, what the command gets as an argument string contains a literal backslash followed by a literal N wherever you type \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, which means such strings work with any command whatsoever:

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.

My preferred way of doing this would be to use a here-document and avoid the need for echo or printf entirely:

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

EOF


回答3:

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



回答4:

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


回答5:

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