This question already has answers here:
How do I use sudo to redirect output to a location I don't have permission to write to? [closed]
(15 answers)
Closed 1 year ago.
This is a pretty simple question, at least it seems like it should be, about sudo permissions in Linux.
There are a lot of times when I just want to append something to /etc/hosts or a similar file but end up not being able to because both > and >> are not allowed, even with root.
Is there someway to make this work without having to su or sudo su into root?
Use tee --append or tee -a.
echo 'deb blah ... blah' | sudo tee -a /etc/apt/sources.list
Make sure to avoid quotes inside quotes.
To avoid printing data back to the console, redirect the output to /dev/null.
echo 'deb blah ... blah' | sudo tee -a /etc/apt/sources.list > /dev/null
Remember about the (-a/--append) flag!
Just tee works like > and will overwrite your file. tee -a works like >> and will write at the end of the file.
The problem is that the shell does output redirection, not sudo or echo, so this is being done as your regular user.
Try the following code snippet:
sudo sh -c "echo 'something' >> /etc/privilegedfile"
The issue is that it's your shell that handles redirection; it's trying to open the file with your permissions not those of the process you're running under sudo.
Use something like this, perhaps:
sudo sh -c "echo 'something' >> /etc/privilegedFile"
sudo sh -c "echo 127.0.0.1 localhost >> /etc/hosts"
Doing
sudo sh -c "echo >> somefile"
should work. The problem is that > and >> are handled by your shell, not by the "sudoed" command, so the permissions are your ones, not the ones of the user you are "sudoing" into.
I would note, for the curious, that you can also quote a heredoc (for large blocks):
sudo bash -c "cat <<EOIPFW >> /etc/ipfw.conf
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.company.ipfw</string>
<key>Program</key>
<string>/sbin/ipfw</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/ipfw</string>
<string>-q</string>
<string>/etc/ipfw.conf</string>
</array>
<key>RunAtLoad</key>
<true></true>
</dict>
</plist>
EOIPFW"
In bash you can use tee in combination with > /dev/null to keep stdout clean.
echo "# comment" | sudo tee -a /etc/hosts > /dev/null
Some user not know solution when using multiples lines.
sudo tee -a /path/file/to/create_with_text > /dev/null <<EOT
line 1
line 2
line 3
EOT
Using Yoo's answer, put this in your ~/.bashrc:
sudoe() {
[[ "$#" -ne 2 ]] && echo "Usage: sudoe <text> <file>" && return 1
echo "$1" | sudo tee --append "$2" > /dev/null
}
Now you can run sudoe 'deb blah # blah' /etc/apt/sources.list
Edit:
A more complete version which allows you to pipe input in or redirect from a file and includes a -a switch to turn off appending (which is on by default):
sudoe() {
if ([[ "$1" == "-a" ]] || [[ "$1" == "--no-append" ]]); then
shift &>/dev/null || local failed=1
else
local append="--append"
fi
while [[ $failed -ne 1 ]]; do
if [[ -t 0 ]]; then
text="$1"; shift &>/dev/null || break
else
text="$(cat <&0)"
fi
[[ -z "$1" ]] && break
echo "$text" | sudo tee $append "$1" >/dev/null; return $?
done
echo "Usage: $0 [-a|--no-append] [text] <file>"; return 1
}
You can also use sponge from the moreutils package and not need to redirect the output (i.e., no tee noise to hide):
echo 'Add this line' | sudo sponge -a privfile
By using sed -i with $ a , you can append text, containing both variables and special characters, after the last line.
For example, adding $NEW_HOST with $NEW_IP to /etc/hosts:
sudo sed -i "\$ a $NEW_IP\t\t$NEW_HOST.domain.local\t$NEW_HOST" /etc/hosts
sed options explained:
-i for in-place
$ for last line
a for append
echo 'Hello World' | (sudo tee -a /etc/apt/sources.list)
How about:
echo text | sudo dd status=none of=privilegedfile
I want to change /proc/sys/net/ipv4/tcp_rmem.
I did:
sudo dd status=none of=/proc/sys/net/ipv4/tcp_rmem <<<"4096 131072 1024000"
eliminates the echo with a single line document
This worked for me:
original command
echo "export CATALINA_HOME="/opt/tomcat9"" >> /etc/environment
Working command
echo "export CATALINA_HOME="/opt/tomcat9"" |sudo tee /etc/environment
Can you change the ownership of the file then change it back after using cat >> to append?
sudo chown youruser /etc/hosts
sudo cat /downloaded/hostsadditions >> /etc/hosts
sudo chown root /etc/hosts
Something like this work for you?
Related
This question already has answers here:
How do I use sudo to redirect output to a location I don't have permission to write to? [closed]
(15 answers)
sudo cat << EOF > File doesn't work, sudo su does [duplicate]
(5 answers)
Closed 1 year ago.
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)
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 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. (Without the quotes or backslash, $arch would get replaced by the value of the shell parameter arch, 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.
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.)
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
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
Here is the chunk of code for reference:-
Output:
I have checked the variable values using echo and those looks fine.
But what I want do achieve is searching logs on remote hosts using grep which does not give any output.
for dir in ${log_path}
do
for host in ${Host}
do
if [[ "${userinputserverhost}" == "${host}" ]]
then
ssh -q -T username#userinputserverhost "bash -s" <<-'EOF' 2>&1 | tee -a ${LogFile}
echo -e "Fetching details: \n"
`\$(grep -A 5 -s "\${ID}" "\${dir}"/archive/*.log)`
EOF
fi
break
done
done
First, remove all the crap around the grep.
Second, you're overquoting your vars.
Third, skip the "bash -s" if you can.
ssh -q -T username#userinputserverhost <<-'EOF' 2>&1 | tee -a ${LogFile}
echo -e "Fetching details: \n"
grep -A 5 -s "${ID}" "${dir}"/archive/*.log
EOF
Fourth, I don't see where $ID is set...so if that's being loaded on the remote system by the login or something, then that one would need the dollar sign backslashed.
Finally, be aware that here-docs are great, but sometimes here-strings are simpler if you can spare the quotes.
$: ssh 2>&1 dudeling#sandbox-server '
> date
> whoami
> ' | tee -a foo.txt
Fri Apr 30 09:23:09 EDT 2021
dudeling
$: cat foo.txt
Fri Apr 30 09:23:09 EDT 2021
dudeling
That one is more a matter of taste. Even better, if you can, write your remote-script to a local file & use that. And of course, you can always add set -vx into the script to see what gets remotely executed.
cat >tmpScript <<-'EOF'
echo -e "Fetching details: \n"
set -vx
grep -A 5 -s "${ID}" "${dir}"/archive/*.log
EOF
ssh <tmpScript 2>&1 -q -T username#userinputserverhost | tee -a ${LogFile}
Now you have an exact copy of what was issued for debugging.
Thanks Paul for spending time and coming up with suggestions/solutions.
I have managed to get it working couple of days back. Would have felt happy to say that your solution worked 100% but even satisfied that I got it sorted on my own as it helped me learn some new stuff.
FYI - grep -A 5 -s "${ID}" "${dir}"/archive/*.log - this will work but only by using shell built-in 'declare -p' to declare the variables within EOF. Also, I read somewhere and it is recommended to use EOF unqouted as it caters variable expansion to remote hosts without any trouble.
Below piece of code is working for me in bash:
ssh -q -T username#userinputserverhost <<-EOF 2>&1 | tee -a ${LogFile}
echo -e "Fetching details: \n"
$(declare -p ID)
$(declare -p dir)
grep -A 5 -s "${ID}" "${dir}"/archive/*.log
EOF
I want to make a script to install a program (ROS) and I need to write this line:
sudo sh -c 'echo "TEXT VARIABLE TEXT" > systemFile' # to write in systemaFile I need sudo sh
if echo is just fixed text, it works.
If echo is text + variable it doesn't work.
I've tried with:
read f1 < <(lsb_release -a | grep Code* | cut -f2) #codename is writted in variable $f1
echo $f1 # retruns "quantal" as I expected
sudo sh -c 'echo "TEXT $f1 TEXT" > systemFile' #f1 is empty, WHY?
Then I have to assign the variable inside the same instruction sudo sh, for example:
sudo sh -c ' read f1 < <(lsb_release -a | grep Code* | cut -f2) ; echo "TEXT $f1 TEXT" > systemFile'
sh: 1: Syntax error: redirection unexpected
Please try just like this script line
sudo sh -c 'echo TEXT '$f1' TEXT > systemFile'
sudo bash -c 'echo TEXT '$f1' TEXT > systemFile'
i have use this able script line in .sh file and its working fine.
This can work too:
sudo sh -c "echo 'TEXT $VARIABLE TEXT' > systemFile"
However, it is generally not recommended to un-necessarily run a command as sudo. You seem to want only redirection to be "sudoed". So try these options:
echo "TEXT $VARIABLE TEXT" | sudo tee systemFile >/dev/null
echo "TEXT $VARIABLE TEXT" | sudo dd of=systemFile
echo can be simple echo, or any other command you want. Note that this command is not being run under sudo.
use -E option to run sudo:
sudo -E sh -c 'echo "TEXT VARIABLE TEXT" > systemFile'
from man sudo:
-E
The -E (preserve environment) option indicates to the security policy that the user wishes to preserve their existing environment variables. The security policy may return an error if the -E option is specified and the user does not have permission to preserve the environment.
Shell variables are only expanded in "double quotes", not in 'single quotes'.
$ v=value
$ echo $v
value
$ echo "$v"
value
$ echo '$v'
$v
You're starting a new instance of sh which then runs the command echo "TEXT $f1 TEXT" > systemFile.
Since $f1 has not been assigned within the new process, it's empty.
To fix this, you can expand $f1 and pass it in the command line:
sudo sh -c 'echo "TEXT '$f1' TEXT" > systemFile'
Or export it so it's available to the child process (using -E to preserve the environment, thanks anishsane):
export f1
sudo -E sh -c 'echo "TEXT $f1 TEXT" > systemFile'
I want to add lines to /etc/my.conf as "sudo" using Shell.
Without logging as sudo, I can do:
{ echo "[mysqld]"
echo "default-character-set=utf8"
echo "character_set_server=utf8"
echo "[mysql]"
echo "default-character-set=utf8"
} >> /etc/my.conf
But when:
sudo { echo "[mysqld]"
echo "default-character-set=utf8"
echo "character_set_server=utf8"
echo "[mysql]"
echo "default-character-set=utf8"
} >> /etc/my.conf
I get an error:
sudo: {: command not found
character_set_server=utf8
[mysql]
default-character-set=utf8 .....
What am I doing wrong?
Firstly, use cat with a here-doc rather than a series of echoes. It's much cleaner. You can use the special form with a dash which strips leading tabs (tabs, not spaces!) to let you indent the here-doc to make it stand out.
Secondly, you can do the redirection as sudo by using sudo sh -c to start a root subshell, in which you then run cat doing the redirection.
Putting it together:
sudo sh -c "cat >>/etc/my.conf" <<-EOF
[mysqld]
default-character-set=utf8
character_set_server=utf8
[mysql]
default-character-set=utf8
EOF
I don't know of a more direct way to write a stream to a file as root. It's a shame if there really isn't one.
An alternative to launching a new shell is to use the tee command:
{ echo "[mysqld]"
echo "default-character-set=utf8"
echo "character_set_server=utf8"
echo "[mysql]"
echo "default-character-set=utf8"
} | sudo tee -a /etc/my.conf > /dev/null
or
sudo tee -a /etc/my.conf >/dev/null <<-EOF
[mysqld]
default-character-set=utf8
character_set_server=utf8
[mysql]
default-character-set=utf8
EOF
$ sudo echo "foo
bar
baz" > f
should work
To make everything easier you should place the commands in a script, and let sudo execute that script.
This question already has answers here:
How do I use sudo to redirect output to a location I don't have permission to write to? [closed]
(15 answers)
sudo cat << EOF > File doesn't work, sudo su does [duplicate]
(5 answers)
Closed 1 year ago.
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)
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 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. (Without the quotes or backslash, $arch would get replaced by the value of the shell parameter arch, 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.
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.)
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
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