Making shell scripts executable via editor hooks

Posted by Steve on Thu 6 Dec 2007 at 11:42

Tags: , , ,

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 | endif

This 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.

 

 


Posted by nijel (125.207.xx.xx) on Thu 6 Dec 2007 at 12:29
I guess there is missing <afile> in vim version after chmod.

[ Parent | Reply to this comment ]

Posted by Steve (80.68.xx.xx) on Thu 6 Dec 2007 at 12:31
[ View Steve's Scratchpad | View Weblogs ]

There was, thanks for spotting it. Fixed now.

Steve

[ Parent | Reply to this comment ]

Posted by Thorsten (80.69.xx.xx) on Thu 6 Dec 2007 at 12:42
wohoo, great idea! no annoying chmod after creating a shellscript.

again, many thanks Steve!
=)

[ Parent | Reply to this comment ]

Posted by Anonymous (165.200.xx.xx) on Thu 6 Dec 2007 at 16:49
Using vim, I needed to put a
set autoread
in .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 ]

Posted by Anonymous (165.200.xx.xx) on Thu 6 Dec 2007 at 23:03
I am same poster as parent... ended up doing a little research and chaged the vim code. Put this in my .vimrc and not getting errors anymore:
" 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 ]

Posted by Steve (80.68.xx.xx) on Fri 7 Dec 2007 at 12:07
[ 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.

Steve

[ Parent | Reply to this comment ]

Posted by Anonymous (80.67.xx.xx) on Fri 7 Dec 2007 at 12:32
Hi,
I had to add "<afile>" after "!chmod a+x".
Otherwise, your function works great, thanks :)

[ Parent | Reply to this comment ]

Posted by Anonymous (82.67.xx.xx) on Thu 6 Dec 2007 at 19:13
In Emacs 22 you don't even need an external package, you can just use the following in your ~/.emacs file:

(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)

[ Parent | Reply to this comment ]

Posted by Steve (82.32.xx.xx) on Thu 6 Dec 2007 at 19:38
[ 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))

Steve

[ Parent | Reply to this comment ]

Posted by Anonymous (64.2.xx.xx) on Sat 8 Dec 2007 at 01:24
Umm yeah.. go ahead... then any file I can squeeze into /tmp/bin on your box becomes an executable ... wii! I've got root.

[ Parent | Reply to this comment ]

Posted by Steve (82.32.xx.xx) on Sat 8 Dec 2007 at 12:26
[ View Steve's Scratchpad | View Weblogs ]

Only if you convince me to edit it and save changes to it - then later actually execute it!

Steve

[ Parent | Reply to this comment ]

Posted by Anonymous (88.134.xx.xx) on Tue 11 Dec 2007 at 22:17
I think it's more reasonable to use chmod +x <afile> as the command-line for the vim solution, since it honors the umask.

[ Parent | Reply to this comment ]

Posted by mcortese (213.70.xx.xx) on Wed 12 Dec 2007 at 17:11
[ View Weblogs ]

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 ]

Posted by Anonymous (90.16.xx.xx) on Thu 17 Jan 2008 at 13:35

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 ]

Posted by Anonymous (213.70.xx.xx) on Mon 28 Jan 2008 at 09:03

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" | endif
It 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 ]

Sign In

Username:

Password:

[Register|Advanced]

 

Flattr

 

Current Poll

What do you use for configuration management?








( 487 votes ~ 5 comments )