137

When you attempt to modify a file without having write permissions on it, you get an error:

> touch /tmp/foo && sudo chown root /tmp/foo
> echo test > /tmp/foo
zsh: permission denied: /tmp/foo

Sudoing doesn't help, because it runs the command as root, but the shell handles redirecting stdout and opens the file as you anyway:

> sudo echo test > /tmp/foo
zsh: permission denied: /tmp/foo

Is there an easy way to redirect stdout to a file you don't have permission to write to, besides opening a shell as root and manipulating the file that way?

> sudo su
# echo test > /tmp/foo
xenoterracide
  • 57,918
  • 74
  • 184
  • 250
Michael Mrozek
  • 91,316
  • 38
  • 238
  • 232
  • 2
    Answer for a similar question from StackOverflow http://stackoverflow.com/questions/82256/how-do-i-use-sudo-to-redirect-output-to-a-location-i-dont-have-permission-to-wri/82278#82278 – Cristian Ciupitu Sep 03 '10 at 11:22

7 Answers7

148

Yes, using tee. So echo test > /tmp/foo becomes

echo test | sudo tee /tmp/foo

You can also append (>>)

echo test | sudo tee -a /tmp/foo
Warren Young
  • 71,107
  • 16
  • 178
  • 168
Gert
  • 9,886
  • 3
  • 36
  • 37
  • 40
    Tee will also output to stdout; sometimes you don't want the contents filling the screen. To fix this, do `echo test | sudo tee /tmp/foo > /dev/null` – Shawn J. Goff Dec 14 '10 at 15:20
  • 1
    How will you do it with heredoc? –  Nov 19 '16 at 02:54
  • A related question is https://unix.stackexchange.com/questions/712933/does-unix-have-a-command-to-read-from-stdin-and-write-to-a-file-like-tee-withou – PatS Aug 16 '22 at 14:12
31

To replace the content of the file with the output of echo (like the > shell redirection operator).

echo test | sudo dd of=/tmp/foo

To write into the file (at the beginning, though you can use seek to output at different offsets) without truncating (like the 1<> Bourne shell operator):

echo test | sudo dd of=/tmp/foo conv=notrunc

To append to the file (like >>), with GNU dd:

echo test | sudo dd of=/tmp/foo oflag=append conv=notrunc

See also GNU dd's conv=excl to avoid clobbering an existing file (like with set -o noclobber in POSIX shells) and conv=nocreat for the opposite (only update an existing file).

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
Chris Jepeway
  • 311
  • 2
  • 2
  • 1
    clever! this alleviates the need to do `echo test | sudo tee /tmp/foo >/dev/null` to discard the output. – Adam Katz Jan 14 '15 at 23:52
  • 2
    I may have to take that back; [`dd` is unreliable for that](https://unix.stackexchange.com/questions/17295/when-is-dd-suitable-for-copying-data-or-when-are-read-and-write-partial) unless you're using obscure GNU-only options `iflag=fullblock oflag=fullblock`, which remove the elegance of this answer. I'll stick with `tee`. – Adam Katz Jan 16 '15 at 06:32
  • 5
    dd is reliable with the non-obscure bs=1 – umeboshi Jan 25 '15 at 03:50
  • 2
    @umeboshi But reliable only if you're experienced enough to know exactly what you're doing. For`dd` can be fairly dangerous (if not to say: devastating) if only a *slight* mistake was made. So for new users, I'd rather recommend the `tee` method to be on the safe shore. – syntaxerror Jan 29 '16 at 17:38
  • 1
    @AdamKatz, in the case of `dd of=file` alone (without `count`/`skip`...), it is reliable. `iflag=fullblock` is not needed because here `dd` writes on output what it has read on input. It doesn't matter if it was not full blocks. – Stéphane Chazelas Sep 12 '17 at 11:30
  • 1
    dd also (by default) outputs status information to stderr. To fix that add `status=none` to the `dd` command or redirect the status output which goes to stderr using `2> /dev/null`. – PatS Aug 16 '22 at 14:15
15

tee is probably the best choice, but depending on your situation something like this may be enough:

sudo sh -c 'echo test > /tmp/foo'
phunehehe
  • 20,030
  • 27
  • 99
  • 151
5

While I agree, that | sudo tee is the canonical way, sometimes sed (here assuming GNU sed) may work:

cat sudotest 
line 1

sudo sed -i '1iitest' sudotest && cat sudotest 
itest
line 1

sudo sed -i '$aatest' sudotest && cat sudotest 
itest
line 1
atest

-i modifies the file in place. 1i means insert before line 1. $a means append after last line.

Or copy to xclipboard:

somecommand | xclip
sudo gedit sudotest
move cursor to desired place, click middle mouse button to insert, save
user unknown
  • 10,267
  • 3
  • 35
  • 58
  • 1
    Note that [`sed -i` does not actually modify the file in place](https://stackoverflow.com/a/12696224/4756299) - it creates a temporary file and renames it on exiting. So you won't be able to do something like `tail -f ...` on the original file and see the output using `sed -i ...` while the pipeline is running – Andrew Henle Mar 14 '18 at 10:24
  • @AndrewHenle: Yes, since the size may be increased or shrinked, and since that's probably the case for most sed invocations, and you can't even - afaik - write to the same location on SSDs it's only a pseudo 'in place' operation. As a non native english speaker, may I ask for a brief expression, which isn't so likely misinterpreted? Just `-i creates a new file of same name` or is there something more compact? I guess I like `in place`, because it explains the `i`. The gnu-sed manpage calls it *in place* too and the long flag is `--in-place`. – user unknown Mar 14 '18 at 10:30
2

I have been kicking around in the back of my mind ideas for a similar problem, and came up with the following solutions:

  • sudo uncat where uncat is a program that reads standard input and writes it to the file named on the command line, but I haven't written uncat yet.

  • sudocat the variant of sudoedit that I haven't written yet that does a cleaner sudo cat or sudo uncat.

  • or this little trick of using sudoedit with an EDITOR that is a shell script

    #!/bin/sh
    # uncat
    cat > "$1"
    

    which can be invoked as either |sudo ./uncat file or | EDITOR=./uncat sudoedit but that has interesting side-effects.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
hildred
  • 5,759
  • 3
  • 30
  • 43
  • `cat` takes a list of files to con**cat**inate, therefore uncat should take a list of files to **un** con**cat**inate to. It would have to use magic to decide how much to put in each file. Alternative name include `dog`, `to-file`, `redirect`. – ctrl-alt-delor Apr 15 '15 at 14:51
  • I can't think of any reason why I would want `uncat` when I have `tee`. – Wildcard Sep 29 '15 at 09:26
  • 2
    Well, `tee` has the trivial drawback that it writes its stdin to its stdout — which is trivially mitigated by redirecting the stdout to `/dev/null`.  Other alternatives include `dd of=/tmp/foo` (mentioned in another answer), which writes status information to stderr, and `cp /dev/stdin /tmp/foo`. – Scott - Слава Україні Feb 17 '16 at 14:44
2

Use sponge from the moreutils package. It has the advantage that it does not write to stdout.

echo test | sudo sponge /tmp/foo

Use the -a option to append to a file instead of overwriting it.

  • 1
    I'm not sure what advantage not writing to stdout presents, but you could just redirect the output to `/dev/null` if it were an issue – Michael Mrozek Dec 18 '16 at 04:41
  • 3
    Of course I can redirect to /dev/null, but the command is easier to read and type without the redirection. The advantage of not writing to stdout is that my terminal is not filled with rubbish. – Hontvári Levente Dec 18 '16 at 22:48
  • 1
    I would not install a package to spare a redirection, but I regularly use sponge, so it is already there. – Hontvári Levente Dec 18 '16 at 22:50
1

The error comes from the order in which the shell does things.

The redirection is handled before the shell even executes sudo, and is therefore done with the permissions of the user that you are currently working as. Since you don't have write permissions to create/truncate the target of the redirection, you get a permission denied error from the shell.

The solution is to guarantee that the output file is created under the identity given to you by sudo, e.g. with tee:

$ generate_output | sudo tee target_file
Kusalananda
  • 320,670
  • 36
  • 633
  • 936