Making shell scripts executable via editor hooks
Posted by Steve on Thu 6 Dec 2007 at 11:42
If you spend a lot of time creating new shell scripts, be they plain shell or scripting languages such as perl or python, then it can be very useful to make new scripts be executable by default. Here we'll show two simple recipes for GNU Emacs and vim to do just that.
The basic aim is to run a hook whenever a file is written, which will "chmod 755 $file" if the file you're saving looks like a shell script. We do this by examining the first line of the file looking for a "shebang" line which looks something like this:
#!/bin/sh
If that line is present, or one similar to it, we can then change the permissions of the file.
GNU Emacs
Reece Hart wrote a lisp file, shebang.el which allows files to be made executable when they are saved if they contain a shebang line pointing to an executable which exists.
To use this download shebang.el, and place it in a directory upon your emacs load-path, and require it in your startup file
If the term load-path doesn't mean anything to you then create the directory ~/.emacs.d/ and download the file into it. Once you've done that add this to the end of your .emacs file (creating it if necesary):
;; Add ~/.emacs.d/ to the load-path if the directory exists (if (file-exists-p (expand-file-name "~/.emacs.d")) (add-to-list 'load-path (expand-file-name "~/.emacs.d/"))) ;; Load the shebang module (require 'shebang)
vim
To get this effect in Vim add the following lines to the end of your ~/.vimrc file - creating it if necessary: " " automatically give executable permissions if file begins with #! and contains " '/bin/' in the path " au BufWritePost * if getline(1) =~ "^#!" | if getline(1) =~ "/bin/" | silent !chmod a+x <afile> | endif | endifThis code will automatically change the file to executable if the first line contains both "#!" and "/bin/".
After adding either of these recipies to your editor startup file you should be able to run "emacs test.sh", or "vim test.sh". Once you add #!/bin/sh to the start of the file and save it, the file will be immediately executable and runnable.
[ Parent | Reply to this comment ]
[ Send Message | View Steve's Scratchpad | View Weblogs ]
There was, thanks for spotting it. Fixed now.
[ Parent | Reply to this comment ]
again, many thanks Steve!
=)
[ Parent | Reply to this comment ]
set autoreadin .vimrc or else I was getting an error about the mode of the file changing... It's also giving two "/bin/bash: endif: command not found" errors. Taking out the two endif statements seems to take care of the error but I'm not really sure this is the right thing to do as I don't really know .vimrc that well...
[ Parent | Reply to this comment ]
" automatically give executable permissions if file begins with #! and contains
" '/bin/' in the path
function ModeChange()
if getline(1) =~ "^#!"
if getline(1) =~ "/bin/"
silent !chmod a+x
endif
endif
endfunction
au BufWritePost * call ModeChange()
not sure why the originally posted code didn't work... Thanks so much for the idea of doing this within .vimrc, and for giving the impetus to do some vimrc research ;)
[ Parent | Reply to this comment ]
[ Send Message | View Steve's Scratchpad | View Weblogs ]
Thanks for the updates there.
I can confirm the original code works for me upon Debian Etch with Vim version 7.0-122+1etch3, but with the updates there I'll not experiment further.
[ Parent | Reply to this comment ]
I had to add "<afile>" after "!chmod a+x".
Otherwise, your function works great, thanks :)
[ Parent | Reply to this comment ]
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
[ Parent | Reply to this comment ]
[ Send Message | View Steve's Scratchpad | View Weblogs ]
That's a new one on me, thanks for sharing!
I'd guess we could use something like this to make it work for either case:
;;
;; Make shell scripts executable by default.
;;
(if (fboundp 'executable-make-buffer-file-executable-if-script-p)
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
(require 'shebang))
[ Parent | Reply to this comment ]
[ Parent | Reply to this comment ]
[ Send Message | View Steve's Scratchpad | View Weblogs ]
Only if you convince me to edit it and save changes to it - then later actually execute it!
[ Parent | Reply to this comment ]
[ Parent | Reply to this comment ]
In the vim version, why did you opt for two nested checks, instead of a single regexp:
... if getline(1) =~ "^#!.*/bin/" ...
[ Parent | Reply to this comment ]
Indeed, what about :if getline(1) =~ "^#!\/bin\/"
But I can't find out how to take into account a possible space or extra character between "!" and the first "/". Your pattern "^#!.*/bin/" doesn't make it for my Vim/Etch version. Geoff
[ Parent | Reply to this comment ]
That's weird. Open a new file and try to execute the following command directly in the Vim window:
:if getline(1) =~ "^#!.*/bin/" | echo "yes" | else | echo "no" | endifIt should show "yes" or "no" in the bottom line. It works for me (I'm trying it on an old Vim 6.3 compiled for Windows).
[ Parent | Reply to this comment ]