过滤输出流以仅保留由特定行分隔的部分文本?

Filter output stream to only keep portion of text delimited by specific lines?

我已经创建了一个简单示例来说明我要完成的任务。本质上,我需要 运行 一个 shell 命令并捕获其输出,但只是其中的特定部分。以下面的 bash 命令为例:

> echo "hello\nhello\nstart\nI\nWANT\nTHIS\nTEXT\nend\nhello\n"
hello
hello
start
I
WANT
THIS
TEXT
end
hello

我预期的 common-lisp 输出将是 (list "I" "WANT" "THIS" "TEXT")。我有一个半工作解决方案,但想知道如何获得 exact 所需的输出,以及是否有更惯用的方法来完成此任务。

首先,我创建一个闭包来跟踪我应该处理的行:

(defun make-pre-processor ()
  (let ((proc-lines nil))
    #'(lambda (str)
        (cond
          ((string= str "start") (setf proc-lines t))
          ((string= str "end") (setf proc-lines nil))
          (proc-lines str)))))

接下来,我使用 let 语句启动我的程序,然后遍历输出流:

(let* ((input (concatenate 'string
                "hello\n" "hello\n" "start\n"
                "I\n" "WANT\n" "THIS\n" "TEXT\n"
                "end\n" "hello\n"))
        (command (concatenate 'string "echo " "\"" input "\""))
        (*proc* (uiop:launch-program command :output :stream))
        (stream (uiop:process-info-output *proc*))
        (take-lines? (make-pre-processor)))
  (uiop:wait-process *proc*)
  (loop while (listen stream) collect
    (funcall take-lines? (read-line stream))))

哪个returns

(NIL NIL T "I" "WANT" "THIS" "TEXT" NIL NIL NIL)

如您所见,我不想要 TNIL 值。我还必须使用我不太喜欢的 uiop:wait-process,但我假设这是必需的。

更广泛的图片,我有大约 100 个这样的命令需要 运行 和解析。因此,我将同时关注 运行。这只是出于某种观点,我会 post 在一个单独的问题中。

> (loop for e in '(NIL NIL T "I" "WANT" "THIS" "TEXT" NIL NIL NIL)
        when (stringp e)
        collect e)
("I" "WANT" "THIS" "TEXT")

还有这个:

CL-USER 17 > (defun skip-lines-until (stream stop-line)
               (loop for line = (read-line stream nil)
                     while (and line
                                (not (string= line stop-line)))))
SKIP-LINES-UNTIL

CL-USER 18 > (defun collect-lines-until (stream stop-line)
               (loop for line = (read-line stream nil)
                     while (and line (not (string= line stop-line)))
                     collect line))
COLLECT-LINES-UNTIL

CL-USER 19 > (let ((lines "hi
there
start
1
2
3
stop
more
here"))
               (with-input-from-string (stream lines)
                 (skip-lines-until stream "start")
                 (collect-lines-until stream "stop")))
("1" "2" "3")

如果你想在一个地方完成所有工作,你可以使用loop编码状态机:

(with-open-file (in "/tmp/example")
  (loop
    for previous = nil then line
    for line = (read-line in nil nil)
    for start = (equal previous "start")
    for end = (equal line "end")
    for active = nil then (and (not end) (or active start))
    while line
    when active collect line))

这里是 table 随着时间的推移绑定到每个循环变量的值,其中点表示 nil 以便于阅读。

|----------+-------+-------+-------+------+------|
| line     | hello | start | text  | text | end  |
|----------+-------+-------+-------+------+------|
| previous | .     | hello | start | text | text |
| start    | .     | .     | T     | .    | .    |
| end      | .     | .     | .     | .    | T    |
| active   | .     | .     | T     | T    | .    |
|----------+-------+-------+-------+------+------|