1706

Many of you have probably seen the command that allows you to write on a file that needs root permission, even when you forgot to open vim with sudo:

:w !sudo tee %

The thing is that I don't get what is exactly happening here.

I have already figured this: w is for this

                                                        *:w_c* *:write_c*
:[range]w[rite] [++opt] !{cmd}
                        Execute {cmd} with [range] lines as standard input
                        (note the space in front of the '!').  {cmd} is
                        executed like with ":!{cmd}", any '!' is replaced with
                        the previous command |:!|.

so it passes all the lines as standard input.

The !sudo tee part calls tee with administrator privileges.

For all to make sense, the % should output the filename (as a parameter for tee), but I can't find references on the help for this behavior.

tl;dr Could someone help me dissect this command?

Yamaneko
  • 3,433
  • 2
  • 38
  • 57
Doppelganger
  • 20,114
  • 8
  • 31
  • 29
  • 9
    @Nathan: Would `:w !sudo cat > %` not work as well, and not pollute standard output? – Bjarke Freund-Hansen Jun 30 '11 at 08:13
  • 65
    @bjarkef - no, that doesn't work. In that case, `sudo` is applied to `cat`, but not to `>`, so it is not allowed. You could try running the whole command in a sudo subshell, like `:w !sudo sh -c "cat % > yams.txt"`, but that won't work either, because in the subshell, `%` is nil; you'll blank out the contents of your file. – Nathan Long Aug 16 '11 at 12:58
  • @NathanLong: You could probably use `:w !sudo sh -c 'cat $1 > yams.txt' - %`, but I do not know how that will handle special chars or whitespace – knittl Mar 03 '12 at 20:21
  • 3
    I just wish to add that after typing that command, a warning message may appear. If so, press L. Then, you will be asked to press enter. Do and you will finally have your file saved. – pablofiumara Nov 04 '13 at 00:28
  • 1
    @Parsa: that has [already been suggested](http://stackoverflow.com/questions/2600783/how-does-the-vim-write-with-sudo-trick-work#comment7691076_2600783), and [rejected](http://stackoverflow.com/questions/2600783/how-does-the-vim-write-with-sudo-trick-work#comment8471211_2600783): `sudo` applies to `cat` not to `>` – Han Seoul-Oh May 22 '15 at 20:53
  • 14
    @NathanLong @knittl: `:w !sudo sh -c "cat >%"` actually works just as well as `sudo tee %` because Vim substitutes the filename for `%` before it ever gets to the subshell. However, neither of them work if the filename has spaces in it; you have to do `:w !sudo sh -c "cat >'%'"` or `:w !sudo tee "%"` to fix that. – Han Seoul-Oh May 22 '15 at 21:45
  • 5
    Save using :W and reload the file: command W :execute ':silent w !sudo tee % > /dev/null' | :edit! – Diego Roberto Dos Santos Jan 19 '18 at 18:37
  • 1
    Note that this trick currently [does **not** work](https://github.com/neovim/neovim/issues/1716) work with [`neovim`](https://neovim.io/). – patryk.beza Mar 26 '19 at 09:30
  • Quick explanation: (1) `:w !{cmd}` - Executes `{cmd}` with all lines in buffer as standard input. (2) `%` - Expands to current filename. Put quotes around it (i.e., `"%"`) to keep the filename as a single argument even if it contains whitespace. (3) `tee {file}` - Copies standard input to standard output and `{file}`. See [this thread](https://twitter.com/susam/status/1390991411424595973) for more details. – Susam Pal May 10 '21 at 09:44
  • I had this to work with nvim 0.5.1 using `command W :execute ':silent w !sudo tee > /dev/null "%"' | :edit!` – la Fleur Nov 12 '21 at 11:44
  • Fun fact: vim source code actually comes with an implementation of `tee` though I noticed that it incorrectly uses `void` for `main()` return type. – Pryftan Nov 09 '22 at 11:26

9 Answers9

1925

In :w !sudo tee %...

% means "the current file"

As eugene y pointed out, % does indeed mean "the current file name", which is passed to tee so that it knows which file to overwrite.

(In substitution commands, it's slightly different; as :help :% shows, it's equal to 1,$ (the entire file) (thanks to @Orafu for pointing out that this does not evaluate to the filename). For example, :%s/foo/bar means "in the current file, replace occurrences of foo with bar." If you highlight some text before typing :s, you'll see that the highlighted lines take the place of % as your substitution range.)

:w isn't updating your file

One confusing part of this trick is that you might think :w is modifying your file, but it isn't. If you opened and modified file1.txt, then ran :w file2.txt, it would be a "save as"; file1.txt wouldn't be modified, but the current buffer contents would be sent to file2.txt.

Instead of file2.txt, you can substitute a shell command to receive the buffer contents. For instance, :w !cat will just display the contents.

If Vim wasn't run with sudo access, its :w can't modify a protected file, but if it passes the buffer contents to the shell, a command in the shell can be run with sudo. In this case, we use tee.

Understanding tee

As for tee, picture the tee command as a T-shaped pipe in a normal bash piping situation: it directs output to specified file(s) and also sends it to standard output, which can be captured by the next piped command.

For example, in ps -ax | tee processes.txt | grep 'foo', the list of processes will be written to a text file and passed along to grep.

     +-----------+    tee     +------------+
     |           |  --------  |            |
     | ps -ax    |  --------  | grep 'foo' |
     |           |     ||     |            |
     +-----------+     ||     +------------+
                       ||   
               +---------------+
               |               |
               | processes.txt |
               |               |
               +---------------+

(Diagram created with Asciiflow.)

See the tee man page for more info.

Tee as a hack

In the situation your question describes, using tee is a hack because we're ignoring half of what it does. sudo tee writes to our file and also sends the buffer contents to standard output, but we ignore standard output. We don't need to pass anything to another piped command in this case; we're just using tee as an alternate way of writing a file and so that we can call it with sudo.

Making this trick easy

You can add this to your .vimrc to make this trick easy-to-use: just type :w!!.

" Allow saving of files as sudo when I forgot to start vim using sudo.
cmap w!! w !sudo tee > /dev/null %

The > /dev/null part explicitly throws away the standard output, since, as I said, we don't need to pass anything to another piped command.

Nathan Long
  • 122,748
  • 97
  • 336
  • 451
  • 184
    Especially like your notation "w!!" which is so easy to remember after using "sudo !!" on the command line. – Aidan Kane Sep 05 '11 at 22:23
  • Is there a way of defining a command for this like e.g. `:sudow`? – white_gecko Dec 10 '12 at 14:49
  • 1
    @white_gecko he already did. `cmap` maps a command to an action. – BryanH Jan 11 '13 at 20:43
  • 14
    So this uses `tee` for its ability to write stdin to a file. I'm surprised there isn't a program whose job it is to do that (I found a program I've never heard of called `sponge` which does this). I guess the typical "write a stream to a file" is performed by a shell built-in. Does Vim's `!{cmd}` not fork a shell (forking `cmd` instead)? Perhaps something that is more obvious would be to use some working variant of `sh -c ">"` rather than `tee`. – Steven Lu May 14 '13 at 15:27
  • 2
    @Steven Lu: `sponge` is part of the `moreutils` package on pretty much every distribution except Debian based distros. `moreutils` has some pretty nice tools that are on par with more common tools like `xargs` and `tee`. – Swiss May 23 '13 at 18:16
  • 10
    How to expand this alias to also tell vim to automatically load the changed file contents to the current buffer? It asks me for it, how to automate it? – Zlatko Dec 01 '13 at 12:11
  • 1
    Your cmap didn't work for me. But this did: ca w!! w !sudo tee % >/dev/null – Doppelganger Jun 09 '14 at 21:31
  • 10
    @user247077: In that case, `cat` runs as root, and the output is redirected by the shell, which does not run as root. It's the same as `echo hi > /read/only/file`. – WhyNotHugo May 23 '15 at 02:15
  • @WarFox that score would really have been more appropriate for http://stackoverflow.com/a/1732454/4376 – Nathan Long Sep 21 '15 at 14:52
  • This great explanation misses a reference to [SudoEdit.vim](https://github.com/chrisbra/SudoEdit.vim) plugin. – mMontu Feb 23 '16 at 12:42
  • "`:%s/foo/bar` means 'in the current file, ...'" - Huh. I always read that visually, as in, "0 from the top to 0 from the bottom" – Izkata Jan 18 '17 at 01:11
  • This is mapping a [command-line mode](http://vimdoc.sourceforge.net/htmldoc/map.html#mapmode-c) key sequence which will introduce a scanning pause when typing `:w`. You may consider adding a user defined command to your `.vimrc` `command! Wsudo w !sudo tee > /dev/null % `. This can then be invoked as `:Wsudo` – Jack Rowlingson Feb 23 '17 at 17:09
  • 1
    Now i understand why when using this "hack" it outputs while file before going back to vim. Thanks for the great explanation. – Gonzague May 17 '18 at 10:01
  • 1
    That `tee` command explanation was something out of this world. Pure Brilliant! – Mayank Sharma Feb 27 '19 at 21:10
  • 1
    The `w!!` shortcut is very nice, but it introduces a noticeable lag while typing `:w` as denoted by @JackRowlingson. Instead of adding a `:Wsudo` command, it seems replacing `cmap` with `ca` fix the problem, but I don't understand any of this. I copy-pasted it blindly from [this related Github issue](https://github.com/carlhuda/janus/issues/484). – Delgan May 05 '19 at 21:55
  • 2
    `%` in front of a command is a range, it is an abbreviation of `1,$` (see `:help cmdline-ranges` or `:help :%`). This `%` isn't replaced with the filename. If you try it manually, as in `:filenames/foo/bar`, you will see that it doesn't work. `%` later on the command line is a special character, and it is actually replaced with the current filename, see `:help cmdline-special` or `:help :_%`. – Orafu Sep 19 '19 at 00:55
  • Thanks @Orafu - I updated the answer accordingly. :) – Nathan Long Sep 20 '19 at 17:26
  • @NathanLong, I think your update is a little confused. It now reads: _In `:w !sudo tee %` % means "from the start of the file to the end"_. In this instance, `%` evaluates to the current filename, because `tee` needs to know that. `%` only evaluates to `1,$` when specifying a range, at the start of a command. – c24w Oct 02 '19 at 15:41
  • @c24w oh, right, nice catch! Updated again. Thanks. :) – Nathan Long Oct 07 '19 at 15:10
  • Has anyone else seen the error message `sudo: no tty present and no askpass program specified`? I'm running it from `neovim 0.4.2`. – jc00ke Nov 19 '19 at 17:48
  • Update: I found https://stackoverflow.com/a/24107529/710404 and added `/usr/bin/tee` and now it warns but works. – jc00ke Nov 19 '19 at 17:54
  • `...when I forgot to start vim using sudo.` note that running vim itself as sudo/root is probably ***not a good idea***. Vim is as dynamic as any shell, if not even more so, and can do just about anything (run arbitrary executables, run arbitrary interpreted scripts and plugins, read and write arbitrary files, etc.). Therefore, it is much better to limit the scope of sudo/root access and only use it as needed, for example, for the duration a single `tee` or `mv` command, as opposed to starting an entire shell or dynamic text editor (such as vim) session with root privileges from the get-go. – tmillr Jun 25 '23 at 08:49
  • @tmillr Good advice! `sudo -e ` is a nice command to know: it runs the editor as the current user, on a temporary copy of the file which is written back to the original when you exit the editor. – Thomas Jun 25 '23 at 12:00
115

In the executed command line, % stands for the current file name. This is documented in :help cmdline-special:

In Ex commands, at places where a file name can be used, the following
characters have a special meaning.
        %       Is replaced with the current file name.

As you've already found out, :w !cmd pipes the contents of the current buffer to another command. What tee does is copy standard input to one or more files, and also to standard output. Therefore, :w !sudo tee % > /dev/null effectively writes the contents of the current buffer to the current file while being root. Another command that can be used for this is dd:

:w !sudo dd of=% > /dev/null

As a shortcut, you can add this mapping to your .vimrc:

" Force saving files that require root permission 
cnoremap w!! w !sudo tee > /dev/null %

With the above you can type :w!!<Enter> to save the file as root.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • 1
    Interesting, `:help _%` brings up what you entered, but `:help %` brings up the brace-matching key. I wouldn't have thought to try the underscore prefix, is that a pattern of some kind in the vim documentation? Are there any other 'special' things to try when looking for help? – David Pope Mar 03 '12 at 23:18
  • 2
    @David: The `help` command jumps to a tag. You can see available tags with `:h help-tags`. You can also use command line completion to see matching tags: `:h cmdline` (or `:h cmdline` if you set `wildmode` accordingly) – Eugene Yarmash Mar 04 '12 at 12:54
  • 1
    I had to use `cmap w!! w !sudo tee % > /dev/null` in my .vimrc file to make this work. Is the `%` misplaced in the answer above? (No vim expert here.) – dmmfll May 01 '15 at 16:13
  • @DMfll Yes, it is. The command in the answer would result in `sudo tee > /dev/null /path/to/current/file` which doesn't really make sense. (Going to edit that) – jazzpi Jun 23 '15 at 08:43
  • 1
    @jazzpi: You're wrong. Shells don't actually care where on the command line you do the file redirection. – Eugene Yarmash Jun 23 '15 at 09:41
  • @eugeney Oh, I'm sorry, I didn't know that works. It only works when putting the `tee` pipe inbetween, to. Still strange that it didn't work for @DMfll. – jazzpi Jun 23 '15 at 09:56
29

The accepted answer covers it all, so I'll just give another example of a shortcut that I use, for the record.

Add it to your etc/vim/vimrc (or ~/.vimrc):

  • cnoremap w!! execute 'silent! write !sudo tee % >/dev/null' <bar> edit!

Where:

  • cnoremap: tells vim that the following shortcut is to be associated in the command line.
  • w!!: the shortcut itself.
  • execute '...': a command that execute the following string.
  • silent!: run it silently
  • write !sudo tee % >/dev/null: the OP question, added a redirection of messages to NULL to make a clean command
  • <bar> edit!: this trick is the cherry of the cake: it calls also the edit command to reload the buffer and then avoid messages such as the buffer has changed. <bar> is how to write the pipe symbol to separate two commands here.

Hope it helps. See also for other problems:

DrBeco
  • 11,237
  • 9
  • 59
  • 76
20

This also works well:

:w !sudo sh -c "cat > %"

This is inspired by the comment of @Nathan Long.

NOTICE:

" must be used instead of ' because we want % to be expanded before passing to shell.

feihu
  • 1,935
  • 16
  • 24
  • 15
    While this may work, it also gives sudo access to multiple programs (sh and cat). The other examples could be more secure by replacing `tee` with `/usr/bin/tee` to prevent PATH-modification attacks. – idbrii Sep 07 '14 at 15:54
19

:w - Write a file.

!sudo - Call shell sudo command.

tee - The output of write (vim :w) command redirected using tee. The % is nothing but current file name i.e. /etc/apache2/conf.d/mediawiki.conf. In other words tee command is run as root and it takes standard input and write it to a file represented by %. However, this will prompt to reload file again (hit L to load changes in vim itself):

tutorial link

kev
  • 155,172
  • 47
  • 273
  • 272
10

I'd like to suggest another approach to the "Oups I forgot to write sudo while opening my file" issue:

Instead of receiving a permission denied, and having to type :w!!, I find it more elegant to have a conditional vim command that does sudo vim if file owner is root.

This is as easy to implement (there might even be more elegant implementations, I'm clearly not a bash-guru):

function vim(){
  OWNER=$(stat -c '%U' $1)
  if [[ "$OWNER" == "root" ]]; then
    sudo /usr/bin/vim $*;
  else
    /usr/bin/vim $*;
  fi
}

And it works really well.

This is a more bash-centered approach than a vim-one so not everybody might like it.

Of course:

  • there are use cases where it will fail (when file owner is not root but requires sudo, but the function can be edited anyway)
  • it doesn't make sense when using vim for reading-only a file (as far as I'm concerned, I use tail or cat for small files)

But I find this brings a much better dev user experience, which is something that IMHO tends to be forgotten when using bash. :-)

Augustin Riedinger
  • 20,909
  • 29
  • 133
  • 206
  • 6
    Just be aware that this is a lot less forgiving. Personally, most of my mistakes are stupid mistakes. So I prefer to get a heads up when I'm doing something that might be both stupid and consequential. This is of course a matter of preference, but privilege escalation aught to be a conscientious act. Also: If you experience this so often as to make ":w!!" enough of a bother to silently auto sudo (but only if owner=root); you may want to examine your current workflow. – Payne Nov 26 '18 at 22:22
  • 1
    Interesting comment, though one shall be conscious because when the file is being opened as `root` the password is queried. – Augustin Riedinger Nov 29 '18 at 21:29
  • Ah! There's the difference. It depends if the sudo user has "NOPASSWD" set or not. – Payne Nov 30 '18 at 11:58
  • 3
    Then having NOPASSWD *is* what's less forgiving ... :) – Augustin Riedinger Dec 14 '18 at 17:39
  • Btw if awareness is an issue, one could definitely add an extra `read -p "Owner is root. Do you want to open the file with sudo?" -n 1 -r` – Augustin Riedinger Aug 21 '22 at 13:27
7

FOR NEOVIM

Due to problems with interactive calls (https://github.com/neovim/neovim/issues/1716), I am using this for neovim, based on Dr Beco's answer:

cnoremap w!! execute 'silent! write !SUDO_ASKPASS=`which ssh-askpass` sudo tee % >/dev/null' <bar> edit!

This will open a dialog using ssh-askpass asking for the sudo password.

dbranco
  • 129
  • 1
  • 9
  • I appreciate the neovim consideration. For whatever reason, this did not work for me. I did find a plugin that solves the issue. It's a relatively non-trivial accomplishment with a nice end result. https://github.com/lambdalisue/suda.vim - E – Edmund's Echo Jan 11 '22 at 13:52
3

A summary (and very minor improvement) on the most common answers that I found for this as at 2020.

tl;dr

Call with :w!! or :W!!. After it expands, press enter.

  • If you are too slow in typing the !! after the w/W, it will not expand and might report: E492: Not an editor command: W!!

NOTE Use which tee output to replace /usr/bin/tee if it differs in your case.

Put these in your ~/.vimrc file:

    " Silent version of the super user edit, sudo tee trick.
    cnoremap W!! execute 'silent! write !sudo /usr/bin/tee "%" >/dev/null' <bar> edit!
    " Talkative version of the super user edit, sudo tee trick.
    cmap w!! w !sudo /usr/bin/tee >/dev/null "%"

More Info:

First, the linked answer below was about the only other that seemed to mitigate most known problems and differ in any significant way from the others. Worth reading: https://stackoverflow.com/a/12870763/2927555

My answer above was pulled together from multiple suggestions on the conventional sudo tee theme and thus very slightly improves on the most common answers I found. My version above:

  • Works with whitespace in file names

  • Mitigates path modification attacks by specifying the full path to tee.

  • Gives you two mappings, W!! for silent execution, and w!! for not silent, i.e Talkative :-)

  • The difference in using the non-silent version is that you get to choose between [O]k and [L]oad. If you don't care, use the silent version.

    • [O]k - Preserves your undo history, but will cause you to get warned when you try to quit. You have to use :q! to quit.
    • [L]oad - Erases your undo history and resets the "modified flag" allowing you to exit without being warned to save changes.

Information for the above was drawn from a bunch of other answers and comments on this, but notably:

Dr Beco's answer: https://stackoverflow.com/a/48237738/2927555

idbrii's comment to this: https://stackoverflow.com/a/25010815/2927555

Han Seoul-Oh's comment to this: How does the vim "write with sudo" trick work?

Bruno Bronosky comment to this: https://serverfault.com/a/22576/195239

This answer also explains why the apparently most simple approach is not such a good idea: https://serverfault.com/a/26334/195239

Chris
  • 417
  • 3
  • 11
1

The only problem with cnoremap w!! is that it replaces w with ! (and hangs until you type the next char) whenever you type w! at the : command prompt. Like when you want to actually force-save with w!. Also, even if it's not the first thing after :.

Therefore I would suggest mapping it to something like <Fn>w. I personally have mapleader = F1, so I'm using <Leader>w.

usretc
  • 723
  • 4
  • 9
  • don't see a problem here. both `:w` and `:w!` can be typed in without waiting and seems to work as expected even if not visible on screen. – Amith Apr 16 '22 at 09:43
  • Lack of visual feedback, *especially when expected*, is a great annoyance. I consider it a bug. But to each his own. – usretc Jun 16 '22 at 16:26