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)
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:
- quote the redirection ( so it is passed on to
sudo)
- 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 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
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
sudo bash -c 'echo "[archlinuxfr]" >> /etc/pacman.conf'
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