緩衝區和文件

假若將 Elisp 的應用場景固定爲文本處理,學習 Elisp,我認爲無需像學習其餘任何一門編程語言那樣亦步亦趨,因此本章直接從文件讀寫開始入手,經過一些小程序,創建對 Elisp 語言的初步感覺。編程

Hello world!

雖然我已決定從文件讀寫開始學習 Elisp,可是我仍是但願像學習任何一門編程語言那樣,從寫一個可以輸出 Hello world! 的程序開始。小程序

用 Emacs 新建一份文本文件,名曰 hello-world.el。固然,也可使用其餘文本編輯器完成此事,可是要保證系統已安裝了 Emacs 且可用。數組

hello-world.el 的內容只有一行:網絡

(princ "Hello world!\n")

在終端(或命令行窗口)裏,將工做目錄(當前目錄)切換至 hello-world.el 文件所在的目錄,而後執行編程語言

$ emacs -Q --script ./hello-world.el

終端會隨即顯示編輯器

Hello world!

從這個 Hellow world 程序裏,能學到哪些 Elisp 知識呢?函數

首先,princ 是一個函數,確切地說,是 Elisp 的內建函數。什麼是函數?在數學裏,y=f(x) 是函數,f 可將 x 映射爲 y。princ 也是這樣的函數,它將 "Hello world!\n 這個對象映射爲顯示於終端的對象,姑且這樣認爲。學習

其次,"Hello world!\n" 是 Elisp 的字符串類型,用於表示一段文本。文本是數據。數據未必是文本。若將 Elisp 做爲用於處理文本的語言,字符串就是基本且核心的數據類型。搜索引擎

最後,這個做爲示例的 Elisp 程序的最小單位是一個函數調用。我向 princ 函數提供一個字符串類型的值,即可令其工做,且足以構成一個程序。Emacs 裏有 Elisp 解釋器。Elisp 程序是由 Elisp 解釋器解釋運行的,相似於計算機程序是由計算機的 CPU 「解釋」運行。換言之,Elisp 解釋器可以讀懂 Elisp 程序,並完成這個程序所描述的工做,例如在終端裏輸出 Hello world!命令行

習題:在 Hello world 程序中,將字符串 "Hello world!\n" 裏的 \n 刪除,而後從新運行程序,觀察終端的輸出有何變化。

定義一個新的函數

"Helo world!\n" 裏的 \n 是換行符。計算機鍵盤上的 Enter 鍵,在大多數狀況下,所起的做用即是 \n。從 Enter 鍵的角度看待

(princ "Hello world!\n")

就像咱們在與他人在網絡上聊天同樣,輸入 Hello world,而後單擊 Enter 鍵發送。下面,我定義了一個新的函數 princ\',它能夠接受我要發送的信息,而後幫我發送:

(defun princ\' (x)
  (princ x)
  (princ "\n"))

定義一個函數,遵循的格式是

(defun 函數名 (參數)
  函數體)

函數體由一個或一組表達式構成。在 princ\' 的定義中,函數體由兩個表達式構成。

一旦函數有了定義,即可以調用,就像調用 princ 函數那樣。下面是基於 princ\' 重寫的 Hello world 程序,

(defun princ\' (x)
  (princ x)
  (princ "\n"))

(princ\' "Hello world!")

緩衝區

假設存在文本文件 foo.txt,其內容爲

Hello world!

如何寫一個 Elisp 程序,從 foo.txt 讀取所有內容並輸出到終端?

讀取文件,這個操做意味着什麼?意味着從計算機輔存(硬盤)中獲取數據,放入主存(內存)。緣由在於,計算機 CPU 訪問主存的速度遠快於輔存。

爲了簡化文件的讀寫,Elisp 提供一種數據類型——緩衝區(Buffer)。任何一種編程語言,都有數據類型,例如整型數字,浮點型數字,字符串,數組,列表等,這些類型在 Elisp 語言裏也是有的。緩衝區也是一種數據類型。緩衝區對象(也可稱爲緩衝區實例)本質上是計算機主存裏的一段空間。文件的內容被讀取後,存入緩衝區實例裏,在後者中可進行文件內容的編輯工做。編輯完畢後,緩衝區實例包含的信息能夠再存迴文件。爲了便於描述,在不至於引發誤解的前提下,我會將緩衝區實例簡稱爲緩衝區。相似的稱謂也適用於 Elisp 的其餘數據類型上。

使用 Elisp 函數 generate-new-buffer 能夠建立一個有名字的緩衝區。例如,建立一個名曰 foo 的緩衝區:

(generate-new-buffer "foo")

能建立一個,就能建立多個,可是不管建立了多少個,其中只可能有一個是激活的,亦即當前緩衝區。在讀取文件時,從文件獲取的數據老是存放在當前緩衝區內。Elisp 函數 buffer-name 能夠得到當前緩衝區的名字。如下程序可查看當前緩衝區的名字:

(princ\' (buffer-name))

Elisp 解釋器有一個默認的緩衝區,名字叫 *scratch*。假若沒有建立新的緩衝區並將其激活爲當前緩衝區,那麼上述程序的輸出就是 *scratch*

Elisp 函數 set-buffer 可將指定的緩衝區設爲當前緩衝區。例如,下面這個程序可將上文建立的 foo 緩衝區設爲當前緩衝區,並經過輸出當前緩衝區的名字它是否爲當前緩衝區:

(set-buffer "foo")
(princ\' (buffer-name))

set-buffer 的參數除了能夠是緩衝區的名字,也能夠是緩衝區自己。因爲 generate-new-buffer 可以返回它建立的新緩衝區,所以它能夠與 set-buffer 函數複合,用於建立一個緩衝區並將其設爲當前的緩衝區,例如

(set-buffer (generate-new-buffer "foo"))

將上述代碼綜合一下,能夠放在一個名字叫 foo.el 的文本文件裏。foo.el 內容爲

(defun princ\' (x)
  (princ x)
  (princ "\n"))

(princ\' (buffer-name))

(set-buffer (generate-new-buffer "foo"))
(princ\' (buffer-name))

在終端裏,若以 foo.el 所在目錄爲工做目錄,執行

$ emacs -Q --script ./foo.el

輸出爲

*scratch*
foo

這是我寫的第二個 Elisp 程序,感受還不錯。

文件讀取

對於上一節一開始所提出的問題,事實上並不須要我去爲待讀取的文件建立一個緩衝區,並將其設爲當前緩衝區。Elisp 提供的 find-file 能夠替我完成這項工做。例如,

(find-file "foo.txt")
(princ\' (buffer-name))

所產生的輸出爲

foo.txt

這個名曰 foo.txt 的緩衝區,即是 find-file 函數爲 foo.txt 文件而建立的。

如何確認 foo.txt 文件裏的內容真的被讀取後存放到 foo.txt 緩衝區呢?可經過 buffer-string 函數以字符串的形式得到當前緩衝區存儲的數據,而後將所得結果顯示於終端,例如

(princ\' (buffer-string))

所以,讀取 foo.txt 文件裏的內容,並將其顯示於終端的程序至此便完成了。完整的程序以下:

(defun princ\' (x)
  (princ x)
  (princ "\n"))

(find-file "foo.txt")
(princ\' (buffer-string))

代碼風格

Elisp 代碼,只要不破壞名字,它的風格是很隨意的。例如 princ\'函數的定義,寫成

(defun princ\' (x) (princ x) (princ "\n"))

也是能夠的。

寫成

(defun
    princ\'
    (x)
  (princ
   x)(princ
       "\n"))

也不是不行。可是,最好不要寫怪異的代碼。畢竟,那層層括號的嵌套,人生已經很不容易了。

括號不管是內層的,仍是外層的,它們老是成對出現。Lisp 語言最大特色就是,不管是函數的定義,仍是函數的調用,仍是其餘的一些表達式,在形式上是由括號構成的嵌套結構。這種結構,Lisp 語言稱爲列表。

若是使用 Emacs 編寫 Elisp 代碼或其餘 Lisp 方言的代碼,要記得安裝 paredit 包。我不想浪費時間去講如何安裝和使用這個包。不徹底是由於沒人給我發稿費,主要是每一個人都應該會用網絡搜索引擎。

在緩衝區內插入文本

不管是用 find-file 函數自動建立的緩衝區,仍是基於 generate-new-buffer 建立的緩衝區,一旦它們被設定爲當前緩衝區,即可以使用 Elisp 提供的一些函數,將數據寫入其中。

insert 函數可將字符串類型的數據寫入當前緩衝區,例如:

(defun princ\' (s)
  (princ (concat s "\n")))
  
(find-file "foo.txt")
(insert "|||")
(princ\' (buffer-string))

輸出結果爲

|||Hello world!

可見 insert 函數將 ||| 插入到了當前緩衝區存儲的文本數據的首部。這是由於,當前緩衝區內存在這一個不可見的光標,我將其稱爲插入點,它對應於 Emacs 圖形窗口裏不斷閃動的那個光標,表示文本的插入點。在使用 find-file 打開一份文件時,插入點會自動定位在文件的開頭,座標爲 1。爲了理解插入點,就須要將緩衝區想像成一維數組,存放的元素爲字符,這個一維數組就像一根很長的紙帶那樣,插入點的座標就是插入點位於第幾個字符以前。

point 函數能夠得到插入點的座標。例如

(find-file "foo.txt")
(princ (point))

輸出 1

goto-char 函數可將插入點移動到緩衝區內的任何位置。例如,假若將 ||| 插入 Hello world! 的兩個單詞的中間,只需

(find-file "foo.txt")
(goto-char 6)
(insert "|||")

因爲函數 point-minpoint-max 能夠得到緩衝區的起止位置,所以可基於它們將插入點移動到緩衝區的開頭或結尾。例如,將 ||| 插入到 Hello world! 的尾部:

(find-file "foo.txt")
(goto-char (point-max))
(insert "|||")

在此,也許應該提出一個疑問,爲什麼須要用 point-min 得到緩衝區起始位置?難道這個位置不是 1 嗎?由於在緩衝區內部能夠建立更小的局部區域,而它也是 Elisp 的一種數據類型,它的名字叫 Narrowing。對於位於 Narrowing 區域的文本,也能夠用 point-minpoint-max 獲取起止位置,故而 point-min 得到的結果未必是 1。關於 Narrowing,它在 Emacs 圖形界面裏較爲有用,在使用 Elisp 編寫文本處理程序方面,我暫時還沒思考出它的應用場景。

在緩衝區內刪除文本

Elisp 函數 delete-char 能夠刪除插入點以後的字符。例如,如下程序將 foo.txt 讀入緩衝區後,插入點尚在緩衝區起始位置時,刪除它後面 5 個字符,

(find-file "foo.txt")
(delete-char 5)

Elisp 也提供了一些與插入點位置無關的緩衝區文本刪除函數,其中 delete-region 能夠刪除落入指定區間的文本。例如,刪除緩衝區內第 6 個字符到第 12 個字符之間的字符,被刪除的字符包括前者,但不包括後者,

(find-file "foo.txt")
(delete-region 6 12)

可使用 (princ\' (buffer-string)) 查看緩衝區內容的變化。

將緩衝區內容寫入文件

如今,已經基本掌握了從文件讀取內容到緩衝區,在緩衝區內寫入一些內容,接下來,須要考慮的一個問題是,緩衝區的內容該如何保存到文件裏。保存方式天然有兩種,一種是保存到與當前緩衝區關聯的文件,另外一種是保存到其餘文件。

save-buffer 可將當前緩衝區保存到與之關聯的文件裏。例如

(find-file "foo.txt")
(goto-char (point-max))
(insert "|||")
(save-buffer)

運行上述程序後,可打開 foo.txt 文件查看其內容,是否在 Hello world! 以後多了 |||

write-file 可將當前緩衝區保存到其餘文件。例如

(find-file "foo.txt")
(goto-char (point-max))
(insert "|||")
(write-file "bar.txt")

結語

本章內容雖然較爲簡單,可是已經隱約觸及了 Emacs 的一些本質。假若理解並熟悉了本文出現的全部 Elisp 已經提供的函數的用法,至關於掌握了 Emacs 最樸素的功能,即打開一份文件,添加一些內容,刪除一些內容,而後保存,並不須要一個圖形界面幫助咱們完成這些事。

文中所出現的函數,除 princ\' 以外,我將其餘全部函數說成 Elisp 提供的,甚至一度想將它們稱爲 Elisp 標準庫裏的函數。但事實上,Elisp 只是一門語言,並且也不存在這個標準庫。這些函數來自於 Emacs 的核心功能——數量龐大的函數集,分散於衆多 Elisp 程序。我將這些函數統稱爲 Elisp 函數。

在 Emacs 裏執行默認的鍵綁定 C-h f,而後輸入某個函數名,回車,Emacs 便會打開該函數的文檔。在文檔裏,函數的用途、參數以及返回結果皆有詳細的說明。一開始,看不懂,也不大要緊,關鍵是要去看。

相關文章
相關標籤/搜索