How to change file permission from within vi - vim

I sometimes open a read-only file in vi, forgetting to do chmod +w before opening it. Is there way to change the file from within vi?
Something like !r chmod +w [filename]?
Is there a shortcut to refer to the currently open file without spelling it's long name?

Just use
:!chmod +w %
in command mode. % will be replaced by the current file name.

If you have the rights to write to the file, then you can just use exclamation mark to force it:
:w!
If you don't have the rights and need to change user, but still want to write to the file, sometimes you may go for something like
:w !sudo tee %

I know this is an old post, but with Vim Version8 a function has been included with which you can change file permissions.
According to the version8.txt file:
setfperm() set the permissions of a file
This function can then be called via the "call" command in Vim.
This is done as follows:
:call setfperm("file name","permissions")
The structure of the "permissions" string takes the same form as described in the Vim documentation:
getfperm({fname}) getfperm()
The result is a String, which is the read, write, and execute
permissions of the given file {fname}.
If {fname} does not exist or its directory cannot be read, an
empty string is returned.
The result is of the form "rwxrwxrwx", where each group of
"rwx" flags represent, in turn, the permissions of the owner
of the file, the group the file belongs to, and other users.
If a user does not have a given permission the flag for this
is replaced with the string "-". Example:
:echo getfperm("/etc/passwd")
This will hopefully (from a security point of view) display
the string "rw-r--r--" or even "rw-------".
A minimal example:
:call setfperm("foo.txt","rwxrwxrwx")
This adds read, write and execute permissions to the "foo.txt" file in the current directory.

Have you tried
!chmod +w %
The % represents the current filename.
You could also map a key to this like Ctrl-W.
:map <C-w> :!chmod +w %<CR>
Note that you type Ctrl-V Ctrl-M to get the <CR>

After editing your file with vim, press "esc" and then ":".
Then type the following command:
w !sudo tee %
Then press "Enter".
Then type
:q!
to successfully exit from the editor.

:!chmod <perms> <file>
and if vi still doesn't want to write it,
:se cpo-=W

As David pointed out, setfperm() is the way to do this within vim.
Here are the mappings I use to add write or execute permissions to the current file:
function! ChmodPlus(expr, pat)
let file = expand('%')
let oldperms = getfperm(file)
let newperms = substitute(oldperms, a:expr, a:pat, '')
if (oldperms != newperms)
call setfperm(file, newperms)
endif
echom(printf('Permissions: %s', newperms))
endfunction
function! ChmodPlusX()
call ChmodPlus('^\(..\).', '\1x')
endfunction
function! ChmodPlusW()
call ChmodPlus('^\(.\).', '\1w')
endfunction
" Make current file writeable
noremap <silent> <Leader>W :call ChmodPlusW()<CR>
" Make current file executable
noremap <silent> <Leader>X :call ChmodPlusX()<CR>

You can also do this with the netrw module that comes with vim if your lazy, example :Texplore followed by scrolling to the file in normal mode and entering gp. Octal or symbolic at least work.
It's a little more intuitive with multiple files but per buffer-window without, use your colon command history to repeat commands.

Related

Vim: execute current file?

If I have a file with a shebang line (e.g. #!/bin/bash) open in Vim and the file has execute permissions (i.e. chmod +x) I know I can type this to execute it without leaving the editor:
:! %:p
: for command mode
! to run a shell command
% to refer to the file in the current buffer
:p to use the full path of the current file
Is there a shorter shortcut for this frequent task?
e.g. there is a ZZ shortcut for :wq, etc.
:!%:p
,without the spaces, is shorter.
If you want an even shorter shortcut, you can create a custom mapping:
nnoremap <F9> :!%:p
or the more "mnemonic":
nnoremap <leader>r :!%:p
If you haven't set permissions you can run:
:! sh %
None of the previous answers work if your filename/directory path has spaces in it. Simple fix.
:!"%:p"
After you've executed that once, a short :!! will repeat it.
When starting vi, specify file path explicitly, like this "vi ./blablabla"
vi ./yourscript.pl
Then start with !%
The other variant is to invoke the vi command like this
!./%
You can add a key mapping to your .vimrc
map <F5> :!%

How can I execute the current line as Vim EX commands?

Say I'm editing my _vimrc file and I've just added a couple of lines, for instance a new key mapping. I don't want to reload the whole file (:so %) since that will reset a lot of temporary stuff I'm experimenting with. I just want to run the two lines that I'm currently working on.
I'm having no luck trying to copy/paste the lines into the command buffer, since I can't use the put command in there. Is there any way I could run the current line (or current selection) as EX commands?
Summary:
After Anton Kovalenko's answer and Peter Rincker's comment I ended up with these key maps, which either executes the current line, or the current selected lines if in visual mode:
" Execute current line or current selection as Vim EX commands.
nnoremap <F2> :exe getline(".")<CR>
vnoremap <F2> :<C-w>exe join(getline("'<","'>"),'<Bar>')<CR>
To execute the current line as an ex command, you may also use:
yy:#"
This will yank the current line to the "-register and execute it. I don't think it is too much typing.
Executing the line under cursor as an Ex command:
:execute getline(".")
Convenient enough for 2 lines. (I'd figure out something for doing it with regions, but I'm not a vim user). And for currently selected region, the following seems to do the job:
:execute getreg("*")
As commented by Peter Rincker, this mapping can be used for executing the currently selected lines:
:vnoremap <f2> :<c-u>exe join(getline("'<","'>"),'<bar>')<cr>
For that purpose, I have defined the following commands and mappings:
":[range]Execute Execute text lines as ex commands.
" Handles |line-continuation|.
" The same can be achieved via "zyy#z (or yy#" through the unnamed register);
" but there, the ex command must be preceded by a colon (i.e. :ex)
command! -bar -range Execute silent <line1>,<line2>yank z | let #z = substitute(#z, '\n\s*\\', '', 'g') | #z
" [count]<Leader>e Execute current [count] line(s) as ex commands, then
" {Visual}<Leader>e jump to the following line (to allow speedy sequential
" execution of multiple lines).
nnoremap <silent> <Leader>e :Execute<Bar>execute 'normal! ' . v:count1 . 'j'<CR>
xnoremap <silent> <Leader>e :Execute<Bar>execute 'normal! ' . v:count1 . 'j'<CR>
Just after posting this, I found a work-around. I can copy text into the clipboard using "*y, then put that text into the command buffer by using the middle mouse button. This works for me, but is hardly a convenient solution for people without clipboard support, mouse support or just an aversion to removing their hands from the Vim position.
The accepted answer doesn't handle continuation sections. Also, surprisingly, the bar isn't needed, newlines are fine. This will work, first yanking the text into register x:
vno <c-x> "xy:exe substitute(#x,"\n\\",'','g')<cr>
As someone has already mentioned, the only exception are commands that "eat up" newlines. Eg, executing the above mapping on:
:sign define piet text=>> texthl=Search
:exe ":sign place 2 line=23 name=piet file=" . expand("%:p")
will cause vim to to think that the user is trying to define textl as "Search\n:exe ":sign place... etc.
You could also try
:<C-R><C-L><CR>
Per the vim docs, the combination will plop the current line into the command line. From there, hitting enter should do the trick. I realize that this does not handle multiline cases, however it doesn't require a .vimrc and therefore works out of the box.
If you're doing a lot of experimenting (trying things out that you might want to add to your vimrc, I assume?) it might help to do so in a scratch file like experimental.vim so you aren't just relying on your history to know what you're trying out. Now that you have these great mappings, it will be easy to rerun things from experimental or vimrc without sourcing the whole file.
Also (sorry, I can't comment on answers yet, it seems), I tried this mapping of Peter's:
vnoremap <Leader>es :<c-u>exec join(getline("'<","'>"),'<BAR>')<CR>
This works in most cases, but it fails specifically on function definitions.
function! TestMe()
echo "Yay!"
endfunction
This mapping joins the lines into a single string, separated by <BAR> and then execs them.
I'm not entirely sure why, but if I try to do that with a function definition in normal mode:
:exec 'function! TestMe()| echo "Yay!"|endfunction'
-> E488: Trailing characters
After some testing, I've found that it will work with newline separators instead:
:exec "function! TestMe()\n echo 'Yay!'\nendfunction"
:call TestMe()
-> Yay!
So, I've changed my mapping to this:
vnoremap <Leader>es :<c-u>exec join(getline("'<","'>"),"\n")<CR>
I suppose there is a vim or ex reason why the <BAR> method doesn't work on functions (maybe even some setting I have on?), and I'm curious to hear what it is if someone knows.
I don't want to reload the whole file (:so %) since that will reset a lot of temporary stuff I'm experimenting. I just want to run the two lines that I'm currently working on.
If you want to execute a command because you want to refine it before committing it to _.vimrc, then you should launch a Command Line Window for Ex-mode commands with q:.
At launch the Command Line Window is buffered with the contents of the command line history. It is a normal Vim window the contents of which can be edited as any text buffer with the exception of pressing on any line which executes the command on that line. It is very useful when you want to slightly change a long, complex command you wrote earlier and re-run it.
To launch a 'Command Line Window' for search strings press q/.
!! (shorthand for :.!) executes the current line as input to a command, per POSIX ex & vi. You may need to append sh if it is a system command.
Executing !! on a blank line (and omitting sh) is a shortcut for reading a shell command straight into the buffer. By it's nature :.! overwrites the current line while :.r! inserts on the line below.
ls -A | head -n +4
~
~
!sh
Results:
.sh_history
.sh_logout
.kshrc
corelist.txt
~
~
4 lines added; 1 line deleted`
This means there is no need to redirect pipelines to a file and then examine the data to see if the contents are valid. Just execute commands in vi directly and undo if you make a mistake.
Alternately, yanking a line as a named buffer allows you to execute it as an ex command, almost like a macro. You can still edit and undo the line to get it correct instead of trying to edit the : line in command mode.
The functions recommended here are all POSIX and have been supported for over 40 years, so no special vim or other enhanced features are required.
:%s/meep/pEEp/ | g/foo/ s//BAR
foo
grok
meep
~
~
Yank the ex command (line 1, :%s...) into a named buffer / macro.
I just use the label m for "macro".
"myy
or
:1y m
Now execute the named buffer / macro, in command mode, using #:
#m
Results:
:%s/pEEp/pEEp/ | g/BAR / s//BAR
BAR
grok
pEEp
~
~
4 lines changed
But remember that "multiple undo" is not POSIX. undo is only a toggle between undo and redo in a "historically accurate & compliant" ex / vi implementation.
The work-around is to save to a temporary (or valid) file name before executing a questionable edit:
:w $$.tmp
Then just :e! to "reset and reload" if needed.
You can also use :pre (preserve) to make a special temporary backup file prior to making multiple changes.
Then use :reco! % (recover this!) to restore back to that point.
Realize that :preserve creates a snapshot-like file which is deleted as soon as it is rolled back to. It does not matter if you save the edit(s) or not.
Therefore writing your own file (:w ...) and restoring with :e! may still have value because the system will not automatically delete it.
:pre is perfect when you should have ran sudo vi ... or otherwise do not have the necessary permissions - but you only realized the mistake after making several changes. i.e. vi /etc/sudoers instead of sudo vi /etc/sudoers.
^^ NEVER DO THIS! ONLY AN EXAMPLE! USE sudo visudo INSTEAD!
You can get a list of existing recovery files with vi -r and recover one directly with vi -r filename as needed, optionally with something like sudo vi -r filename.
The distinction here is that even though the ":preserved file" has it's own "special" name and path internally, it will :write to the original, intended location when ":recovered ==> /etc/sudoers
Just be sure to use :wq! and not something like ZZ when done with your "recovery" or you will still lose the edits which you tried to save.
By the way, ^R is expected to redraw or repaint the display per POSIX; it is not "undo" in any compliant vi implementation.

Move File within Vim

Is there a way to move a file within Vim? E.g. I opened a file foo/bar.txt in Vim. I know 2 ways to move this file:
First solution:
Delete the buffer with :bd bar.txt
Perform the move on the shell with mv foo/bar.txt foo/bar2.txt
Load the file in vim with :e foo/bar2.txt
Second solution:
Close Vim, so all buffer where closed.
Perform the move on the shell...
Start Vim and load the file.
But these two solutions are embarrassing. I know, there is a plugin for renaming files vim-enuch, but isn't there a Vim way for performing such basic functionality?
You could also use netrw (the default file explorer) rename functionality.
Open netrw with :E
Move your cursor to the line with the file you intend to rename, in this case bar.txt . You move to the file in question using h,j,k,l or you can search for it with / (e.g. /bar.txt)
Hit R. You will then be prompted for a new filepath. When done entering the filepath hit <CR>
Move your cursor to the new file and open it with <CR>
While this solution may not be as quick as using vim-eunch, it does allow you to see the project's structure as you rename the file. This will also allow you to move multiple files at once.
For further reading run :help netrw-move
There is no atomic way to move a file like that, but this should be close:
function! MoveFile(newspec)
let old = expand('%')
" could be improved:
if (old == a:newspec)
return 0
endif
exe 'sav' fnameescape(a:newspec)
call delete(old)
endfunction
command! -nargs=1 -complete=file -bar MoveFile call MoveFile('<args>')
Now you could say:
:MoveFile file2.txt
To rename to file2.txt
:MoveFile %.0
to move file2.txt to file2.txt.0
if you're in the bar.txt buffer:
:w bar2.txt
:!rm bar.txt
If bar2.txt already exists in the current directory, use :w!.

vim shortcut to open a file with current directory populated

I'm trying to add a key mapping so that it opens up command and populate it with :e /path/to/current/file
I can get the current directory using :pwd but I'm having a trouble to use it in the mapping
I think it will be along the lines of setting pwd to a variable and use that variable as such:
noremap <C-q> <C-o>:e *pwdvariable*<Space>
Should I create a function to perform this?
I guess you need
nnoremap <C-q> <C-\><C-n>:e <C-r>=fnameescape(expand('%:p:h'))<CR>
%:p:h will get the full path of the current file (without trailing slash). Read more in :help filename-modifiers.
Not exactly, what you have asked for, but maybe more helpful: Vim tip 64: Set working directory to the current file: In short, add the following line to .vimrc:
autocmd BufEnter * silent! lcd %:p:h
Interesting for you would be also Easy edit of files in the same directory.

Adding current filename to a Vim shortcut

I am new to Vim and trying to add a new shortcut. I was wondering how I can put current file's path to the command dynamically. So that every time I use that shortcut, my command will executed with correct filepath in it.
What you are looking for is expand("%"). It will return the file you are currently editing. If you use expand("%:p") you will get the full path of that file. So say you would want to have a shortcut to print your current file in the command bar and you wanted it mapped to F5. Then you would add the following in your .vimrc:
map <F5> :echo expand("%:p")<CR>
nnoremap <expr> <leader>cd (expand("%:p:h") !~ '^/tmp') ? ":lcd %:p:h\<CR>:echo expand(\"%:p:h\")\<CR>" : "echo \"foo\"\<CR>"
I have this line in my .vimrc to what you want. My leader is ',' so when I type ,cd it changes the local directory to that of the current file.

Resources