按行长对文本文件进行排序,包括空格
Posted
技术标签:
【中文标题】按行长对文本文件进行排序,包括空格【英文标题】:Sort a text file by line length including spaces 【发布时间】:2011-08-20 12:40:45 【问题描述】:我有一个如下所示的 CSV 文件
AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Atlantis,RI,12345,(999)123-5555,1.56 AS2345,ASDF1232, Mrs. Plain Example, 1121110 Ternary st. 110 Binary ave..,Atlantis,RI,12345,(999)123-5555,1.56 AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Liberty City,RI,12345,(999)123-5555,1.56 AS2345,ASDF1232, Mr. Plain Example, 110 Ternary ave.,Some City,RI,12345,(999)123-5555,1.56我需要按行长(包括空格)对其进行排序。以下命令不 包括空格,有没有办法修改它以便对我有用?
cat $@ | awk ' print length, $0 ' | sort -n | awk '$1=""; print $0'
【问题讨论】:
我真的很想住在 Binary Avenue 或 Ternary Street,那些人肯定会同意“8192 是一个整数”之类的话 【参考方案1】:回答
cat testfile | awk ' print length, $0 ' | sort -n -s | cut -d" " -f2-
或者,对任何等长的行进行原始(可能是无意的)子排序:
cat testfile | awk ' print length, $0 ' | sort -n | cut -d" " -f2-
在这两种情况下,我们已经解决了您提出的问题,方法是从 awk 移出您的最终剪辑。
匹配长度的行 - 在平局的情况下怎么办:
问题没有说明是否需要对匹配长度的行进行进一步排序。我假设这是不需要的,并建议使用 -s
(--stable
) 来防止这些行相互排序,并保持它们在输入中出现的相对顺序。
(想要更好地控制对这些关系进行排序的人可以查看 sort 的 --key
选项。)
为什么该问题的尝试解决方案失败(awk line-rebuilding):
有趣的是注意以下之间的区别:
echo "hello awk world" | awk 'print'
echo "hello awk world" | awk '$1="hello"; print'
它们分别产生
hello awk world
hello awk world
relevant section of (gawk's) manual 仅作为旁白提到,当您更改一个字段时,awk 将重建整个 $0(基于分隔符等)。我想这不是疯狂的行为。它有这个:
“最后,有时可以方便地强制 awk 使用字段和 OFS 的当前值重建整个记录。为此,请使用看似无害的赋值:”
$1 = $1 # force record to be reconstituted
print $0 # or whatever else with $0
“这会强制 awk 重建记录。”
测试输入包括一些等长的行:
aa A line with MORE spaces
bb The very longest line in the file
ccb
9 dd equal len. Orig pos = 1
500 dd equal len. Orig pos = 2
ccz
cca
ee A line with some spaces
1 dd equal len. Orig pos = 3
ff
5 dd equal len. Orig pos = 4
g
【讨论】:
heemayl,是的,谢谢。我已尝试尽可能匹配 OP 尝试解决方案的形式,以使他能够只关注他和我之间的重要差异。 值得指出的是cat $@
也坏了。你绝对想引用它,比如cat "$@"
awk 可能无处不在且最简单,但 *nix 系统上的 Python 等价物是 python -c "for line in open('/dev/stdin'): print(len(line), line, end='')"
:-)【参考方案2】:
如果您真的想使用awk
,AWK solution from neillb 非常棒,它解释了为什么在那里很麻烦,但如果您想要快速完成工作而不关心您在做什么,一种解决方案是使用 Perl 的 sort()
函数和自定义 caparison 例程来迭代输入行。这是一个班轮:
perl -e 'print sort length($a) <=> length($b) <>'
你可以把它放在你需要的地方,要么接收 STDIN(来自cat
或 shell 重定向),要么只是将文件名作为另一个参数提供给 perl,然后让它打开文件。
在我的情况下,我首先需要最长的行,所以我在比较中换掉了 $a
和 $b
。
【讨论】:
这是更好的解决方案,因为当输入文件包含数字和字母数字行时,awk 会导致意外排序 这里的 oneline 命令: $ cat testfile | perl -e '打印排序 长度($a) 长度($b) ' 快!当输出重定向到另一个文件时,在 cat testfile.txt | perl -e 'print sort length($a) <=> length($b) <>' > out.txt 带有 StrawberryPerl 的 Windows 工作:type testfile.txt | perl -e "print sort length($a) <=> length($b) <>" > out.txt
【参考方案3】:
基准测试结果
以下是针对此问题的其他答案的解决方案的基准测试结果。
测试方法
在快速机器上连续运行 10 次,平均 Perl 5.24 awk 3.1.5(gawk 4.1.0 倍快约 2%) 输入文件是一个 550MB、600 万行的怪物(英国国家语料库 txt)结果
-
Caleb's
perl
solution 耗时 11.2 秒
my perl
solution 耗时 11.6 秒
neillb's awk
solution #1 耗时 20 秒
neillb's awk
solution #2 耗时 23 秒
anubhava's awk
solution 耗时 24 秒
Jonathan's awk
solution 耗时 25 秒
Fritz's bash
solution 比 awk
解决方案花费的时间长 400 倍(使用 100000 行的截断测试用例)。它工作正常,只是需要很长时间。
另一个perl
解决方案
perl -ne 'push @a, $_; END print sort length $a <=> length $b @a ' file
【讨论】:
您列出的解决方案中有多少可以处理 Unicode?span> 我不知道,但如果您尝试一下,请告诉我们【参考方案4】:试试这个命令:
awk 'print length, $0' your-file | sort -n | cut -d " " -f2-
【讨论】:
【参考方案5】:纯猛击:
declare -a sorted
while read line; do
if [ -z "$sorted[$#line]" ] ; then # does line length already exist?
sorted[$#line]="$line" # element for new length
else
sorted[$#line]="$sorted[$#line]\n$line" # append to lines with equal length
fi
done < data.csv
for key in $!sorted[*]; do # iterate over existing indices
echo -e "$sorted[$key]" # echo lines with equal length
done
【讨论】:
【参考方案6】:length()
函数确实包含空格。我会对你的管道做一些小的调整(包括避免UUOC)。
awk ' printf "%d:%s\n", length($0), $0;' "$@" | sort -n | sed 's/^[0-9]*://'
sed
命令直接去掉awk
命令添加的数字和冒号。或者,保留awk
的格式:
awk ' print length($0), $0;' "$@" | sort -n | sed 's/^[0-9]* //'
【讨论】:
【参考方案7】:我发现如果您的文件包含以数字开头的行,这些解决方案将不起作用,因为它们将与所有计数的行一起按数字排序。解决方案是给sort
-g
(general-numeric-sort)标志而不是-n
(numeric-sort):
awk ' print length, $0 ' lines.txt | sort -g | cut -d" " -f2-
【讨论】:
嗨,马库斯。我没有观察到行内容(数字或非数字) - 与行长度相反 - 对排序有任何影响,除非行长度匹配。这是你的意思吗?在这种情况下,我没有发现将排序方法从-n
切换到您建议的 -g
以产生任何改进,所以我预计不会。我现在已经在我的回答中解决了如何禁止对等长行进行子排序(使用--stable
)。不管这是否是您的意思,感谢您引起我的注意!我还添加了一个经过深思熟虑的输入来进行测试。
不,让我通过分解来解释。仅awk
部分将生成以行长和空格为前缀的行列表。将其连接到sort -n
将按预期工作。但是,如果其中任何一行的开头已经有一个数字,那么这些行将以长度 + 空格 + 数字开头。 sort -n
忽略该空间并将其视为由长度 + 数字连接的一个数字。使用-g
标志将改为在第一个空格处停止,从而产生正确的排序。通过创建一个带有一些以数字为前缀的行的文件并逐步运行命令来自己尝试。
我还发现sort -n
忽略了空格并产生了不正确的排序。 sort -g
输出正确的顺序。
我无法在sort (GNU coreutils) 8.21
中使用-n
重现所描述的问题。 info
文档将 -g
描述为效率较低且可能不太精确(它将数字转换为浮点数),因此如果不需要,可能不要使用它。
n.b. -n
的文档:“按数字排序。数字开始于每一行,由可选的空格、可选的 '-' 符号和零个或多个数字组成,可能由千位分隔符分隔,可选后跟一个小数点字符和零或“ 【参考方案8】:
使用 POSIX awk:
c = length
m[c] = m[c] ? m[c] RS $0 : $0
END
for (c in m) print m[c]
Example
【讨论】:
【参考方案9】:1) 纯 awk 解决方案。假设行长不能大于 > 1024 那么
cat 文件名 | awk '开始 分钟 = 1024; s = ""; l = 长度($0);如果 (l
2) 一种 liner bash 解决方案,假设所有行只有 1 个单词,但可以针对所有行具有相同单词数的任何情况进行修改:
LINES=$(cat 文件名);对于 $LINES 中的 k;做 printf "$k";回声 $k | wc -L;完成 |排序-k2 |头-n 1 |剪切 -d " " -f1
【讨论】:
【参考方案10】:使用 Raku(以前称为 Perl6)
~$ cat "BinaryAve.txt" | raku -e 'given lines() .sort(*.chars).join("\n").say;'
AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Atlantis,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mr. Plain Example, 110 Ternary ave.,Some City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Liberty City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mrs. Plain Example, 1121110 Ternary st. 110 Binary ave..,Atlantis,RI,12345,(999)123-5555,1.56
要反转排序,请在方法调用链的中间添加.reverse
——紧跟在.sort()
之后。以下代码显示.chars
包含空格:
~$ cat "number_triangle.txt" | raku -e 'given lines() .map(*.chars).say;'
(1 3 5 7 9 11 13 15 17 19 0)
~$ cat "number_triangle.txt"
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 0
以下是使用 Genbank 的 9.1MB txt 文件对 awk 和 Raku 进行的时间比较:
~$ time cat "rat_whole_genome.txt" | raku -e 'given lines() .sort(*.chars).join("\n").say;' > /dev/null
real 0m1.308s
user 0m1.213s
sys 0m0.173s
~$ #awk code from neillb
~$ time cat "rat_whole_genome.txt" | awk ' print length, $0 ' | sort -n -s | cut -d" " -f2- > /dev/null
real 0m1.189s
user 0m1.170s
sys 0m0.050s
HTH。
https://raku.org
【讨论】:
【参考方案11】:这是一种按长度对行进行排序的多字节兼容方法。它需要:
wc -m
可供您使用(macOS 有)。
您当前的语言环境支持多字节字符,例如,通过设置 LC_ALL=UTF-8
。您可以在 .bash_profile 中进行设置,也可以简单地将其添加到以下命令之前。
testfile
具有与您的语言环境匹配的字符编码(例如 UTF-8)。
这是完整的命令:
cat testfile | awk 'l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); print c, $0 ' | sort -ns | cut -d" " -f2-
部分解释:
l=$0; gsub(/\047/, "\047\"\047\"\047", l);
← 复制 awk 变量 l
中的每一行,并在每个 '
中进行两次转义,因此该行可以安全地作为 shell 命令回显(\047
是单个- 八进制引用)。
cmd=sprintf("echo \047%s\047 | wc -m", l);
← 这是我们将执行的命令,它将转义的行回显到 wc -m
。
cmd | getline c;
← 执行命令并将返回的字符计数值复制到 awk 变量c
。
close(cmd);
← 关闭 shell 命令的管道,以避免达到系统对一个进程中打开文件数的限制。
sub(/ */, "", c);
← 从wc
返回的字符计数值中修剪空白。
print c, $0
← 打印行的字符计数值、空格和原始行。
| sort -ns
← 按数字(-n
)对行进行排序(按前置字符计数值),并保持稳定的排序顺序(-s
)。
| cut -d" " -f2-
← 删除前置字符计数值。
它很慢(在快速的 Macbook Pro 上每秒只有 160 行),因为它必须为每一行执行一个子命令。
或者,只需使用gawk
来执行此操作(从版本 3.1.5 开始,gawk 支持多字节),这会明显更快。进行所有转义和双引号以安全地通过 awk 的 shell 命令传递行很麻烦,但这是我能找到的唯一不需要安装其他软件的方法(gawk 默认情况下不可用苹果系统)。
【讨论】:
【参考方案12】:重温这个。这就是我的处理方式(计算 LINE 的长度并将其存储为 LEN,按 LEN 排序,仅保留 LINE):
cat test.csv | while read LINE; do LEN=$(echo $LINE | wc -c); echo $LINE $LEN; done | sort -k 2n | cut -d ' ' -f 1
【讨论】:
以上是关于按行长对文本文件进行排序,包括空格的主要内容,如果未能解决你的问题,请参考以下文章