想到一种启动 Emacs Script 的方法

用 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

这种方法有三个问题:

  1. 需要写出 Emacs 的路径,如 /usr/bin/emacs
  2. 不能指定更多 Emacs 的选项,如 -Q --script,否则就无法在 Linux 下使用,因为 Linux 下 shebang 不会拆分(Mac 下没问题)
  3. 无法独立地处理命令行参数,如 hello --version 会被 Emacs 优先处理

法三

#!/usr/bin/env emacs -Q --script
;;; hello --- "Hello World!"  -*- lexical-binding: t; -*-

(message "Hello World!")

;;; hello ends here

这种方法有两个问题:

  1. 无法在 Linux 下使用,因为 Linux 下 shebang 不会拆分(Mac 下没问题)
  2. 无法独立地处理命令行参数,如 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 个赞

然而前人已经有办法了。四种都有提到,还有能用 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 个赞

https://github.com/spacebat/lexbind-mode/blob/master/lexbind-mode.el

看这个包,闭包的确可以通过设置 lexical-binding 变量实现。 所以可以不用字符操作这种办法。

关键是在程序执行之前 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。

当初发明 lisp 的时候也用 # 做注释就好了 :smile:

我翻了下 flychek 的代码:
(defun flycheck-process-send-buffer (process)
  (save-restriction
    (widen)
    (if flycheck-chunked-process-input
        (flycheck--process-send-buffer-contents-chunked process)
      (process-send-region process (point-min) (point-max))))
  (process-send-eof process))

这个函数应该可以改造,把 (point-min) 改成从 n 行开始。


试了一下,elisp 并没有用到这个函数 :sweat:

(论坛 bug,预览窗口 <del></del> 没有闭合)

1 个赞
(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.