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)
Best Answer
As @geekosaur explained, the shell does the redirection before running the command. When you type this:
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 executesudo
. This is failing at the first step.If you're allowed (sudoer configs often preclude running shells), you can do something like this:
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 needsudo
in the first place; after all, needlessly running processes as root is precisely whatsudo
was created to avoid. And runningecho
as root is just silly.I added
> /dev/null
on the end becausetee
sends its output to both the named file and its own standard output, and I don't need to see it on my terminal. (Thetee
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 parameterarch
, 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 forcat
orecho
orprintf
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: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:If you add
-e
to theecho
(and you're using a shell that supports that non-POSIX extension), you can embed newlines directly into the string using\n
: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\n
s instead. The POSIX way of doing that is to useprintf
instead ofecho
; it automatically treats its argument likeecho -e
does, but doesn't automatically append a newline at the end, so you have to stick an extra\n
there, too: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 insideprintf
orecho
) 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
-lessecho
: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.