在org-mode下重用一个代码块的结果
DONE 在org-mode下重用一个代码块的结果
:categories: emacs,elisp,orgmode :date: 2019/02/12 09:20:58 :updated: 2019/02/25 14:17:16 :org-url: http://kitchingroup.cheme.cmu.edu/org/2019/02/12/Using-results-from-one-code-block-in-another-org-mode.org :permalink: http://kitchingroup.cheme.cmu.edu/blog/2019/02/12/Using-results-from-one-code-block-in-another-org-mode/index.html
This runs in a session where you can keep variables in memory between blocks, and use them in subsequent blocks. org模式中一个非常棒的特性是,您可以使用许多选项在代码块之间传递数据。 在这篇文章中,我们将介绍其中一些选项在emacs-lisp语言中的应用。 它在 会话 中运行,通过会话你可以将变量保存在块之间的内存中,并在后续块中使用它们。
这里我们为一个变量设置值。
(setq some-variable 42)
42
然后在另一个块中我们可以使用这个变量:
(+ some-variable 1)
43
你可以在会话中使用 some-variable
。令人感到意外的是,emacs-lisp会话是全局性的,您甚至可以在另一个缓冲区中访问 some-variable
!
但是不要这样做。当您关闭emacs后,该变量将消失,只剩下上面的结果。
还有一种方法可以使用块标头中的命名代码块和变量将信息从一个块传递到另一个块。这允许您按名称在块之间传递数据,稍后您将会看到你甚至可以从其他文件中通过名称访问结果。
:var
首先,我们给代码块一个名字,像这样:
#+name: block-1 #+BEGIN_SRC emacs-lisp (current-time-string) #+END_SRC
当我们运行它时,结果中会有一个名字。
(current-time-string)
Tue Feb 12 08:19:23 2019
现在,我们可以通过 :var 标头将命名结果作为 输入 作用于一个新块。
#+BEGIN_SRC emacs-lisp :var input=block-1 (format "We got %S in block-1" input) #+END_SRC
当我们运行这个块时,emacs将运行block-1并将输出放入变量 input
中,该变量被用于代码块中。
(format "We got %S in block-1" input)
We got "Tue Feb 12 08:20:44 2019" in block-1
注意以下几点:
- 每次运行它,block-1都会重新运行。
- 此块中的结果与block-1中的结果不同
- 运行第二个块时,block-1中的结果不会改变。
您可能不想每次都重新运行block-1;比如这个计算可能非常昂贵,或者每次的计算结果都一样。那么可以使用:cache标头来防止每次都重新计算。
:cache
如果您指定了 :cache yes
,那么org-mode 应该会 存储一个带有结果的代码块散列,只要代码块没有改变,那么它就不会重新运行。
#+name: block-2 #+BEGIN_SRC emacs-lisp :cache yes (current-time-string) #+END_SRC
(current-time-string)
Tue Feb 12 08:06:22 2019
现在,我们使用block-2作为输入,我们可以看到其输出和block-2的输出是一样的。
(format "We got %S in block-2" input)
We got "Tue Feb 12 08:06:22 2019" in block-2
好吧,但是如果我的结果太大而不能放入buffer,或者太复杂而不能用文本来表达怎么办?你还有其他选择。
:wrap
假设我们在某个块中生成了一些json,然后希望在另一个块中使用它。 同时我们还希望看到json作为中间结果存储在buffer中。我们可以像这样将输出包装在json块中。
(require 'json) (json-encode `(("date" . ,(current-time-string))))
{"date":"Tue Feb 12 08:30:20 2019"}
然后,我们可以很容易地将输出注入到一个新块中。
(format "We got %S in json" input)
We got "{"date":"Tue Feb 12 08:30:20 2019"} " in json
这对文本数据非常简单. 但对二进制数据就不太好了.
值得注意的是,你甚至能在另一个org文件中引用这个结果:
#+BEGIN_SRC emacs-lisp :var input=./2019-02-12.org:json input #+END_SRC #+RESULTS: : {"date":"Tue Feb 12 08:30:20 2019"}
:file
如果数据太大或者数据是二进制的,无法方便地存入org文件,也没问题,只需使用:file头将其存入外部文件即可。像这样:
#+name: block-3 #+BEGIN_SRC emacs-lisp :cache yes :file block-3 (require 'json) (json-encode `(("date" . ,(current-time-string)))) #+END_SRC #+RESULTS[a14d376653bd8c40a0961ca95f21d8837dddec66]: block-3 [[file:block-3]]
注意,您必须提供一个文件名。如果你希望文件名有意义,可以发送给某人,这当然不错,但如果有一个自动命名方案也不错,例如基于代码块块的sha-1散列值就不错。
(require 'json) (json-encode `(("date" . ,(current-time-string))))
现在您可以使用其他工具来查看文件。在这里,我们仍然通过简单的shell工具来查看。
cat block-3
{"date":"Tue Feb 12 08:46:55 2019"}
block-3的输出是一个文件名:
input
/Users/jkitchin/Box Sync/kitchingroup/jkitchin/journal/2019/02/12/block-3
因此,您可以在新块中从它那读取数据,然后对其进行一些新操作。
(with-temp-buffer (insert-file-contents input) (format "We got %S in block-3" (json-read-from-string (buffer-string))))
We got ((date . "Tue Feb 12 08:46:55 2019")) in block-3
"remote" data
代码块不一定要按顺序排列。如果你愿意,你可以把一些代码块放在附录里,然后在正文的代码块中使用使用它们。 这样,您可以在正文中存放简短的代码块,这样可读性更好,而在其他地方存放更长、更复杂的块,这样就不会使文档混乱。
(with-temp-buffer (insert-file-contents input) (format "We got %S in the appendix data" (json-read-from-string (buffer-string))))
We got "{"date":"Tue Feb 12 09:11:12 2019"}" in the appendix data
手动保存文件中的数据
注意你也可以手动保存数据到文件中,例如:
(require 'json) (let ((f "block-4.json")) (with-temp-file f (prin1 (json-encode `(("date" . ,(current-time-string)))) (current-buffer))) f)
block-4.json
我们将文件名作为块返回的最后一个变量,这样我们就不必在下一个块中手动输入它。你知道,尽量不要重复…
This just shows we did write out to our file: 这只是为了证明我们确实写入了内容到文件中:
cat block-4.json
:"Tue Feb 12 08:50:00 2019"}
我们使用block-4中的文件名作为输入变量读取文件。
(with-temp-buffer (insert-file-contents input) (format "We got %S in block-4" (json-read-from-string (buffer-string))))
We got "{"date":"Tue Feb 12 08:51:25 2019"}" in block-4
数据附录
(require 'json) (let ((f "appendix.json")) (with-temp-file f (prin1 (json-encode `(("date" . ,(current-time-string)))) (current-buffer))) f)
appendix.json
警告
这样使用org-mode几乎总是在存储内容和存储位置方面找到正确的权衡。 并非所有中间数据/计算都需要储存起来;如果它们真的很便宜,你可以通过重新运行代码块获取结果。 如果它们非常小,也就是说,只需要几行内容就能狗读回来,那么可以将它们存储在文档中。如果它们非常大,可以将它们存储在外部文件中。
在org文件中包含所有内容的美妙之处在于您只有单个文件,易于传输。 但是,当文件变得太大时,就不那么实用了,例如,如果试图将数千行xml数据都放入buffer中,emacs可能会变慢。 然后,你必须做出一些决定,保存什么,保存在哪里,以什么形式保存。
对于只需要单个计算会话的短期项目来说,将所有内容都放在内存中可能没有问题。 但对于较长期的项目来说,如果一个项目足够长期,您需要会关闭所有buffer,甚至可能在此期间会重新启动emacs,这是您必须做出一些决策,每个块中保存什么内容,才能让你在下一次会话中继续工作。 同样的,您必须决定保存什么、在何处保存以及以什么形式保存。
一旦您开始在org文件之外保存数据,它的可移植性就会降低,或者说移动文件会变得更加棘手,因为您需要移动所有的数据文件来保持它的完整性。 之前,我曾探讨过org-archive的概念,这个概念使你可以获得org文件中链接的所有文件列表,但到目前为止,这只是一些概念的小证明而已(proof of concept ideas)。
不同的语言在org模式下是不相同的。例如,有些并不支持会话,而且有些可能无法像这里例子中那样工作。 改版的scimax iPython行为就与上面的示例不同。这可能是我无意中引入的bug造成的,以后我将尝试使它像上面emacs-lisp那样工作。
总的来说,org-mode是我见过的在文档中传递和重用数据的最灵活、最强大的系统之一。 它并不完美的,而且在这样一个强大的系统中,有许多未经探索或很少用到的场景可能存在危险。不过总体来说它看起来还是很有前景的。