如何找到字符最少的行

Mat*_*eld 25 shell text-processing wc

我正在编写一个 shell 脚本,使用任何通用的 UNIX 命令。我必须检索字符最少的行(包括空格)。最多可以有大约 20 行。

我知道我可以用它head -$L | tail -1 | wc -m来查找 L 行的字符数。问题是,我能想到的唯一方法是手动编写一堆 if 语句,比较这些值。

示例数据:

seven/7
4for
8 eight?
five!
Run Code Online (Sandbox Code Playgroud)

将返回,4for因为该行的字符最少。

就我而言,如果多行的长度最短,则应返回一条。选择哪一个并不重要,只要它是最小长度即可。但我没有看到为其他情况下的其他用户显示两种方式的危害。

Jan*_*nis 19

这是awk打印第一个找到的最小行的解决方案的变体:

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'
Run Code Online (Sandbox Code Playgroud)

可以简单地扩展一个条件以打印所有最小行:

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'
Run Code Online (Sandbox Code Playgroud)


Flo*_*elf 18

sqlite3

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT
Run Code Online (Sandbox Code Playgroud)

  • 这是代码高尔夫状态聪明 (2认同)
  • 这会将整个文件读入内存和/或创建第二个磁盘副本吗?如果是这样,它很聪明但效率低下。 (2认同)

ter*_*don 13

Perl 方式。请注意,如果有许多相同的、最短长度的行,则此方法将仅打印其中之一:

perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 
Run Code Online (Sandbox Code Playgroud)

解释

  • perl -lne:-n表示“逐行读取输入文件”,-l导致从每个输入行中删除尾随换行符,并在每次print调用中添加一个换行符;and-e是将应用于每一行的脚本。
  • $m//=$_: 设置$m为当前行 ( $_) 除非$m已定义。该//=运算符从 Perl 5.10.0 开始可用。
  • $m=$_ if length()<length($m):如果当前值$m的长度大于当前行的长度,则将当前行($_)保存为$m
  • END{print $m if $.}:处理完所有行后,打印$m最短行的当前值。的if $.确保当行数(这只是发生$.)被定义,从而避免打印空白输入一个空行。

或者,由于您的文件小到可以放入内存,您可以执行以下操作:

perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 
Run Code Online (Sandbox Code Playgroud)

解释

  • @K=sort{length($a) <=> length($b)}<>:<>这是一个数组,其元素是文件的行。在sort将根据它们的长度对它们进行排序和排序行被保存为阵列@K
  • print "$K[0]":打印数组的第一个元素@K:最短的行。

如果要打印所有最短的行,可以使用

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 
Run Code Online (Sandbox Code Playgroud)


Ste*_*sop 13

Python 的结果相当简洁,代码做它在锡上说的:

python -c "import sys; print min(sys.stdin, key=len),"

我承认最后一个逗号是模糊的。它可以防止打印语句添加额外的换行符。此外,您可以在支持 0 行的 Python 3 中编写它,例如:

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"

  • @mikeserv:不,小字不在锡上。它在一个上锁的文件柜中的一份咨询传单上,在地窖里,在一扇标有“小心豹子”的门后面。 (2认同)

yae*_*shi 10

我总是喜欢使用纯 shell 脚本(没有 exec!)的解决方案。

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"
Run Code Online (Sandbox Code Playgroud)

注意

输入中的 NUL 字节有问题。因此,printf "ab\0\0\ncd\n" | bash this_script打印ab而不是cd.

  • 您是否尝试过将您的 *no exec!* 解决方案与其他解决方案进行对比?[这里有一个比较](http://unix.stackexchange.com/a/194015/52934) *exec!* 和 *no exec!* 解决类似问题的性能差异。执行单独的进程在蜘蛛爬行时很少有优势 - 以“var=$(get data)”等形式,因为它将数据流限制在单个上下文中 - 但是当您*通过*管道移动数据时 - 在流中 -每个应用的 exec 通常都有帮助 - 因为它仅在必要时才启用模块化程序的专门应用程序。 (2认同)
  • 请参阅 [为什么使用 shell 循环处理文本被认为是不好的做法?](http://unix.stackexchange.com/q/169716) (2认同)

cha*_*aos 9

这是一个纯zsh解决方案(它以最小长度打印所有行,从file):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}
Run Code Online (Sandbox Code Playgroud)

示例输入:

seven/7
4for
8 eight?
five!
four
Run Code Online (Sandbox Code Playgroud)

输出是:

4for
four
Run Code Online (Sandbox Code Playgroud)

我认为它需要一个简短的解释:-)


首先,我们将内部字段分隔符设置为换行符:

IFS=$'\n';
Run Code Online (Sandbox Code Playgroud)

到目前为止一切顺利,现在是困难的部分。print使用该-l标志打印由换行符而不是空格分隔的结果。

现在,我们从内部开始:

$(<file)
Run Code Online (Sandbox Code Playgroud)

该文件被逐行读取并被视为数组。然后:

${(o@)...//?/?}
Run Code Online (Sandbox Code Playgroud)

o标志表示结果应按升序排列,这@也是将结果视为数组的方法。( //?/?)后面的部分是一个替换,用 a 替换所有字符?。现在:

${~...[1]}
Run Code Online (Sandbox Code Playgroud)

我们取第一个数组 element [1],它是最短的,在您的情况下是 now ????

${(M)$(<file):#...}
Run Code Online (Sandbox Code Playgroud)

分别对每个数组元素进行匹配,将不匹配的数组元素去掉(M)。每个匹配的元素????(4 个字符)都保留在数组中。所以剩下的元素是那些有 4 个字符(最短的)的元素。

编辑:如果您只需要最短的一行,此修改后的版本将打印第一行:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}
Run Code Online (Sandbox Code Playgroud)


Bic*_*hoy 8

尝试:

awk '{ print length, $0 }' testfile | sort -n | cut -d" " -f2- | head -1
Run Code Online (Sandbox Code Playgroud)

这个想法是先awk打印每行的长度。这将显示为:

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text
Run Code Online (Sandbox Code Playgroud)

然后,使用字符计数对行进行排序sortcut以消除计数并head保留第一行(字符最少的行)。tail在这种情况下,您当然可以使用获取最多字符的行。

(这是从这个答案中采用的)


mik*_*erv 8

tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field
Run Code Online (Sandbox Code Playgroud)

......获胜者是......第2行,看起来。

2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?
Run Code Online (Sandbox Code Playgroud)

但问题是每条线的长度必须加倍以上才能工作 - 因此LINE_MAX有效地减半。原因是它正在使用 - 什么,基数 1?- 表示线的长度。一种类似的——也许更整洁——的方法可能是在流中压缩该信息。我想到的第一个想法是,我应该这样unexpand做:

tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line
Run Code Online (Sandbox Code Playgroud)

那印...

2
4for
Run Code Online (Sandbox Code Playgroud)

另一个,只是sed

sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile
Run Code Online (Sandbox Code Playgroud)

语法符合标准 - 但这并不能保证任何旧的sed都会\(reference-group\)\{counts\}正确处理- 许多人没有。

它基本上将相同的正则表达式应用于重复输入 - 这在编译它们时非常有用。该模式是:

\(.\)\(\n.*\)*
Run Code Online (Sandbox Code Playgroud)

它以不同的方式匹配不同的字符串。例如:

string1\nstring2\nstring3
Run Code Online (Sandbox Code Playgroud)

...与sin\1''空字符串 in匹配\2

1\nstring2\nstring3
Run Code Online (Sandbox Code Playgroud)

...与1in\1\nstring2\nstring3in匹配\2

\nstring2\nstring3
Run Code Online (Sandbox Code Playgroud)

...与\nin\1''空字符串 in匹配\2。如果\n在模式空间的头部有任何出现 ewline 的机会,这将是有问题的- 但是/^\n/D, 和//!g命令用于防止这种情况。我确实使用[^\n]过这个小脚本,但其他需要使可移植性成为一个问题,而且我对它经常被误解的许多方式并不满意。另外,.速度更快。

\nstring2
string1
Run Code Online (Sandbox Code Playgroud)

...匹配\ns再次\1与双方取得''的空字符串\2。空行根本不匹配。

当模式被g局部应用,两个偏差 - 最左侧的标准偏差和较小的右侧\newline 偏差 - 被抵消以实现跳过。几个例子:

s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g
Run Code Online (Sandbox Code Playgroud)

...如果全部应用于(不连续)到以下字符串...

string1\nstring2
Run Code Online (Sandbox Code Playgroud)

……将它变成……

s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2
Run Code Online (Sandbox Code Playgroud)

基本上,我使用正则表达式始终只处理我应用它的任何模式空间中的第一行。这使我能够在不求助于测试循环的情况下同时处理保留的最短匹配行和最新行的两个不同版本 - 应用的每个替换一次处理整个模式空间。

不同的版本对于文字字符串/字符串比较是必要的——所以每一行必须有一个版本,其中所有字符都保证相等。但是,当然,如果其中一个实际上是输入中最早出现的最短行,那么打印到输出的行可能应该是该行的原始版本——而不是我为了比较而消毒/均质化的那个。所以我需要每个版本的两个版本。

不幸的是,另一个必要条件是大量的缓冲区切换来处理相同的问题——但至少没有一个缓冲区超过保持最新所需的四行——所以也许这并不可怕。

无论如何,对于每个循环,发生的第一件事是在记住的行上进行转换 - 因为实际保存的唯一副本是文字原件 - 转换为......

^               \nremembered line$
Run Code Online (Sandbox Code Playgroud)

...然后next 输入行覆盖任何旧缓冲区。如果它不包含至少一个字符,则它被有效地忽略。仅q在第一个出现的空行处进行uit会容易得多,但是,好吧,我的测试数据有很多这样的数据,我想处理多个段落。

因此,如果它确实包含一个字符,则其文字版本将附加到记住的行,并且其间隔比较版本位于模式空间的开头,如下所示:

^   \n               \nremembered line\nnew$
Run Code Online (Sandbox Code Playgroud)

最后一个替换应用于该模式空间:

s/^\( *\)\(\n \1 *\)\{0,1\}\n//
Run Code Online (Sandbox Code Playgroud)

因此,如果换行符可以容纳在包含至少一个字符的记住行所需的空间内,那么前两行将被替换掉,否则只替换第一行。

无论结果如何,模式空间中的第一行总是D在循环结束时被删除,然后再重新开始。这意味着如果新行比最后一个字符串短......

new
Run Code Online (Sandbox Code Playgroud)

...被发送回循环中的第一个替换,它总是只从第一个换行符开始 - 所以它保持完整。但如果不是,那么字符串...

remembered line\nnew
Run Code Online (Sandbox Code Playgroud)

...将开始下一个循环,第一个替换将从它的字符串中剥离...

\nnew
Run Code Online (Sandbox Code Playgroud)

...每次。

在最后一行,记住的行被打印到标准输出,因此对于给定的示例数据,它打印:

4for
Run Code Online (Sandbox Code Playgroud)

但是,说真的,使用tr.


cuo*_*glm 5

使用 POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file
Run Code Online (Sandbox Code Playgroud)

  • 我不认为 `L` 是选择命名变量的最佳字母 :D 像 `min` 这样的东西会让事情更清楚 (3认同)