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

我有个 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}'
114012 次浏览

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

回答我

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

或者,对任何等长的行进行原始的(可能是无意的)子排序:

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

在这两种情况下,我们已经解决了您的陈述的问题,从奥克为您的最终削减。

匹配长度的行——在打平的情况下应该怎么做:

问题没有指定是否需要对匹配长度的行进行进一步排序。我已经假设这是不需要的,并建议使用 -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

手册的相关部分只是作为旁白提到,awk 将在您更改一个字段时重新构建整个 $0(基于分隔符等)。我想这不是疯狂的行为。它有这个:

”最后,有时可以方便地强制 awk 使用字段和 OFS 的当前值重新生成整个记录。要做到这一点,可以使用看似无害的作业:

 $1 = $1   # force record to be reconstituted
print $0  # or whatever else with $0

“这迫使我们重建记录。”

测试输入包括一些等长的行:

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

试试这个命令:

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

纯粹的狂欢:

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

我发现,如果文件中的行以数字开头,那么这些解决方案将不起作用,因为它们将与所有计数行一起进行数字排序。解决方案是为 sort提供 -g(general-numeric-sort)标志,而不是 -n(numeric-sort)标志:

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

如果你真的想使用 awk,那么 来自 Neillb 的 AWK 解决方案非常棒,它解释了为什么它很麻烦,但是如果你想快速地完成工作,而不在乎你用什么方法完成,一个解决方案是使用 Perl 的 sort()函数和一个定制的 caparison 例程来遍历输入行。下面是一句话:

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

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

在我的例子中,我首先需要最长的线,所以我在比较中交换了 $a$b

POSIX Awk:

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

例子

下面是一个多字节兼容的按长度排序行的方法,它要求:

  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是八进制符号中的单引号)。
  • 这是我们要执行的命令,它将转义行回显到 wc -m
  • cmd | getline c; ∞执行命令并将返回的字符计数值复制到 awk 变量 c中。
  • close(cmd); ∞关闭管道到 shell 命令,以避免一个进程中打开的文件数量达到系统限制。
  • wc返回的字符计数值中删除空白。
  • { print c, $0 } ∞打印行的字符计数值、空格和原始行。
  • | sort -ns ∞对行进行数字排序(通过预置的字符计数值)(-n) ,并保持稳定的排序顺序(-s)。
  • | cut -d" " -f2- ∞删除预先添加的字符计数值。

它很慢(在快速的 MacbookPro 上每秒只有160行) ,因为它必须为每一行执行一个子命令。

或者,仅仅使用 gawk(在3.1.5版本中,gawk 具有多字节感知能力)完成此操作,这样会快得多。通过转义和双引号来安全地从 awk 通过 shell 命令传递代码行是很麻烦的,但是这是我能找到的唯一不需要安装额外软件的方法(gawk 在 MacOS 上默认不可用)。

基准结果

下面是这个问题的其他解决方案的基准测试结果。

测试方法

  • 在一台快速机器上连续运行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. 阿努巴瓦的 awk溶液花了24秒
  6. 乔纳森的 awk解决方案花了25秒
  7. Fritz 的 bash解决方案 awk解决方案花费的时间长400倍(使用100000行的截断测试用例)。效果很好,只是需要很长时间。

另一个 perl解决方案

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

1)纯 awk 解,假设行长不能超过1024 那么

Cat 文件名 | awk‘ BEGIN { min = 1024; s = “”; }{ l = length ($0) ; if (l < min){ min = l; s = $0; }}} END { print s }’

2)一个 linerbash 解决方案,假设所有行只有一个单词,但是可以在所有行都有相同数量单词的情况下重写:

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

使用 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

下面是 awk 和 Raku 使用 Genbank 9.1 MB txt 文件的时间比较:

~$ 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

高温。

Https://raku.org

重新审视这个案子。我是这样处理它的(计算 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

Python 解决方案

下面是一个 Python 一行程序,它也是这样做的,用 Python 3.9.10和2.7.18进行了测试。它比 Caleb 的 perl 解决方案快约60% ,而且输出完全相同(使用一个300MiB 的字表文件进行测试,该文件有1480万行)。

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