709

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?

jww
  • 97,681
  • 90
  • 411
  • 885
David
  • 17,673
  • 10
  • 68
  • 97

15 Answers15

1081

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.

cubuspl42
  • 7,833
  • 4
  • 41
  • 65
Yoo
  • 17,526
  • 6
  • 41
  • 47
  • 6
    I absolutely prefer this one. It's just the simplest (and it tought me about tee, which comes in handy in other scenarios as well). – Joachim Sauer Apr 08 '09 at 19:00
  • 6
    I agree. Seems neater than start a new sh too, especially with potentially to do things with environment etc. – Sam Brightman Oct 16 '10 at 05:59
  • 43
    One important thing to note: NEVER forget the -a! Just imagine what a `echo 'tmpfs /tmp tmpfs defaults 0 0' | sudo tee /etc/fstab` would do – mic_e Feb 17 '13 at 08:00
  • 22
    Under OS X, this should be `tee -a` instead of `tee --append`. – knite Feb 17 '15 at 08:25
  • 30
    For those who don't understand what @mic_e said: **without the `-a`** (`--append`) flag **the command would overwrite the whole file** with the given string instead of appending it to the end. – totymedli Oct 22 '15 at 00:15
  • 1
    For the record, you want `-a` on Ubuntu Server 16.04, `--append` is ignored as @totymedli illustrates. – James Dec 05 '17 at 12:30
357

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"
Community
  • 1
  • 1
Matt P
  • 5,447
  • 4
  • 23
  • 20
  • What are "sudo permission boundaries"? It's just the shell which parses the redirection operator with higher precedence than a command for obvious reasons – Vinko Vrsalovic Sep 17 '08 at 16:22
  • 1
    Depending on your `sh`, echo can interpret escape sequences like `\t` inside single quotes. You could use `printf %s 'something'` instead. – Lri Oct 02 '12 at 00:43
  • 1
    Using **tee** is more popular and more compatible to newer distros. – Eduardo B. Aug 29 '16 at 22:29
  • 1
    This is the only one which works as-is as an SSH command which can be executed on a remote node. – Duncan Lock Jan 28 '17 at 04:41
  • 1
    I agree with other posts here. This uses a shell and only shell so another program like tee is not required here. Yes I know tee is installed already, but maybe not depending on what micro distro you're using like bare bones alpine. I like that you have a shell option too: `sudo /bin/bash -c "echo 'fs.inotify.max_user_watches = 524288' > /etc/sysctl.d/60-golang-inotify.conf"` for example. – akahunahi Jun 29 '18 at 21:48
  • great, this way you can also repeat the last command as sudo: `sudo sh -c "!!"` – rubo77 Dec 19 '18 at 12:38
37

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"
Incident
  • 489
  • 4
  • 7
  • @GordonSun this is because `sudo` (for security reasons) doesn't propagate the environment to the subprocess. You may use `sudo -E` to bypass this restriction. – arielf Oct 30 '18 at 18:17
  • 1
    @GordonSun yes it will, I don't understand why you keep repeating this statement! If you do `sudo sh -c "echo 'something' >> $FILENAME"`, with double quotes, this *will* work - the variable substitution is done by the outer shell, not the sudoed shell. – Nadav Har'El Jan 03 '19 at 18:41
  • does not work with multiline echo – mjs Jul 17 '22 at 19:18
22
sudo sh -c "echo 127.0.0.1 localhost >> /etc/hosts"
Manuel Selva
  • 18,554
  • 22
  • 89
  • 134
Vinko Vrsalovic
  • 330,807
  • 53
  • 334
  • 373
16

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.

agnul
  • 12,608
  • 14
  • 63
  • 85
11

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"
msanford
  • 11,803
  • 11
  • 66
  • 93
  • 2
    This works great, except the embedded quotes might need to be escaped with a \ – N Jones Aug 16 '15 at 03:48
  • Quite right @NJones! I shall edit my answer. (Note, however, that not doing this strips the internal `"` but does not cause the command to fail.) – msanford Aug 18 '15 at 14:52
10

In bash you can use tee in combination with > /dev/null to keep stdout clean.

 echo "# comment" |  sudo tee -a /etc/hosts > /dev/null
Vytenis Bivainis
  • 2,308
  • 21
  • 28
10

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
abkrim
  • 3,512
  • 7
  • 43
  • 69
5

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
}
Community
  • 1
  • 1
hololeap
  • 1,156
  • 13
  • 20
3

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
Michael Goldshteyn
  • 71,784
  • 24
  • 131
  • 181
  • ```-a``` appends to file. – Akhil May 25 '22 at 06:31
  • @Akhil, if you have a question, please leave a meaningful comment. If you have a statement or concern, please leave a meaningful comment. If you see an error or omission, please leave a meaningful comment. Do you see a pattern developing in my statements? – Michael Goldshteyn Jun 16 '22 at 15:03
2

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
Noam Manos
  • 15,216
  • 3
  • 86
  • 85
  • I think you've been downvoted because only GNU sed lets you put text immediately after the command. On OSX I had to actually use line breaks AND the -e option: `sudo sed -ie '$a\ ` `> export GOOGLE_APPLICATION_CREDENTIALS="adapter-gcp-compute-test-WWWXXXYYYZZZ.json"` `> ' /Users/gcpclient/.bashrc` – Jeff Jan 19 '21 at 23:57
0

echo 'Hello World' | (sudo tee -a /etc/apt/sources.list)

-1

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
Fthi.a.Abadi
  • 324
  • 2
  • 8
-1

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

Marcelo Pacheco
  • 152
  • 1
  • 5
  • 3
    Please take a moment to read through the [editing help](//stackoverflow.com/editing-help) in the [help]. Formatting on Stack Overflow is different than other sites. – Dharman Aug 23 '19 at 22:57
-6

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?

bfontaine
  • 18,169
  • 13
  • 73
  • 107
pixistix
  • 1
  • 1
  • 4
    Chown is a destructive command to use. If a config manager used that to update /etc/hosts, suddenly /etc/hosts belongs to config user and not root. which potentially leads to other processes not having access to /etc/hosts. The whole point of sudo is to avoid having something logged into root and via sudoers more restrictions can be piled on as well. – David Jan 22 '17 at 00:39