使用 linux 命令对第二列进行排序

Sort second column using linux command

是否可以水平排序文本? 例如我有这个 hunspell 文件,其中包含所有英文单词后跟标签。 (它可能包含 unicode 文本和数百万个单词)

test/BACac
this/QPR
line/MNP
again/Xx

我需要对标签进行排序(最好是:先小写再大写) 预期:

test/acABC
this/PQR
line/MNP
again/xX

我可以在 pandas 中做到这一点。但我想知道我是否可以仅使用 linux 个命令来完成任务!

import pandas as pd
df = pd.read_csv('test.csv', sep='/', header=None)
df.columns = ['word', 'tags']
df['tags']=df['tags'].map(lambda x: ''.join(sorted([i for i in x])))
df['final'] = df['word'] + '/' + df['tags'] 
df['final'].to_csv('result.csv', index=False, header=None)

这可能对你有用(GNU sed 和排序):

sed -E 's#/([[:upper:]]*)(.*)#/#' file | sort -ft/ -k2,2

交换第二个字段中的大小写字母,然后不管大小写对第二个字段中的结果进行排序。

如果大小写字母缠绕在一起,使用:

sed -E ':a;s#/([[:lower:]]*)([[:upper:]]+)([[:lower:]]+)#/#;ta' file |
sort -ft/ -k2,2

我误解了问题:

sed -E ':a;s#/([[:lower:]]*)([[:upper:]]+)([[:lower:]]+)#/#;ta' file |
sed -zE 's#/([[:lower:]]*)(.*)#/\n\n#mg' |
sed '2~3,+1s/.*/echo "&" | sed -z "s#\B#\n#g" | sort | sed -z "s#\n##g"/e' |
sed 'N;N;s/\n//g'

/ 后面的大写字母和小写字母分开,然后将小写字母放在前面。

将每一行分成 3 行记录,第一行是第一个字段,第二行和第三行分别是第二个字段的小写字母和大写字母。

对每第二行和第三行进行排序,将每一行的每个字母分成一行。然后对生成的行集进行排序,并将行内的行集重新构造回单行。

替代方案,也许更好?:

sed -zE 's/(.*\/)(.*)/\n/mg' file |
sed -E 'N;s/(.*)\n(.*)/echo ""|sed -z "s#\B#\n#g"|sort|sed -z "s#\n##g"|sed "s#^##"/e' |
sed -E ':a;s/\/([[:lower:]]*)([[:upper:]]+)([[:lower:]]+)/\//;ta'

当然,有一个实用程序可以执行其中的一些操作:

sed -zE 's/(.*\/)(.*)/\n/mg' file |
sed -E 'N;s/(.*)\n(.*)/echo ""|fold -b1|sort|tr -d "\n"|sed "s#^##"/e' |
sed -E ':a;s/\/([[:lower:]]*)([[:upper:]]+)([[:lower:]]+)/\//;ta

事实上,解决方案可以作为一个替换在一行中呈现:

 sed -E 's/^(.*\/)(.*)/echo ""|fold -b1|sort|tr -d "\n"|sed -E ":a;s#^([[:lower:]]*)([[:upper:]]+)([[:lower:]]+)#\1\3\2#;ta;s#^##"/e' file    

这在 awk 中有点尴尬。但有时最好的 awk 确实是 perl:

perl -F/ -lane 'printf "%s/%s\n", $F[0], join "", sort split //, $F[1];'

perl -F/ -lape '$_ = $F[0] . "/". join "", sort split //, $F[1];'

perl -lape 's@(?=/)(.*)@join "", sort split //, @e'

以上都是同一个原理,不过最后的解决方案还是需要说明一下的。 (?=/) 是一个否定的先行断言,因此表达式 (?=/)(.*) 匹配行中第一个 / 之后的所有文本,但不消耗 //之后的所有字符都放在第一个匹配组中,以便sort split对其进行操作。 split //, 将匹配组拆分为单独的字符,这些字符传递给 sort,然后通过连接重新加入,没有分隔符。 join/sort/split 的结果用作匹配模式的替换。

使用 GNU awk 处理“sorted_in”并在指定空分隔符时将字符串拆分为字符:

$ cat tst.awk
BEGIN {
    FS=OFS="/"
    PROCINFO["sorted_in"] = "@val_str_asc"
}
{
    split(,lets,"")
     = ""
    for (i in lets) {
         =  lets[i]
    }
    print
}

$ awk -f tst.awk file
test/ABCac
this/PQR
line/MNP
again/Xx

要获得小写字母排在大写字母之前的输出,您必须找到具有这种整理顺序的语言环境,并在 运行 脚本之前设置 LC_ALL=<that locale> 或将所有大写字母转换为首先是小写字母,反之亦然,然后进行排序,然后在打印之前将它们转换回来,或者通过在每个真实字符前面放置一个装饰字符来做类似的事情,例如所有小写字母都得到前导 A 而大写得到前导 a 再次强制执行不同的顺序,例如:

$ cat tst.awk
BEGIN {
    FS=OFS="/"
    PROCINFO["sorted_in"] = "@val_str_asc"
}
{
    split(,lets,"")

    for (i in lets) {
        lets[i] = ( lets[i] ~ /[[:lower:]]/ ? "A" : "a" ) lets[i]
    }

     = ""
    for (i in lets) {
         =  substr(lets[i],2)
    }    
    print
}

$ awk -f tst.awk file
test/acABC
this/PQR
line/MNP
again/xX

这是 perl 的替代解决方案,它首先给出小写字母:

$ perl -F'/' -lane '$s = join "", sort split //, $F[1];
                    print $F[0], "/", $s =~ s/^([A-Z]++)(.+)//r' ip.txt
test/acABC
this/PQR
line/MNP
again/xX

另一种选择:

$ perl -pe 's|.*/\K.+|join("", sort split //, $&) =~ s/^([A-Z]++)(.+)//r|e' ip.txt
test/acABC
this/PQR
line/MNP
again/xX

另一个 GNU sed 替代品:

parse.sed

# Save line to hold-space
h

# Remove word
s:.*/::

# New-line separate letters
s/./&\n/g
s/\n$//

# Quote new-line separated string
s/^|$/'/g

# Sort the letters and remove new-lines
s/^/echo /
s/$/ | sort/e
s/\n//g

# Move capital letters to the end (thanks @potong)
:a
s/([[:lower:]]*)([[:upper:]]+)([[:lower:]]+)//
ta

# Recombine word and tag
G
s:/.*::
s:([^\n]*)\n(.*):/:

运行 像这样:

sed -Ef parse.sed infile

输出:

test/acABC
this/PQR
line/MNP
again/xX