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


137

我有一个看起来像这样的CSV文件

AS2345,ASDF1232,Plain Example先生,RI Bintan ave.110,Atlantis,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232,Plain Example夫人,1121110 Ternary st。110 Binary ave ..,Atlantis,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232,Mr.Plain Example,RI Binary Ave.110,Liberty City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232,Mr。Plain Example,RI,Some City,Ternary ave.110,12345,(999)123-5555,1.56

我需要按行长度(包括空格)对其进行排序。以下命令不包含空格,有没有办法对其进行修改,以便它对我有用?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
我真的很想住在Binary Avenue或Ternary Street,这些人当然会同意“ 8192 一个整数”之类的东西
schnaader 2011年

Answers:


224

回答

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(基于分隔符等)。我想这不是疯狂的行为。它具有:

“最后,有时候使用字段和OFS的当前值强制awk重建整个记录很方便。为此,请使用看似无害的赋值:”

 $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

1
heemayl,是的,谢谢。我试图尽可能匹配OP尝试的解决方案的形状,以使他能够专注于自己与我之间的重要区别。
neillb

1
值得指出的cat $@是,它也是坏的。您绝对想引用它,例如cat "$@"
三元组'17

27

如果您真的想使用neillbAWK解决方案,那就很好了awk,它解释了那里的麻烦所在,但是如果您想要的是快速完成工作并且不在乎您做什么,那么一种解决方案是使用Perl的sort()功能带有自定义的Caparison例程,可以遍历输入行。这是一个班轮:

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

您可以将其放在您需要的任何位置的管道中,从STDIN接收(来自cat或Shell重定向),或仅将文件名提供给perl作为另一个参数,然后让它打开文件。

在我的情况下,我首先需要最长的行,因此我将其换出$a$b进行比较。


这是更好的解决方案,因为当输入文件包含数字和字母数字行时,awk会导致意外排序,这是oneline命令:$ cat testfile | perl
-e'print

快速!当输出重定向到另一个文件时,是否在<1秒内完成了465,000行文件(每行一个字)-因此:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus,

Windows与StrawberryPerl的作品:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
布莱克

14

请尝试以下命令:

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

10

基准结果

以下是针对该问题的其他答案的各种解决方案基准测试的结果。

测试方法

  • 快速机器上连续运行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. 乔纳森的awk解决方案花了25秒
  7. Fretz的bash解决方案所花的时间比awk解决方案长400 (使用截断的100000行测试用例)。它工作正常,只是需要永远。

额外的perl选择

另外,我添加了另一个Perl解决方案:

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

6

纯重击:

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

3

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

2

我发现如果您的文件包含以数字开头的行,这些解决方案将不起作用,因为它们将与所有计数行一起进行数字排序。该解决方案是给sort所述-g(通用数字排序)标志,而不是-n(数字排序):

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

2
嗨,马库斯 与行长相反,我没有观察到行内容(数字或不行)对排序没有任何影响,除非行长匹配。这是你的意思吗?在这种情况下,我没有发现将排序方法从切换-n到您的建议-g可以产生任何改善,所以我希望不会。现在,我已经回答了如何禁止等长线的子排序(使用--stable)。无论您的意思是什么,谢谢您引起我的注意!我还添加了一个经过测试的输入。
neillb

4
不,让我解释一下。仅awk零件将生成以线长和空格为前缀的线列表。用管道sort -n将其按预期方式工作。但是,如果其中任何一行的开头已经有数字,则这些行将以长度+空格+数字开头。sort -n忽略该空间,并将其视为由长度+数字连接而成的一个数字。-g相反,使用该标志会停在第一个空格,从而产生正确的排序。自己尝试创建带有一些带数字前缀的行的文件,然后逐步运行命令。
Markus Amalthea Magnuson

1
我还发现sort -n忽略空间并产生不正确的排序。sort -g输出正确的顺序。
罗伯·史密斯

我不能重现所描述的问题-nsort (GNU coreutils) 8.21。该info文档描述-g为效率较低且精度可能较低(它将数字转换为浮点数),因此,如果不需要,可以不要使用它。
菲尔

nb文档-n:“按数字排序。数字以每行开头,由可选的空格,可选的'-'号和可能由数千个分隔符分隔的零个或多个数字,可选地后跟小数点字符和零个或多个数字组成一个空数字被视为“ 0”。“ LC_NUMERIC”语言环境指定小数点字符和千位分隔符。默认情况下,空格或制表符为空格,但“ LC_CTYPE”语言环境可以更改此设置。
菲尔


2

1)纯awk溶液。假设线长不能大于1024

猫文件名| awk'BEGIN {min = 1024; s =“”;} {l = length($ 0); 如果(l <min){min = l; s = $ 0;}} END {print s}'

2)假设所有行只有1个单词的一种内衬bash解决方案,但是对于所有行都具有相同单词数的任何情况都可以重做:

LINES = $(猫文件名); $ LINES中的k;做printf“ $ k”; 回声$ k | wc -L; 完成| 排序-k2 | 头-n 1 | 剪切-d“” -f1


1

这是一种按字节排序的多字节兼容方法。这个需要:

  1. wc -m 可供您使用(macOS拥有)。
  2. 您当前的语言环境支持多字节字符,例如,通过设置LC_ALL=UTF-8。您可以在.bash_profile中设置它,也可以在以下命令之前添加它。
  3. 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不可用)苹果系统)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.