Как запускать программы на Common Lisp как консольные скрипты
2012.06.07
Допустим, у вас есть классная маленькая программка на Common Lisp под
названием filter
. Она занимает всего один файл исходного кода под
названием filter.lisp
примерно в 200 строк длиной. В этом файле вот
такой заголовок:
(defpackage :localhost.me.filter
(:use :common-lisp)
(:export :run))
(in-package :localhost.me.filter)
И дальше собственно сама программа. Допустим также, что вы пользуетесь своей программой часто. Загвоздка в том, что для того, чтобы её запустить, вам необходимо сделать слишком много действий:
$ cd $PROGRAMDIR
$ sbcl
CL-USER> (load "filter.lisp")
CL-USER> (:localhost.me.filter:run)
Рассмотрим два решения, одно простое, но не всегда поможет, другое посложнее и поможет уже в большем числе случаев.
Решение первое
SBCL умеет запускать программы на CL в режиме скриптов командной строки,
для этого используется специальный флаг --script
. Более того,
благодаря этому можно написать лисп-программу, добавить к ней типичный
юниксовый шебанг, вызывающий SBCL с этим флагом, и она будет работать
как любой другой шелл-скрипт (за исключением того, что рантайм SBCL
весит 50MB бгг). Так что напишем этот скрипт:
#!/usr/bin/sbcl --script
(load "filter.lisp")
(:localhost.me.filter:run)
Теперь положим этот скрипт в ту же директорию, что и filter.lisp
, в
файл с именем run
и можно будет запускать нашу программу так:
$ ./run
Что и требовалось.
Решение второе
Однако, у флага --script
есть одна особенность: SBCL не загружает
никакие пользовательские скрипты инициализации при старте, так что
скрипт будет выполняться в 100% дефолтном рантайме. Это плохо, если,
допустим, вы используете
Quicklisp и у вас в
filter.lisp
есть такой вызов:
...
(ql:quickload :CL-FAD)
(use-package :CL-FAD)
...
Ну или вызов любой другой библиотеки, неважно. Если запустить такой
скрипт первым способом, то SBCL будет грязно ругаться по поводу того,
что он не знает, что такое ql:quickload
.
Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким
бинарником, у которого все нужные библиотеки уже загружены. В SBCL есть
функция save-lisp-and-die
, которая выгружает текущее состояние
рантайма в файл с указанным именем. Дальше этот файл можно использовать
как обычный бинарник SBCL, в том числе, и для вызовов с использованием
--script
. Поэтому если запустить SBCL и сразу выгрузить его в файл, то
полученный бинарник будет содержать всё, что SBCL подключил при
загрузке, основываясь на пользовательских конфигах.
(save-lisp-and-die "/абсолютный/путь/до/файла")
Теперь в скрипте run
, который написан по первому варианту, заменяем
путь до системного SBCL путём до выгруженного бинарника, и скрипт начнёт
работать как должен.