33.15 文本排序

本节介绍的排序函数均会对缓冲区中的文本重新排列。这与函数 sort 不同,后者用于重新排列列表元素的顺序(see 重排列表的函数)。这些函数的返回值无实际意义。

Function: sort-subr reverse nextrecfun endrecfun &optional startkeyfun endkeyfun predicate

该函数是通用的文本排序例程,可将缓冲区划分为多条记录并对其排序。本节中的多数命令均使用该函数。

要理解 sort-subr 的工作方式,可将缓冲区的整个可访问区域视为被划分为互不重叠的片段,称为排序记录(sort records)。这些记录可以连续也可以不连续,但不能相互重叠。每条排序记录的一部分(也可以是全部)会被指定为排序键。排序操作会根据排序键重新排列记录。

通常,记录会按排序键升序排列。 若 sort-subr 的第一个参数 reversenil,则排序记录会按排序键降序排列。

sort-subr 接下来的四个参数均为函数,用于在排序记录间移动光标。它们会在 sort-subr 内部被多次调用。

  1. 调用 nextrecfun 时光标位于一条记录末尾。该函数会将光标移至下一条记录开头。第一条记录默认从调用 sort-subr 时光标所在位置开始。因此,通常应在调用 sort-subr 前将光标移至缓冲区开头。

    该函数可通过将光标留在缓冲区末尾来表示已无更多排序记录。

  2. 调用 endrecfun 时光标位于某条记录内部。该函数会将光标移至该记录末尾。
  3. 调用 startkeyfun 可将光标从记录开头移至排序键开头。该参数为可选;若省略,则整条记录即为排序键。若提供该函数,其应返回一个非 nil 值作为排序键,或返回 nil 表示排序键位于缓冲区中光标起始位置。在后一种情况下,会调用 endkeyfun 确定排序键末尾。
  4. 调用 endkeyfun 可将光标从排序键开头移至排序键末尾。该参数为可选。若 startkeyfun 返回 nil 且该参数被省略(或为 nil),则排序键延伸至记录末尾。若 startkeyfun 返回非 nil 值,则无需使用 endkeyfun

参数 predicate 是用于比较键的函数。 它接收两个待比较的键作为参数,若第一个键在排序中应位于第二个键之前,则返回非 nil。 键参数的具体形式取决于 startkeyfunendkeyfun 的返回值。 若 predicate 被省略或为 nil,则默认规则为:键为数字时使用 <,键为序对(其 carcdr 为键在缓冲区中的起止位置)时使用 compare-buffer-substrings,其余情况(键视为字符串)使用 string<

以下是 sort-lines 的完整函数定义,作为 sort-subr 的示例:

;; 注意文档字符串的前两行在用户看来实际为一行
(defun sort-lines (reverse beg end)
  "Sort lines in region alphabetically;\
 argument means descending order.
Called from a program, there are three arguments:
REVERSE (non-nil means reverse order),\
 BEG and END (region to sort).
The variable `sort-fold-case' determines\
 whether alphabetic case affects
the sort order."
  (interactive "P\nr")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (let ((inhibit-field-text-motion t))
        (sort-subr reverse 'forward-line 'end-of-line)))))

此处 forward-line 将光标移至下一条记录开头,end-of-line 将光标移至记录末尾。我们未传入参数 startkeyfunendkeyfun,因为整条记录均作为排序键。

sort-paragraphs 函数与之非常相似,仅其 sort-subr 调用形式如下:

(sort-subr reverse
           (lambda ()
             (while (and (not (eobp))
                         (looking-at paragraph-separate))
               (forward-line 1)))
           'forward-paragraph)

sort-subr 返回后,任何指向排序记录内部的标记位置均不再有效。

User Option: sort-fold-case

若该变量非 nilsort-subr 及其他缓冲区排序函数在比较字符串时会忽略大小写。

Command: sort-regexp-fields reverse record-regexp key-regexp start end

该命令根据 record-regexpkey-regexp 的指定,对 startend 之间的区域按字母顺序排序。若 reverse 为负整数,则按逆序排序。

字母排序意味着两条排序键的比较方式为依次对比各自的第一个字符、第二个字符,依此类推。若发现不匹配,则表示排序键不相等;首次不匹配位置字符更小的排序键为更小的键。单个字符根据其在Emacs字符集中的数值编码进行比较。

参数 record-regexp 的值指定如何将缓冲区划分为排序记录。在每条记录末尾,会搜索该正则表达式,匹配到的文本即为下一条记录。例如,正则表达式 ‘^.+$’ 匹配除换行符外至少包含一个字符的行,可将此类行作为排序记录。有关正则表达式的语法与含义说明,参见See 正则表达式

参数 key-regexp 的值指定每条记录中哪一部分为排序键。key-regexp 可以匹配整条记录,也可以仅匹配部分。在后一种情况下,记录的其余部分不影响排序顺序,但会随记录一同移动到新位置。

参数 key-regexp 可以引用 record-regexp 中子表达式匹配的文本,也可以是独立的正则表达式。

key-regexp 为:

\digit

record-regexp 中第 digit 个 ‘\(...\)’ 括号分组匹配的文本为排序键。

\&

则整条记录为排序键。

正则表达式

sort-regexp-fields 会在记录内部搜索该正则表达式的匹配项。若找到匹配项,则该匹配项即为排序键。若某条记录内无 key-regexp 匹配,则该记录会被忽略,即其在缓冲区中的位置不会改变。(其他记录可在其周围移动。)

例如,若要按每行以字母 ‘f’ 开头的第一个单词对区域内所有行排序,应将 record-regexp 设为 ‘^.*$’,key-regexp 设为 ‘\<f\w*\>’。对应的表达式如下:

(sort-regexp-fields nil "^.*$" "\\<f\\w*\\>"
                    (region-beginning)
                    (region-end))

以交互方式调用 sort-regexp-fields 时,会在迷你缓冲区中提示输入 record-regexpkey-regexp

Command: sort-lines reverse start end

该命令对 startend 之间区域内的行按字母顺序排序。若 reversenil,则按逆序排序。

Command: sort-paragraphs reverse start end

该命令对 startend 之间区域内的段落按字母顺序排序。若 reversenil,则按逆序排序。

Command: sort-pages reverse start end

该命令对 startend 之间区域内的页面按字母顺序排序。若 reversenil,则按逆序排序。

Command: sort-fields field start end

该命令对 startend 之间区域内的行,按每行第 field 个字段进行字母顺序比较排序。字段以空白符分隔,编号从1开始。若 field 为负数,则按行末尾倒数第 −fieldth 个字段排序。该命令适用于表格排序。

Command: sort-numeric-fields field start end

该命令对 startend 之间区域内的行,按每行第 field 个字段进行数值比较排序。字段以空白符分隔,编号从1开始。指定字段在区域每行中均应为数字。以0开头的数字视为八进制,以 ‘0x’ 开头的数字视为十六进制。

field 为负数,则按行末尾倒数第 −fieldth 个字段排序。该命令适用于表格排序。

User Option: sort-numeric-base

该变量指定 sort-numeric-fields 解析数字时使用的默认基数。

Command: sort-columns reverse &optional beg end

该命令对 begend 之间区域内的行,按指定列范围进行字母顺序比较排序。begend 所在的列位置限定了排序所用的列范围。

reversenil,则按逆序排序。

该命令的一个特殊之处在于,包含 beg 位置的整行与包含 end 位置的整行均会被纳入排序区域。

注意 sort-columns 不处理包含制表符的文本,因为制表符可能跨指定列分割。排序前可使用 M-x untabify 将制表符转换为空格。

在条件允许时,该命令实际通过调用系统 sort 工具程序实现。