如何在Emacs中删除重复的行

34

我有一段文字有很多行,我的问题是如何在emacs中删除重复的行?使用emacs或elisp包中的命令而不是外部工具。

例如:

this is line a
this is line b
this is line a

删除第三行(与第一行相同)

this is line a
this is line b
5个回答

55

如果您使用的是 Emacs 24.4 或更新版本,则最清晰的方法是使用新的 delete-duplicate-lines 函数。请注意:

  • 此操作适用于区域而非缓冲区,因此请先选择所需文本
  • 它会保留原始行的相对顺序并删除重复行

例如,如果您的输入为:

test
dup
dup
one
two
one
three
one
test
five

M-x delete-duplicate-lines会使其变得更简洁。

test
dup
one
two
three
five

如果在命令前加上通用参数 (C-u),则可以选择从后向前搜索。搜索结果将如下所示。

dup
two
three
one
test
five

感谢emacsredux.com提供的帮助。

其他绕远路的选项,虽然结果不完全相同,但可以通过Eshell获得:

  1. sort -u;不能保持原始行的相对顺序
  2. uniq;更糟糕的是需要对输入进行排序

"sort -u" 可能不是一个稳定的排序,但 "sort -u -s" 是。 - Squidly
是的,没错。现在已经修复了!此外,从eshell运行它似乎不如使用内置功能更加简洁。 - legends2k
@Squid 我想我在没有正确验证你的评论之前就发表了最后一条评论。尝试将输入数据提供给sort -usort -us,您将获得相同的结果,这与delete-duplicate-lines的结果不同。更重要的是,我们不谈论稳定排序,这意味着相同元素的相对顺序被维护。由于我们正在删除重复项,因此相同的元素无论如何都会丢失。delete-duplicate-lines保留原始内容的顺序而不是重复项;因此,使用sort无法获得相同的结果。 - legends2k
2
似乎 delete-duplicate-lines 现在也可以在缓冲区内工作,因此无需先选择一个区域(对于整个缓冲区使用 C-x h)。至少在 Emacs 26.2 中是这样。 - Ocaso Protal

19
将以下代码添加到您的 .emacs 文件中:
(defun uniq-lines (beg end)
  "Unique lines in region.
Called from a program, there are two arguments:
BEG and END (region to sort)."
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (while (not (eobp))
        (kill-line 1)
        (yank)
        (let ((next-line (point)))
          (while
              (re-search-forward
               (format "^%s" (regexp-quote (car kill-ring))) nil t)
            (replace-match "" nil nil))
          (goto-char next-line))))))

使用方法:

M-x uniq-lines

2
你可以使用一个let绑定变量来保存内容,而不是使用kill-ring。 - event_jr
2
为什么要使用kill-line函数?这个函数会使整个kill-ring变得很多无用的项。 - kuanyui

13
在 Linux 中,选择区域,然后输入。
M-| uniq <RETURN>

没有重复项的结果在新缓冲区中。


6
在需要翻译的内容前面加上“C-u”,它将用你的 shell 命令的结果替换你的区域。 - Santino

2
(defun unique-lines (start end)
  "This will remove all duplicating lines in the region.
Note empty lines count as duplicates of the empy line! All empy lines are 
removed sans the first one, which may be confusing!"
  (interactive "r")
  (let ((hash (make-hash-table :test #'equal)) (i -1))
    (dolist (s (split-string (buffer-substring-no-properties start end) "$" t)
               (let ((lines (make-vector (1+ i) nil)))
                 (maphash 
                  (lambda (key value) (setf (aref lines value) key))
                  hash)
                 (kill-region start end)
                 (insert (mapconcat #'identity lines "\n"))))
      (setq s                           ; because Emacs can't properly
                                        ; split lines :/
            (substring 
             s (position-if
                (lambda (x)
                  (not (or (char-equal ?\n x) (char-equal ?\r x)))) s)))
      (unless (gethash s hash)
        (setf (gethash s hash) (incf i))))))

另一种选择:

  • 不使用撤消历史记录来存储匹配。
  • 通常更快(但如果您追求终极速度-构建前缀树)。
  • 具有将所有以前的换行符替换为 \n(类UNIX风格)的效果。这可能是一个优点或劣势,取决于您的情况。
  • 如果您重新实现 split-string 以接受字符而不是正则表达式,则可以使其稍微好一些(更快)。

略长一些,但也许更有效率的变体:

(defun split-string-chars (string chars &optional omit-nulls)
  (let ((separators (make-hash-table))
        (last 0)
        current
        result)
    (dolist (c chars) (setf (gethash c separators) t))
    (dotimes (i (length string)
                (progn
                 (when (< last i)
                   (push (substring string last i) result))
                 (reverse result)))
      (setq current (aref string i))
      (when (gethash current separators)
        (when (or (and (not omit-nulls) (= (1+ last) i))
                  (/= last i))
          (push (substring string last i) result))
        (setq last (1+ i))))))

(defun unique-lines (start end)
  "This will remove all duplicating lines in the region.
Note empty lines count as duplicates of the empy line! All empy lines are 
removed sans the first one, which may be confusing!"
  (interactive "r")
  (let ((hash (make-hash-table :test #'equal)) (i -1))
    (dolist (s (split-string-chars
                (buffer-substring-no-properties start end) '(?\n) t)
               (let ((lines (make-vector (1+ i) nil)))
                 (maphash 
                  (lambda (key value) (setf (aref lines value) key))
                  hash)
                 (kill-region start end)
                 (insert (mapconcat #'identity lines "\n"))))
      (unless (gethash s hash)
        (setf (gethash s hash) (incf i))))))

1
在Emacs缓冲区中,行始终由\n分隔(不管相应文件使用的是什么分隔符)。\r的使用仅用于旧的selective-display,该功能已经在多年前被叠加层和文本属性的“invisible”属性所取代。 - Stefan

2

另一种方法:

  1. 选择需要操作的文本区域。
  2. 按下Ctrl-U键(前缀),M-|键(shell-command-on-region),输入sort -u命令(对所选内容进行操作,然后用输出替换所选内容)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接