Writing scripts in Emacs-lisp

| categories: emacs | tags:

I have written lots of script commands, mostly in Python, occasionally in bash. Today I learned you can also write them in emacs-lisp (http://www.emacswiki.org/emacs/EmacsScripts ). There is an interesting wrinkle on the first line which specifies how to run the command, which is explained in the emacswiki page.

Here is an example script that just prints some information about Emacs and the command line args you pass to it. We use some Local variables at the end to make the script open in emacs-lisp mode for editing. $0 in shell language is the name of the script being run, so the header here simply loads the script into emacs, and then runs the main function.

:;exec emacs -batch -l "$0" -f main "$@"

(defun main ()
  (print (version))
  (print (format "I did it. you passed in %s" command-line-args-left)))

;; Local Variables:
;; mode: emacs-lisp
;; End:

We need to tangle this code block to get the script. org-babel-tangle

Since we do not have a regular shebang, we manually change the mode to make it executable, and then call the script with some arguments.

chmod +x test.el
./test.el arg1 arg2
"GNU Emacs 22.1.1 (mac-apple-darwin)
 of 2014-06-05 on osx105.apple.com"

"I did it. you passed in (arg1 arg2)"

Hahah! I guess the emacs on my path is an old one! Ironically, the Emacs I am writing in is much more modern (but not on the path).

(version)
GNU Emacs 24.3.1 (x86_64-apple-darwin, NS apple-appkit-1038.36)
 of 2013-03-13 on bob.porkrind.org

And it is evidence I wrote this on a Mac. First Mac post ever.

1 Addition based on Trevor's comment

Also according to http://www.emacswiki.org/emacs/EmacsScripts , there is the following option:

#!emacs --script

as the shebang line. That did not work on my mac, but a small variation did with the absolute path to emacs. You still define the function in the script file, but you finally have to call the function.

(defun main ()
  (print (version))
  (print (format "I did it. you passed in %s" command-line-args-left)))

(main)
;; Local Variables:
;; mode: emacs-lisp
;; End:
./test2.el arg1 arg2 arg3
"GNU Emacs 22.1.1 (mac-apple-darwin)
 of 2014-06-05 on osx105.apple.com"

"Called with (/usr/bin/emacs --no-splash -scriptload ./test2.el arg1 arg2 arg3)"

"I did it. you passed in (arg1 arg2 arg3)"

Now, how do you do this python style so one file is a script and library at once? In python that is done with:

def main ():
    ... put some module code here

if __name__ == '__main__':
    main()

We can check the command line-args to see if there is a clue there.

(defun main ()
  (print (version))
  (print (format "Called with %s" command-line-args))
  (print (format "I did it. you passed in %s" command-line-args-left)))

(main)
;; Local Variables:
;; mode: emacs-lisp
;; End:
./test3.el arg1
"GNU Emacs 22.1.1 (mac-apple-darwin)
 of 2014-06-05 on osx105.apple.com"

"Called with (/usr/bin/emacs --no-splash -scriptload ./test3.el arg1)"

"I did it. you passed in (arg1)"

And apparently, this means when called with –script, we see "-scriptload" as a command line arg. Strange, but workable. We just look for that, and if we see it run as a script, and if not do nothing.

(defun main ()
  (print (version))
  (print (format "Called with %s" command-line-args))
  (print (format "I did it. you passed in %s" command-line-args-left)))

(when (member "-scriptload" command-line-args)
  (main))

Here we run as a script.

./test4.el arg1
"GNU Emacs 22.1.1 (mac-apple-darwin)
 of 2014-06-05 on osx105.apple.com"

"Called with (/usr/bin/emacs --no-splash -scriptload ./test4.el arg1)"

"I did it. you passed in (arg1)"

Now, we try loading the file, and calling our function.

(load-file "test4.el")
(main)
I did it. you passed in nil

Sweet. An emacs script and library in one. Now, I just need to get my modern emacs on the path!

Copyright (C) 2014 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.6

Discuss on Twitter