用 Emacs Lisp 写命令行程序时,遇到的第一个问题就是如何启动。昨晚想到了一个方法(法一),这种方法克服了一些其它方法(法二、法三……)的问题。
法一
#!/bin/bash
# -*- mode: emacs-lisp; -*-
tail -n +6 "$0" > "/tmp/${0//\//!}"
exec emacs -Q --batch -l "/tmp/${0//\//!}" -- "$@"
;;; hello --- "Hello World!" -*- lexical-binding: t; -*-
(message "Hello World!")
;;; hello ends here
法二
#!/usr/bin/emacs --script
;;; hello --- "Hello World!" -*- lexical-binding: t; -*-
(message "Hello World!")
;;; hello ends here
这种方法有三个问题:
需要写出 Emacs 的路径,如 /usr/bin/emacs
不能指定更多 Emacs 的选项,如 -Q --script
,否则就无法在 Linux 下使用,因为 Linux 下 shebang 不会拆分(Mac 下没问题)
无法独立地处理命令行参数,如 hello --version
会被 Emacs 优先处理
法三
#!/usr/bin/env emacs -Q --script
;;; hello --- "Hello World!" -*- lexical-binding: t; -*-
(message "Hello World!")
;;; hello ends here
这种方法有两个问题:
无法在 Linux 下使用,因为 Linux 下 shebang 不会拆分(Mac 下没问题)
无法独立地处理命令行参数,如 hello --version
会被 Emacs 优先处理
法四
#!/bin/sh
":"; exec emacs -Q --script "$0" -- "$@" # -*- mode: emacs-lisp; lexical-binding: t; -*-
(message "Hello World!")
(message "lexical-binding: %s" lexical-binding)
;;; hello ends here
这种写法来自于 Emacs script pitfalls | Sebastian Wiesner ,它的问题是无法启用 lexical-binding
,简单试一下就能知道。背后的原因估计是: lexical-binding
的设定需要在程序执行之前,不在执行完 ":"
之后。
4 个赞
#+TITLE: emacs-script中的那些坑
#+URL: http://www.lunaryorn.com/posts/emacs-script-pitfalls.html
#+AUTHOR: lujun9972
#+CATEGORY: emacs-common
#+DATE: [2016-11-28 周一 10:25]
#+LANGUAGE: zh-CN
#+OPTIONS: H:6 num:nil toc:t \n:nil ::t |:t ^:nil -:nil f:t *:t <:nil
Emacs可不仅仅是一个编辑器而已, 它还是一个完整的Emacs Lisp解释器及其运行环境. 我们不仅仅可以用EmacsLisp来扩展和定制Emacs,还能编写完整的程序应用呢.
Nic Ferrier的 [[https://github.com/nicferrier/elnode][elnode]] 服务器就是一个最好的例子. 我们完全可以用EmacsLisp写一些规模较小的shell脚本与工具.
不过你真的开始写就会发现, 用EmacsLisp编程要比想想中复杂一些. 几十年来Emacs都是作为交互式的应用程序来用的,这给Emacs和EmacsLisp留下了深深的烙印,也使得用它来编写独立的非交互式脚本变得格外的困难.
* Making Emacs Lisp scripts executable
如今Emacs提供了一个很方便的 =--script= 选项来加载并执行指定的文件^[[http://www.lunaryorn.com/posts/emacs-script-pitfalls.html#fn:1][1]], 然而如何分配一个合适的shebang呢?
新手常常会这么做:
#+BEGIN_SRC sh
#!/usr/bin/emacs --script
This file has been truncated. show original
然而前人已经有办法了。四种都有提到,还有能用 lexical 的办法。
我看过那篇博客(原文和翻译),它的方法就是我说的法四,它的问题在于无法启用 Lexical Binding,而且我觉得作者自己并不知道。
Emacs Script 是先加载整个文件再执行吗?
我觉得应该有别的办法来传递 lexical 设置。
不知道
或许吧,但是我没想出来。如果需要测试,可以用下面的代码。
;; Lexical Binding
(message "lexical-binding: %s" lexical-binding)
(let ((fn (let ((s "Lexical Binding is working!"))
(lambda ()
(message s)))))
(funcall fn))
不熟悉 Flycheck,不知道有没有办法让它忽略掉前面几行的 Bash 代码,现在会抱怨第一行的 Shebang
Invalid read syntax: "#"
有些讨厌。
Spacemacs 默认 elisp 模式不开这的,因为的确有点烦
想到一个非常疯狂的方法:
;; 引导的代码
(unless lexical-binding
(let ((code (with-temp-buffer
(insert-file-contents load-file-name)
(buffer-string))))
(insert ";; -*- lexical-binding: t; -*-\n" code)
(eval-current-buffer)
;; 提前结束正在执行中的程序
(with-current-buffer " *load*"
(goto-char (point-max)))))
;; 真正的代码
(funcall (let ((x 100)) (lambda () (message "=> %s" x))))
就这个例子而言,这种方法是可行的,但不表示很靠谱。
$ emacs -Q --batch -l foo.el
=> 100
$ emacs -Q --batch --eval '(funcall (let ((x 100)) (lambda () (message "=> %s" x))))'
Symbol’s value as variable is void: x
1 个赞
关键是在程序执行之前 lexical-binding
就得要是 t
,所以程序自己设置就已经来不及了
(setq lexical-binding t)
(funcall (let ((x 1))
(lambda ()
(message "Lexical binding is working" x))))
.
~$ emacs -Q --batch -l baz.el
Symbol’s value as variable is void: x
V.S.
;; -*- lexical-binding: t; -*-
(funcall (let ((x 1))
(lambda ()
(message "Lexical binding is working" x))))
.
~$ emacs -Q --batch -l baz.el
Lexical binding is working
在交互模式下的确可以用 M-: (setq lexical-binding t)
对当前 Buffer 开启 Lexical Binding。
LdBeth
2017 年10 月 11 日 00:46
12
(defmacro eval-when-compile (&rest body)
"Like `progn', but evaluates the body at compile time if you're compiling.
Thus, the result of the body appears to the compiler as a quoted
constant. In interpreted code, this is entirely equivalent to
`progn', except that the value of the expression may be (but is
not necessarily) computed at load time if eager macro expansion
is enabled."
(declare (debug (&rest def-form)) (indent 0))
(list 'quote (eval (cons 'progn body) lexical-binding)))
忽然发现这样给 eval
传参数就可以启用词法作用域了。
(eval FORM &optional LEXICAL)
Evaluate FORM and return its value.
If LEXICAL is t, evaluate using lexical scoping.
LEXICAL can also be an actual lexical environment, in the form of an
alist mapping symbols to their value.