Unix en Lisp: monte Unix en la imagen de Common Lisp
Unix en Lisp es actualmente un shell Lisp (¡no POSIX!) utilizable para Unix. La característica distintiva de Unix en Lisp es que, en lugar de crear un sublenguaje para las operaciones de Unix, los conceptos de Unix se integran directa/superficialmente en Lisp (los comandos de Unix se convierten en macros de Lisp, los archivos de Unix se convierten en variables de Lisp, los flujos de Unix se convierten en secuencias perezosas de Lisp, etc. ).
El hecho de que Unix en Lisp es Lisp, en lugar de un intérprete escrito en Lisp, hace posible aprovechar las herramientas existentes para Lisp. Una instancia es el oyente de Unix en SLIME, que hereda la finalización, el depurador interactivo y múltiples oyentes, etc. del mismo SLIME. Otro ejemplo es que la biblioteca CL existente, como las funciones auxiliares de secuencia de serapeum
funciona de forma inmediata en flujos de proceso de Unix en Lisp.
Tabla de contenido
Inicio rápido
Actualmente, solo se admite SBCL. Clone este repositorio en su ~/quicklisp/local-projects/
. Unix en Lisp está actualmente en alfa etapa y recibirá cambios frecuentes. Se recomienda usar Ultralisp para instalar dependencias, para asegurarse de que haya varias correcciones de errores disponibles en el flujo ascendente en las que se basa Unix en Lisp. Si no lo has hecho, (ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)
. Antes del primer uso, ejecute (ql:update-dist "ultralisp")
y (ql:quickload "unix-in-lisp")
para instalar todas las dependencias. También se recomienda (ql:update-dist "ultralisp")
y git pull
este repositorio regularmente para obtener actualizaciones y correcciones de errores.
Se recomienda cargar unix-in-slime.el
para una mejor integración SLIME. Para cargarlo, evaluar (require 'unix-in-slime "~/quicklisp/local-projects/unix-in-lisp/unix-in-slime")
en emacs. Es posible que desee agregar esta línea a su init.el
.
unix-in-slime
instala hacks en el entorno Host Lisp llamando (unix-in-lisp:install)
en el arranque. Para deshacer los hackeos realizados en el entorno del host y desmontar los paquetes de Unix FS, ejecute (unix-in-lisp:uninstall)
.
Ejemplos
(Se omiten algunas impresiones)
Contando el número de archivos
/Users/kchan> (cd quicklisp/local-projects/unix-in-lisp)/Users/kchan/quicklisp/local-projects/unix-in-lisp> (pipe (wc -l) (ls)) 9
¡Pero por qué no al estilo Lisp también!
/Users/kchan/quicklisp/local-projects/unix-in-lisp> (length (package-exports ./))9
Para ver más ejemplos, consulte TUTORIAL.org
Documentación
Mapeo del sistema de archivos
Los directorios se asignan como Paquetes Unix FS. Un paquete Unix FS es cualquier paquete Common Lisp cuyo nombre de paquete designa un nombre de ruta absoluto (generalmente cuando comienza con una barra inclinada).
Los símbolos exportados de un paquete Unix FS deben corresponder uno a uno con los archivos en el directorio asignado. Excepciones a esta correspondencia uno a uno:
- Debido al límite del seguimiento de cambios del sistema de archivos, la estructura del paquete en la imagen de Common Lisp puede diferir del estado de Unix FS.
- Actualmente, el estado de un paquete Unix FS se sincroniza al llamar
mount-directory
. Por defecto,remount-current-directory
se agrega a*post-command-hook*
que hace lo obvio.
- Actualmente, el estado de un paquete Unix FS se sincroniza al llamar
Cada uno de estos símbolos exportados tiene un enlace de macro de símbolo global, por lo que se pueden leer/escribir como variables Lisp. El acceso al símbolo proporciona la lista de líneas del archivo subyacente, y configurarlo con un indicador de lista de líneas hace que se escriban en el archivo.
unix-user /Users/kchan/> .bashrc("export CC="clang"" "export PS1='$(hostname):$(pwd) $(whoami)$ '" ...)
Tenga en cuenta que no hay ningún símbolo correspondiente para un archivo inexistente. Para escribir o crear un archivo del que no está seguro si ya existe, se recomienda utilizar defile
macro, que asegurará que el archivo existe y crea el símbolo correspondiente.
unix-user /Users/kchan/> (defile iota.txt (iota 10))/Users/kchan/iota.txtunix-user /Users/kchan/> iota.txt("0" "1" "2" "3" "4" "5" "6" "7" "8" "9")
En el ejemplo anterior, si iota.txt
no existe y yo uso setq
en lugar de defile
un símbolo interno llamado IOTA.TXT
será creado en UNIX-USER
paquete en su lugar y escribiré en su celda de valor, en lugar de /Users/kchan/iota.txt
en el sistema de archivos.
Asignación de comandos y procesos
Unix en Lisp gestiona trabajos en la unidad de Procesos efectivos. Estas tesis incluyen procesos regulares de Unix representados por simple-process
y pipeline
‘s que consisten en cualquier número de procesos UNIX y etapas de funciones Lisp.
Comandos simples
Cuando Unix en Lisp mapea un directorio, se verifica el permiso de ejecución de los archivos y los ejecutables se mapean como macros de Common Lisp. estas macros implícitamente cuasicitas sus argumentos Los argumentos se convierten en cadenas usando literal-to-string
luego se pasa al ejecutable correspondiente.
Ejemplos de uso de macros asignadas desde comandos de Unix
/Users/kchan/some-documents> (cat ,@(ls));; This cats together all files under current directory.
También puede configurar redirecciones (y tal vez otras configuraciones de creación de procesos en el futuro) mediante el suministro de argumentos de palabras clave. Estos argumentos no son implícitamente cuasicitado y son evaluado.
/Users/kchan/some-documents> (ls :output *terminal-io*);; This outputs to *terminal-io*, which usually goes into *inferior-lisp* buffer.
/Users/kchan/some-documents> (ls :error :output);; This redirect stderr of ls command to its stdout, like 2>&1 in posix shell
como has descubierto en (cat ,@(ls))
los procesos efectivos se pueden usar como secuencias Lisp: designan la secuencia de sus líneas de salida.
Tubería
Los conductos se crean a través de la pipe
macro:
/Users/kchan/quicklisp/local-projects/unix-in-lisp> (pipe (wc -l) (ls)) 9
Debajo del capó, excepto la primera etapa, se pasa cada etapa de la tubería :input
como argumento adicional. Alternativamente, si hay argumentos _
, se sustituyen por el resultado de la etapa anterior. Puede mezclar funciones y valores de Lisp con comandos de Unix. Usar el valor de Lisp como la primera etapa de entrada es bastante fácil:
/Users/kchan> (pipe (iota 10) (wc)) 10 10 20
El _
extensión hace que sea fácil agregar funciones Lisp a la mezcla:
/Users/kchan> (pipe (ls) (filter (lambda (s) (> (length s) 10)) _) (wc -l)) 47
Lo anterior cuenta el número de archivos con un nombre de archivo superior a 10 en mi directorio de inicio.
Uso interactivo
Dentro de una unix-in-slime
oyente, si el valor principal de la evaluación es un proceso efectivo y tiene flujos de entrada/salida disponibles, unix-in-slime
automáticamente “conectarlo” al oyente, es decir, la E/S del oyente se redirige al proceso, similar a procesos de primer plano en shell POSIX:
/Users/kchan> (python3 -i)Python 3.8.9 (default, Apr 13 2022, 08:48:07)[Clang 13.1.6 (clang-1316.0.21.2.5)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> print("Hello world!")Hello world!>>> ; No values/Users/kchan>
Atención: usar C-u RET
para señalar EOF en unix-in-slime
Similar a Ctrl D
en conchas POSIX. Puede interrumpir la evaluación a través de C-c C-c
como de costumbre, después de lo cual se le proporcionarán algunos reinicios:
BACKGROUND
pone el trabajo en segundo plano (accesible a través deunix-in-lisp:*jobs*
)ABORT
finaliza el trabajo actual (a través deSIGTERM
para procesos Unix)
Atención: tienes que usar -i
marca para iniciar Python REPL, porque Unix en Lisp actualmente se comunica con todos los procesos usando pipe en lugar de pseudo tty. Sin -i
, Python se iniciará en modo no interactivo. Otros REPL pueden necesitar banderas respectivas.
Al usar Unix en Lisp fuera unix-in-slime
usar (unix-in-lisp:repl-connect
para lograr lo mismo.
unix-in-lisp:*jobs*
mantiene una lista de procesos efectivos en ejecución:
unix-in-lisp> *jobs*(#)
Tenga en cuenta que porque unix-in-slime
el oyente conecta un trabajo automáticamente si es el valor principal de la evaluación, puede usar, por ejemplo
unix-in-lisp> (nth 0 *jobs*)
para reanudar desde un trabajo en segundo plano.
unix-in-lisp:repl-connect
conecta un proceso exclusivamente en a lo sumo un oyente. Si un proceso ya está conectado en otro oyente, no hará nada y el objeto de proceso efectivo se imprimirá como de costumbre. De hecho, muchas operaciones de Unix en Lisp (incluyendo repl-connect
y pipe
) tiene acceso exclusivo al flujo de entrada/salida de los procesos (estableciendo las ranuras respectivas en nil
durante su curso de operación).
Ambiente
Las variables de entorno de Unix se asignan a variables Lisp especiales (de alcance dinámico).
/Users/kchan> $logname"kchan"
Puede configurarlos o vincularlos dinámicamente
/Users/kchan> (setf $test "42")"42"/Users/kchan> (pipe '("echo $TEST") (bash))42nil/Users/kchan> (let (($test "override")) (pipe '("echo $TEST") (bash)))overridenil
Lo anterior funciona con la ayuda de una macro lectora definida en $
, que registra el siguiente símbolo como una variable de entorno. Si desea usar Unix en variables de entorno Lisp sin nuestra tabla de lectura, debe usar la función unix-in-lisp:ensure-env-var
para registrar el símbolo primero. Consulte su cadena de documentación para obtener más información.
Unix en Lisp mantiene su propia idea de un entorno Unix y pasa a los subprocesos creados por él (por ejemplo, a través de las macros que creó a partir de los comandos de Unix). Otras instalaciones de Lisp (p. ej. uiop:run-program
) no lo sabe, y por lo general hereda el entorno Unix “real” del proceso Lisp. Para remediar esto, Unix en Lisp proporciona la función unix-in-lisp:synchronize-env-to-unix
que copia el entorno Unix en Lisp gestiona el entorno Unix “real” del proceso Lisp. Esto se ejecuta de forma predeterminada *post-command-hook*
y es posible que desee llamarlos antes de usar otras funciones de Lisp que generan subprocesos de Unix.
Scripting (inicio increíblemente rápido)
La forma recomendada de escribir scripts es crear archivos ejecutables (digamos do-stuff.sh
) con contenidos como
#!/usr/env/bin sbcl --script(asdf:require-system "")(asdf:require-system "unix-in-lisp")(unix-in-lisp:setup)
El beneficio del enfoque anterior es que es increíblemente rápido cuando se inicia desde Unix en Lisp (a través de, por ejemplo, (do-stuff.sh)
), porque Unix en Lisp tiene un Comando de carga rápida mecanismo, que puede ejecutar el script dentro de Unix en la imagen de Lisp sin iniciar el subproceso si detecta un shebang de Lisp. La esencia de escribir un script de inicio rápido es:
- Usar
#!/usr/env/bin sbcl --script
el asunto. Actualmente tiene que ser una coincidencia exacta. - Usar
asdf:require-system
. Esto evita escanear el árbol de directorios del registro ASDF en busca de modificaciones, ¡lo que desperdicia mucho tiempo!
En mi máquina, un hola mundo que usa el enfoque anterior se ejecuta en 0,5 ms, mientras que Python 3 usa 30 ms.
Unix en SLIME
Las documentaciones anteriores han sido suponiendo que está utilizando el unix-in-slime
oyente. Aquí documentamos algunos aspectos adicionales de unix-in-slime
.
Unix en Lisp asume un servidor ostentoso dedicado para unix-in-slime
oyentes (y potencialmente otras interfaces en el futuro). M-x unix-in-slime
comenzará uno en unix-in-slime-default-port
(4010 por defecto) si no existe ninguno en la imagen de Unix en Lisp. El servidor maneja múltiples conexiones, por lo que puede iniciar múltiples de manera segura unix-in-slime
oyentes al mismo tiempo, como la forma en que debe haber vivido con múltiples ventanas de terminal.
Rendimiento: ¡rápido!
Un logro bastante inesperado es que unix-in-slime
es un shell muy rápido para Emacs. De hecho, un sencillo (pipe "time for i in {0..99999}; do echo line $i; done" (sh))
el punto de referencia tarda 0,83 s en unix-in-slime
y tarda 2,93 s en vterm
. unix-in-slime
es más de 3 veces más rápido que uno de los emuladores de terminal Emacs más rápidos (¡parcialmente escrito en C)! Por supuesto, esta no es una comparación cara a cara porque vterm
es un emulador de terminal mientras unix-in-slime
es un shell, pero experimenté con frecuencia salidas de comandos rápidos que ahogan Emacs y es bueno saberlo unix-in-slime
es bastante bueno en el manejo de estos. Creo que la razón es que el servidor ostentoso de SLIME hace algunos ajustes muy específicos de Emacs, por ejemplo, limita la tasa de paquetes de la red porque sabe que Emacs se ahoga con una avalancha de ellos, lo que también nos beneficia cuando lo usamos como shell.
Terminación
Si ha configurado la finalización para SLIME, la finalización funciona de forma inmediata para unix-in-slime
. Tenga en cuenta que automáticamente obtenemos “completar nombre de archivo”, porque están asignados como símbolos, ¡y tenemos la finalización de símbolo en casa! Actualmente hay una peculiaridad: los nombres de archivo siempre se completan en su ruta totalmente resuelta (con .. . ~
componentes resueltos), porque eso es lo que corresponde a los símbolos. Diría que es un error o una característica dependiendo de a quién le preguntes, lo dejaré así por ahora.
Estructura del sistema de paquetes
Unix en Lisp define y llena una cantidad de paquetes durante unix-in-lisp:install
. Primero, unix-in-lisp:path
se crea de acuerdo a $PATH
Variable ambiental. Entonces, unix-in-lisp.common
se asegura la reexportación unix-in-lisp.path
, y también exportar símbolos correspondientes a variables de entorno. Los paquetes que deseen hacer uso de las funcionalidades de Unix en Lisp deben usar unix-in-lisp.common
, y potencialmente sombrear la importación de algunos de sus símbolos. Cualquier otro uso de paquetes creados por Unix en Lisp es menos seguro, incluido el uso o la importación de símbolos de los paquetes de Unix FS, particularmente porque invocar unix-in-lisp:uninstall
los elimina.
El oyente de Unix en SLIME por defecto comienza en unix-user
paquete, que utiliza unix-in-lisp.common
y otros paquetes de utilidades. Esto hace que todos los oyentes compartan el mismo paquete de forma predeterminada, pero también puede crear nuevos paquetes y cambiar a los oyentes. Tenga en cuenta que nosotros no apoyar el directorio actual por usando su correspondiente paquete Unix FS. En cambio, un gancho de lectura (para sb-impl::%intern
) esta instalado d que reemplace los símbolos que indican la ruta relativa con un nuevo símbolo no interno “efectivo” que fusiona los enlaces del símbolo original y los símbolos montados de acuerdo con la ruta relativa en el directorio actual (*default-pathname-defaults*
). Al igual que en Unix, nuestra redirección nunca sombrea los enlaces de funciones globales existentes, para evitar la ejecución involuntaria de archivos en el directorio actual.