Emacs Lisp: Write Emacs Commands Using {Perl, Python, Ruby, …}

By Xah Lee. Date: . Last updated: .

This page shows you how you can write a text processing script in your favorite language, then make it available in emacs as a command.


If you are new to emacs lisp, it may take several months for you to become productive. However, you are probably familiar with Perl, Python, Ruby, PHP. You can use your existing knowledge to write many text processing scripts and make them available in emacs as commands, so that you can just select text, press a key, then the selected text will be transformed according to your script.


Your shell script will need to:

Let's assume your script is the unix program “wc”. (the “wc” command counts the number of words, lines, chars in the text.) For example, try this in shell: cat file_name | wc.

You need a elisp command that:

  1. Grab the current region.
  2. Pass the text to a external program.
  3. Take the output from Stdout.
  4. Replace the current region by that text.

Lucky for us, the elisp function shell-command-on-region does this exactly.

Here's the elisp wrapper:

(defun do-something-region (startPos endPos)
  "Do some text processing on region.
This command calls the external script “wc”."
(interactive "r")
  (let (scriptName)
    (setq scriptName "/usr/bin/wc") ; full path to your script
    (shell-command-on-region startPos endPos scriptName nil t nil t)

Select the lisp code and call eval-region. 〔➤see Emacs: How to Evaluate Emacs Lisp Code

To use your command, first make a text selection, then call the command by name (that is, M-x name). You can give it a key. 〔➤see Emacs: How to Define Keys

With the above, you can write many little text processing scripts in your favorite language, and have them all available in emacs as commands.

emacs ♥

Passing STDIN & Arg

In this example, the emacs wrapper command calls a python script.

The emacs command passes 2 things to the script:

  1. Takes region and pass to script as STDIN
  2. Takes current buffer file path and pass to the script as first arg

Here's the emacs command:

(defun my-python-ref-linkify ()
  "Transform current line (a file path) into a link.
For example, this line:



<span class=\"ref\"><a href=\"../python_doc_2.7.6/library/stdtypes.html#mapping-types-dict\">5. Built-in Types — Python v2.7.6 documentation #mapping-types-dict</a>

The URL is relative to current file. The link text is the linked file's title, plus any fragment URL part.

Requires a python script. See code."
  (let ((scriptName
          "/usr/bin/python3 /home/xah/git/xahscripts/emacs_pydoc_ref_linkify.py3 %s" 
        (bds (bounds-of-thing-at-point 'filename)))
      (shell-command-on-region (car bds) (cdr bds) scriptName nil "REPLACE" nil t))))

here's the python script it calls:

# -*- coding: utf-8 -*-

# input: stdin and 1st arg from command line
# output: stdout

# input is a file path, like this:
# /home/xah/web/xahlee_info/python_doc_2.7.6/library/stdtypes.html#mapping-types-dict
# first arg is a file path like this
# /home/xah/web/xahlee_info/python/python3_traverse_dir.html
# output should be like this

# <a href="../python_doc_2.7.6/library/stdtypes.html#mapping-types-dict">5. Built-in Types — Python v2.7.6 documentation #mapping-types-dict</a>

# the path is a relative path, relative to script arg. The link text is from the file's title, plus the fragment url, if any

# this is called by a emacs command python-ref-linkify

# Xah Lee
# http://xahlee.info/
# 2013-11-29

import sys
import os.path
import re

default_input = "/home/xah/web/xahlee_info/python_doc_3.3.3/library/os.html#os.walk"
default_bufferpath = "/home/xah/web/xahlee_info/python/python3_traverse_dir.html"

input_text = sys.stdin.read()
input_text = default_input if input_text == "" else input_text

buffer_path = sys.argv[1] if len(sys.argv) == 2 else default_bufferpath


input_text = re.sub(r"^file://", "" , input_text.strip())

if re.search(r"#", input_text):
    doc_path, frac = input_text.split("#")
    doc_path, frac = input_text, ""

doc_title = ""

with open(doc_path, "r") as f1:
    for xline in f1:
        xmatch = re.search(r"<title>([^<]+?)</title>", xline, re.UNICODE)
        # <title>16.1. os — Miscellaneous operating system interfaces — Python v3.3.3 documentation</title>
        if xmatch:
            doc_title = xmatch.group(1)

# if doc_title == "":
#     sys.exit("no title found in {}".format(doc_path))

relative_path = os.path.relpath( doc_path, start= os.path.dirname(buffer_path))

print("""<a href="{}">{}</a>""".format(relative_path + "#" + frac, doc_title + " #" + frac) )

thanks to Jordan Greenberg for python tips.

Simple Example with JavaScript Node.Js

Emacs Lisp: Calling External Command to Decode URL Percent Encoding

Like it? Buy Xah Emacs Tutorial. Thanks.