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.
Best Answer
As of Emacs 24.3, an analog of the old
multi:
syntax has been layered on top of the moderntramp-default-proxies-alist
approach, meaning that you can once again perform multi-hops without any prior configuration. For details, see:C-hig
(tramp)Ad-hoc multi-hops
RETWith the new syntax, each 'hop' is separated by
|
. The example in the manual is:C-xC-f
/ssh:bird@bastion|ssh:you@remotehost:/path
RETWhich connects firstly as
bird@bastion
, and from there toyou@remotehost:/path
/su: or /sudo: on remote hosts
You can also use this syntax to sudo/su to root (or of course any other user) on a remote host:
C-xC-f
/ssh:you@remotehost|sudo:remotehost:/path/to/file
RETImportant: be sure to specify the hostname explicitly:
sudo:remotehost:
rather thansudo::
(see below).As this still uses the proxy mechanism underneath,
tramp-default-proxies-alist
should now include the value("remotehost" "root" "/ssh:you@remotehost:")
Meaning that the proxy
/ssh:you@remotehost:
is going to be used whenever you request a file asroot@remotehost
.root
is the default user for these methods, but you can of course also change to a non-root user with:C-xC-f
/ssh:you@remotehost|sudo:them@remotehost:/path/to/file
RETAlways specify the remote hostname explicitly
You are probably used to using
sudo::
orsu::
and omitting the hostname. If you are staying on the localhost then this is still fine, but if you are hopping to a remote server then you must specify the hostname for every hop -- even if it is the same as for the previous hop. Always usesudo:hostname:
orsu:hostname:
with remote hosts.The trap here is that
sudo::
does actually appear to work -- however when you do that the HOST for the dynamic proxy entry will be the hostname you originated from rather than the host you connected to. This will not only look confusing (as the wrong host will be displayed in the file paths), but it will also mean that any subsequent attempt to usesudo::
on your localhost will instead be proxied to the remote server! (and the proxy would also presumably be clobbered if you did the same thing on a second server, causing further issues).In short, don't use
::
when you multi-hop!Emacs 27+
Starting from Emacs 27.1 (or Tramp 2.4.2, if using the GNU ELPA package) the
::
case works intuitively, such that/ssh:you@remotehost|sudo::
will re-useremotehost
rather than your own local host, and so you won't end up with a bad proxy entry.In addition, the likes of
/ssh:you@remotehost|sudo:localhost:
are detected and flagged as user errors.If you are liable to use a mixture of Emacs versions including versions earlier than 27 (or you are advising someone else who may be using an older version), then it would be safest to continue to treat
::
as unsafe when multi-hopping, to avoid potential mishap. (I.e. specifying the correct remote host explicitly will remain the safest approach if the Tramp version is unknown.)