按行长度(包括空格)对文本文件进行排序

197

我有一个看起来像这样的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

我需要按照行长度(包括空格)对其进行排序。下面的命令不包括空格,有没有一种方法可以修改它使其适用于我的情况?

```bash awk '{ print length, $0 }' file.csv | sort -n -s | cut -d" " -f2- ```
cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

34
我很想住在二进制大道或三进制街道,那些人肯定会同意“8192 确实是一个整数”。 - schnaader
14个回答

298

答案

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行重建):
值得注意的是以下两种方法之间的区别:
echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

它们分别产生

hello   awk   world
hello awk world

gawk手册中相关章节仅作为旁白提到,当你改变一个字段时,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

3
heemayl,是的,谢谢。我尽可能地匹配了OP尝试解决方案的形状,以便他可以专注于他和我的重要差异。 - neillb
3
值得指出的是,cat $@也存在问题。您绝对需要将其加引号,如 cat "$@" - tripleee
1
awk可能是最常见和最简单的,但*nix系统中的Python等效方法是python -c "for line in open('/dev/stdin'): print(len(line), line, end='')":-) - Terry Brown
1
@TerryBrown 他们不想在输出中打印行长度,只是要对原始行进行排序。这两个命令都是跨平台的:python -c "import sys; [print(x) for x in sorted(sys.stdin.read().splitlines(), key=len)]"python -c "import sys;[sys.stdout.write(x) for x in sorted(sys.stdin, key=len)]"。第一个命令可以很好地处理混合换行符,而第二个命令需要更少的处理并保留换行符样式,但可能会导致文件有混合换行符的情况下产生混乱的结果。 - Mike Clark

57

如果您真正想使用awk并且要解释为什么会很麻烦,那么neillb的AWK解决方案非常不错,但是如果您只想快速完成工作而不在意使用什么语言,一个解决方案是使用Perl的sort()函数和自定义比较例程来迭代输入行。以下是一行代码:

perl -e 'print sort { length($a) <=> length($b) } <>'

你可以把这个放在管道里需要的任何地方,无论是接收标准输入(来自 cat 或 shell 重定向)还是只需将文件名作为另一个参数提供给 perl,并让它打开文件。

在我的情况下,我需要最长的行首先出现,因此我在比较中交换了 $a$b


3
这是更好的解决方案,因为当输入文件包含数字和字母混合行时,awk会导致意外排序。以下是一行命令:$ cat testfile | perl -e 'print sort { length($a) <=> length($b) } <>' - alemol
3
快!当输出重定向到另一个文件时,<1秒内处理了包含465,000行内容(每行一个单词)的文件。因此,命令为:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt。请注意,该命令通过对单词长度进行排序来处理文件内容。 - cssyphus
2
Windows系统下使用StrawberryPerl可以正常工作:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt - bryc
这是计算机语言中的隐藏宝石之一。非常感谢! - riccs_0x

22

基准测试结果

以下是对此问题其他答案提供的解决方案进行基准测试的结果。

测试方法

  • 在一台快速计算机上连续运行10次,并取平均值
  • 使用Perl 5.24
  • 使用awk 3.1.5 (gawk 4.1.0的速度约快2%)
  • 输入文件为550MB,600万行的数据文件(英国国家语料库txt)

测试结果

  1. Caleb的perl解决方案耗时11.2秒
  2. 我的perl解决方案耗时11.6秒
  3. neillb的awk解决方案#1耗时20秒
  4. neillb的awk解决方案#2耗时23秒
  5. anubhava的awk解决方案耗时24秒
  6. Jonathan的awk解决方案耗时25秒
  7. Fritz的bash解决方案awk解决方案相比需要的时间长了400倍(使用100000行截断测试用例)。虽然可以正常工作,但需要很长时间。

另一个perl解决方案

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file

1
你列出的解决方案中有多少可以处理Unicode? - jubilatious1
2
我不知道,但如果你尝试了,请告诉我们。 - Chris Koknat

17

试试这个命令:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-

7

Python解决方案

这是一个Python一行代码的解决方案,已经在Python 3.9.10和2.7.18测试。它比Caleb的perl解决方案快了大约60%,并且输出是相同的(使用包含1480万行单词列表文件的300MiB进行测试)。

python -c 'import sys; sys.stdout.writelines(sorted(sys.stdin.readlines(), key=len))'

基准测试:

python -c 'import sys; sys.stdout.writelines(sorted(sys.stdin.readlines(), key=len))'
real    0m5.308s
user    0m3.733s
sys     0m1.490s

perl -e 'print sort { length($a) <=> length($b) } <>'
real    0m8.840s
user    0m7.117s
sys     0m2.279s

7

纯Bash:

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

5
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]* //'

3

使用POSIX Awk:

{
  c = length
  m[c] = m[c] ? m[c] RS $0 : $0
} END {
  for (c in m) print m[c]
}

示例


3

1) 纯awk解决方案。假设行的长度不超过1024,则:

cat filename | awk 'BEGIN {min = 1024; s = "";} {l = length($0); if (l < min) {min = l; s = $0;}} END {print s}'

2) 单行bash解决方案,假设所有行只有一个单词,但也可以为任何所有行具有相同单词数的情况重新设计:

LINES=$(cat filename); for k in $LINES; do printf "$k "; echo $k | wc -L; done | sort -k2 | head -n 1 | cut -d " " -f1


3

如果你的文件中包含以数字开头的行,那么这些解决方案将无法起作用,因为它们将与所有计算行一起按数字排序。解决方法是给 sort 命令使用 -g(通用数值排序)标志,而不是 -n(数值排序):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

4
嗨,马库斯。除了行长度以外,我认为不论是数字还是非数字的行内容对排序都没有任何影响,除非这些行的长度匹配。您是这个意思吗?在这种情况下,我发现从“-n”切换到您建议的“-g”并没有产生任何改进,所以我不抱有期望。现在,在我的答案中,我已经解决了如何禁止相等长度的行进行子排序(使用“--stable”)。无论您是否是这个意思,感谢您提醒我!我还添加了一个经过深思熟虑的输入来测试。 - neillb
5
不,让我逐步解释一下。仅使用“awk”部分将生成一个以行长度和空格为前缀的行列表。将其传输到“sort -n”将按预期工作。但如果其中任何一行已经以数字开头,则这些行将以长度+空格+数字开头。 “sort -n”忽略了那个空格,并将其视为从长度 + 数字连接而成的一个数字。使用“-g”标志将停在第一个空格处,得出正确的排序。通过创建一些以数字为前缀的行的文件并逐步运行命令来尝试自己。 - Markus Amalthea Magnuson
2
我还发现sort -n忽略了空格并产生了错误的排序。sort -g输出了正确的顺序。 - r_31415
1
我无法在“sort(GNU coreutils)8.21”中重现使用“-n”的问题。 “info”文档将“-g”描述为效率较低且可能不太精确(它将数字转换为浮点数),因此如果您不需要,最好不要使用它。 - phils
2
注意:-n的文档说明:“按数字排序。每行的数字由可选空格、可选的‘-’符号和零个或多个数字(可能由千位分隔符分隔),可选地后跟小数点字符和零个或多个数字组成。空数字被视为‘0’。‘LC_NUMERIC’语言环境指定小数点字符和千位分隔符。默认情况下,空格或制表符为空格,但‘LC_CTYPE’语言环境可以更改此设置。” - phils
1
也许尝试使用“LC_ALL=C sort -n” - phils

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