;;;; common-shell.lisp #| scm, ldbeth and I were talking about lisp and shells on our phlogs and mastodon. A shell is a program whose main responsibility is to fork and exec. Making system(3) calls to the parent shell is a kind of shell scripting, but it would also be cool to be a shell ourselves. I figure I need a reader macro that calls strings and gets their output. Using direct control of *read-table* and read-time macros is one iconic feature of lisp. #:uiop, which is wrapped in with #:asdf provides a portable posix compatibility layer. BUT FIRST I feel like I have been a little too tech-blogger lately. Lisp and falteringly jamming soft synth is part of who I am, but I will try and make at least a phlog a week that is not just C, lisp and systems. But that is a topic for later. |# (require 'asdf) ;; in order to get the portable #'uiop:run-program (defun my-shell (s c m) " (my-shell stream character number) collects READs from stream until the next read-char is #\], which is eaten. maps #'uiop:run-program to that list, except *standard-output* is collected as a list of newline delimited strings. " (declare (ignore c m)) `(let ((list ',(loop for r = (read s nil nil) for ch = (read-char s nil nil) collect r while (not (char= ch #\])) do (unread-char ch s)))) (mapcar (lambda (x) (let ((response (with-output-to-string (*standard-output*) (uiop:run-program x :output t)))) (with-input-from-string (*standard-input* response) (loop for line = (read-line *standard-input* nil nil) while line nconcing (unless (string= "" line) `(,line)))))) list))) ;; And let's Make that the reader macro for #[ (set-dispatch-macro-character #\# #\[ #'my-shell) #| I think this is pretty powerful already. To see it in action, we must evoke the reader (this file is finished being read prior to being evaluated to begin with, so we have to READ something else to see it in action.) |# (defun shell-reader-example-1 () (let ((unread-string " #[ \"date\" \"printf 'floo\\\\nblah' | sed s/o/e/\"] ") (output)) (with-input-from-string (*standard-input* unread-string) (setf output (eval (read)))) (values output))) (shell-reader-example-1) #| $ rlwrap ecl --load common-shell.lisp ECL (Embeddable Common-Lisp) 21.2.1 (git:UNKNOWN) Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya Copyright (C) 1993 Giuseppe Attardi Copyright (C) 2013 Juan J. Garcia-Ripoll Copyright (C) 2018 Daniel Kochmanski Copyright (C) 2021 Daniel Kochmanski and Marius Gerbershagen ECL is free software, and you are welcome to redistribute it under certain conditions; see file 'Copyright' for details. Type :h for Help. Top level in: #<process TOP-LEVEL 0x1cf7126f80>. > (shell-reader-example-1) (("Mon Nov 14 09:17:18 UTC 2022") ("fleo" "blah")) > Alright that seems to be working. |# (defun shell-reader-example-2 () (let ((input " #[ \"tmux new-session -sFOO -d\" \"tmux send-keys -tFOO:0 'date' C-m\" \"tmux capturep -tFOO:0 -p\"] ") (output)) (with-input-from-string (*standard-input* input) (setf output (eval (read)))) (values output))) #| > (shell-reader-example-2) (NIL NIL ("$ date" "Mon Nov 14 09:22:26 UTC 2022" "$")) interesting. |# #| Otherwise... I still owe jns copying an org file from some laptops that are sleeping right now basically, which is incredibly cool and trivial to do. And if darkness didn't get figured out I should probably try to participate in that. Once I post that org file, it would be cool if lots of people used it. The org file ended up very convenient (not pictured here). And I didn't use ronald's gopher revision control yet, though it will make my life infinitely better once I get to it. |#