在VIM中对单词(而非行)进行排序

42

内置的 VIM :sort 命令可对文本行进行排序。我想要对单行文字进行排序,例如将该行转换为:

b a d c e f

a b c d e f

目前我通过选择文本行,然后使用:!tr ' ' '\n' | sort | tr '\n' ' '来实现,但我相信有更好、更简单、更快的方法。是否有更好的方法?

请注意,我的操作系统是bash,所以如果有更短、更优雅的bash命令可用,那也可以。

编辑: 我的用例是我有一行代码:SOME_VARIABLE="one two three four etc",我希望对该变量中的单词进行排序,即我想得到 SOME_VARIABLE="etc four one three two"

最好的结果是可以映射到快捷键,因为我经常需要这样做。


4
这是我认为疾病比治疗更好的情况之一。特别地,我认为你的“tr”配方比这里提供的其他答案更好。 - user67416
1
另一种选择是 !tr ' ' \\n | sort | xargs - CervEd
6个回答

28

在纯vim中,你可以这样做:

call setline('.', join(sort(split(getline('.'), ' ')), " "))

编辑

如果要实现在小于一行范围内工作的排序,需要进行一些更复杂的操作(这允许对单独的多行进行排序或对部分单行进行排序,具体取决于视觉选择):

command! -nargs=0 -range SortWords call SortWords()
" Add a mapping, go to your string, then press vi",s
" vi" selects everything inside the quotation
" ,s calls the sorting algorithm
vmap ,s :SortWords<CR>
" Normal mode one: ,s to select the string and sort it
nmap ,s vi",s
function! SortWords()
    " Get the visual mark points
    let StartPosition = getpos("'<")
    let EndPosition = getpos("'>")

    if StartPosition[0] != EndPosition[0]
        echoerr "Range spans multiple buffers"
    elseif StartPosition[1] != EndPosition[1]
        " This is a multiple line range, probably easiest to work line wise

        " This could be made a lot more complicated and sort the whole
        " lot, but that would require thoughts on how many
        " words/characters on each line, so that can be an exercise for
        " the reader!
        for LineNum in range(StartPosition[1], EndPosition[1])
            call setline(LineNum, join(sort(split(getline('.'), ' ')), " "))
        endfor
    else
        " Single line range, sort words
        let CurrentLine = getline(StartPosition[1])

        " Split the line into the prefix, the selected bit and the suffix

        " The start bit
        if StartPosition[2] > 1
            let StartOfLine = CurrentLine[:StartPosition[2]-2]
        else
            let StartOfLine = ""
        endif
        " The end bit
        if EndPosition[2] < len(CurrentLine)
            let EndOfLine = CurrentLine[EndPosition[2]:]
        else
            let EndOfLine = ""
        endif
        " The middle bit
        let BitToSort = CurrentLine[StartPosition[2]-1:EndPosition[2]-1]

        " Move spaces at the start of the section to variable StartOfLine
        while BitToSort[0] == ' '
            let BitToSort = BitToSort[1:]
            let StartOfLine .= ' '
        endwhile
        " Move spaces at the end of the section to variable EndOfLine
        while BitToSort[len(BitToSort)-1] == ' '
            let BitToSort = BitToSort[:len(BitToSort)-2]
            let EndOfLine = ' ' . EndOfLine
        endwhile

        " Sort the middle bit
        let Sorted = join(sort(split(BitToSort, ' ')), ' ')
        " Reform the line
        let NewLine = StartOfLine . Sorted . EndOfLine
        " Write it out
        call setline(StartPosition[1], NewLine)
    endif
endfunction

我该如何修改代码,让它能够对所选的文本中的单词进行排序,而不是整行? - drrlvn
@depesz - 当然可以,但它会对整行进行排序,我想知道如何对选定的文本进行排序。 - drrlvn
@spatz:对一行进行排序相当复杂,因为命令不能接受子行范围,但我已经添加了一个如何实现这个的示例。 - DrAl

21

在您的答案中获得了很棒的思路,特别是Al的回答,最终我想出了以下解决方案:

:vnoremap <F2> d:execute 'normal i' . join(sort(split(getreg('"'))), ' ')<CR>

这将把 F2 按钮在 visual 模式下映射为删除选定的文本、拆分、排序、连接然后重新插入。当选择跨越多行时,这将对所有行中的单词进行排序并输出一个已排序的行,我可以使用 gqq 快速修复它。

如果您有进一步改进的建议,我会很高兴听到。

非常感谢,我学到了很多 :)

编辑: 将'<C-R>"'更改为getreg('"')来处理包含字符'的文本。


+1 很好!有一个评论是,如果我有一行“abc def ghi”,并选择+排序“def ghi”,它似乎会删除第一个单词前面的空格,并将其附加到最后一个单词之后,而不是保留它。 - l0b0
1
我曾经遇到过同样的问题。将 'normal i' 更改为 'normal a' 即可解决。 - ElementalVoid
如果您使用此方法对逗号分隔的单词列表进行排序,它将无法处理重新分配逗号。 - Jefferson Hudson

14

这是纯vimscript的等效代码:

 :call setline('.',join(sort(split(getline('.'),' ')),' '))

虽然这种写法并没有更加简短或简单,但如果你经常需要使用它,可以将其跨越多行来运行:

 :%call setline('.',join(sort(split(getline('.'),' ')),' '))

或者创建一个命令

 :command -nargs=0 -range SortLine <line1>,<line2>call setline('.',join(sort(split(getline('.'),' ')),' '))

你可以与之一起使用

:SortLine
:'<,'>SortLine
:%SortLine

等等等等


1
正如@spatz在对@Al的回答评论中提到的那样,这将对整行进行排序,而不是行内的选择。 - technomalogical
@rampion 不错的解决方案。你能解释一下你的两个用例 :'<,'>SortLine 吗? - SebMa
1
@SebMa - 当使用 : 从可视模式切换到命令模式时,vim会自动填充命令行为 '<,'>。这是一个命令行范围的示例,通常用于指定一组行。在这种情况下,'< 表示可视选择中的第一行,而 '> 表示可视选择中的最后一行。范围也可以使用实际行号(例如 112,499)或使用其他特殊字符来指定,如文件的第一行使用 ^,最后一行使用 $。当给定一个范围时,call 在范围内的每一行上运行给定的表达式。SortLine也是如此。 - rampion

7
:!perl -ne '$,=" ";print sort split /\s+/'

不确定是否需要解释,但如果需要:

perl -ne ''

对于输入的每一行,运行''中的任何内容 - 将该行放入默认变量$_中。

$,=" ";

将列表输出分隔符设置为空格。例如:

=> perl -e 'print 1,2,3'
123

=> perl -e '$,=" ";print 1,2,3'
1 2 3

=> perl -e '$,=", ";print 1,2,3'
1, 2, 3

相当简单。
print sort split /\s+/

是缩写的版本:

print( sort( split( /\s+/, $_ ) ) )

split - 使用给定的正则表达式将 $_ 分割成数组,sort 对给定列表进行排序,print - 将其打印出来。


我几乎不能说一个复杂的Perl脚本是一种改进... - mikl
2
我喜欢它。简单而优雅。 - Diego Pino

7

也许你更喜欢Python:

!python -c "import sys; print(' '.join(sorted(sys.stdin.read().split())))"

选中文本,执行此行。


对于 Vim 中的某些语言集成仍然很陌生,但是这个使用编译内置的 Python 支持还是使用外部可执行文件呢? - technomalogical
澄清一下,它不使用vim中编译的Python支持。它使用外部Python二进制文件作为shell命令。 - Lucas Oman
啊,抱歉。我不知道我们在谈论这个一行代码。我以为我们在谈论 Vim 的一般情况。 - iElectric

3

我的AdvancedSorters插件现在有一个:SortWORDs命令,可以执行此操作(以及其他与排序相关的命令)。


非常好用!感谢这个插件! - abu_bua

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