在bash中,如何计算变量中的行数?


79

我有一个变量,其中存储了一个字符串,需要检查它是否包含行:

var=`ls "$sdir" | grep "$input"`

伪代码:

while [ ! $var's number of lines -eq 1 ]
  do something

那是我关于如何检查的想法。echo $var | wc -l不起作用-1即使可以,它总是说3

echo -e 效果不佳。

Answers:


104

行情很重要。

echo "$var" | wc -l

3
这不是命令周围的引号,而是命令替换周围的引号。双引号不会干扰。
伊格纳西奥·巴斯克斯

37
这有一个微妙之处。空字符串将返回1,因为在空字符串上的echo会打印换行符。
安德鲁·阮

3
换句话说:if [ -z "$var" ]; then printf '%s\n' '0'; else printf '%s\n' "${var%$'\n'}" | wc -l; fi。尝试使用var=(无行)var='foo'var=$'foo\n'(在* nix意义上都行)。
l0b0

2
还有另一种将其放入变量的方法: LINE_COUNT=$(wc -l <<< "${var}")
幸运的是,2015年

4
@Timecho -n只会将计数减少一。@lucifuriousecho $(wc -l <<< "${NONEXISTENTVAR}")仍然给出1,而不是0
朱利安

72

如果变量为空(未定义或字符串为空),则此处接受的答案和其他答案将不起作用。

这有效:

echo -n "$VARIABLE" | grep -c '^'

例如:

ZERO=
ONE="just one line"
TWO="first
> second"

echo -n "$ZERO" | grep -c '^'
0
echo -n "$ONE" | grep -c '^'
1
echo -n "$TWO" | grep -c '^'
2

我无法从@PolyTekPatrick再现听起来像是错误的声音,即WHITESPACE_ONE=' '在我的shell(bash)中仍然可以正常工作,正确报告了一行。
朱利安(Julian)

你是对的。我发誓测试过,但是我一定错过了双引号或输入了错误的内容。实际上,一个或多个空格字符确实确实应计为1行。我删除了我之前的评论,以避免造成混淆。
PolyTekPatrick

4
完善!这应该是公认的答案。到目前为止,这是唯一可以正确回答所有情况下的问题的解决方案。感谢您展示测试案例以证明这一点。
PolyTekPatrick

4
是!我也认为这应该是公认的答案。
马丁·乔纳

1
这是正确的,但可以使用printf而不是进行简化echo -n。我已将此添加为替代答案,以包括测试结果。见下文。
Stilez

23

在bash中使用字符串的另一种方法:

wc -l <<< "$var"

本注释中所述,为空$var将导致1行而不是0行,因为在这种情况下,字符串在此添加了换行符(说明)。


1
我必须这样做才能获得正确的答案:(wc -l <<<"$(echo "$var")"是的,每个符号都是必要的)
Nicolai S

@NicolaiS然后您做错了。的内容是$var什么?
讲者

1
@NicolaiS正确,因为您var包含一行:您没有\n用任何东西解释。将多行输入到您的计算机中var,它将可以正常运行,例如与var="foo<ENTER>bar<ENTER>baz"<ENTER>
讲者

2
xxd <<< ''创建这个hexdump 00000000: 0a。因此,<<<(此处字符串)将换行符添加到任何内容。
MiniMax

1
@MiniMax谢谢,我在您的答案中添加了您的输入。您可以在此处找到对此行为的解释。
讲者

9

您可以将“ wc -l”替换为“ wc -w”,以计算单词数而不是行数。这不会计算任何新行,可用于在继续之前测试原始结果是否为空。


wc -l即使输入变量为空,解决方案也会输出1,因此使用起来wc -w会更好。
卡迪尔,2015年

8

没有人提到参数扩展,因此这里有几种使用纯bash的方法。

方法1

删除非换行符,然后获取字符串长度+1。引号很重要

 var="${var//[!$'\n']/}"
 echo $((${#var} + 1))

方法2

转换为数组,然后获取数组长度。为此,请不要使用引号

 set -f # disable glob (wildcard) expansion
 IFS=$'\n' # let's make sure we split on newline chars
 var=(${var})
 echo ${#var[@]}

2
方法2更干净,可能更快。但是它依赖于IFS。因此IFS=$'\n',请确保将变量扩展为数组时将其拆分为新行:IFS=$'\n'; var=(${var})
撤消

我喜欢方法2,因为它的开销最小:没有外部命令,甚至没有内置命令。它也很可读。
DKroot

1
ShellCheck抱怨:github.com/koalaman/shellcheck/wiki/SC2206。它在某事上。set -f需要避免不必要的全局扩展。尝试在上使用方法2 var=$'*\n.*'
DKroot

7

@Julian答案的简单版本,适用于所有带有或不带有尾随\ n的字符串(它会将仅包含一个尾随\ n的文件计为空):

printf "%s" "$a" | grep -c "^"

  • 返回零:未设置变量,空字符串,包含裸换行符的字符串
  • 返回1:任何非空行,带有或不带有尾随换行符
  • 等等

输出:

# a=
# printf "%s" "$a" | grep -c "^"
0

# a=""
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf "")"
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf "\n")"
# printf "%s" "$a" | grep -c "^"
0

# a="$(printf " \n")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf " ")"
# printf "%s" "$a" | grep -c "^"
1

# a="aaa"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s" "aaa")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s\n" "aaa")"
# printf "%s" "$a" | grep -c "^"
1

# a="$(printf "%s\n%s" "aaa" "bbb")"
# printf "%s" "$a" | grep -c "^"
2

# a="$(printf "%s\n%s\n" "aaa" "bbb")"
# printf "%s" "$a" | grep -c "^"
2

+1。将标志传递到echo(例如echo -n)不是标准的,并且在不同的实现上可能会给出不同的结果。而printf默认情况下我们想要什么。另外,使用printf可以节省您一个过程,因为它是内置的Shell。(建议:printf "$a" | wc -l更简洁,避免不必要的使用grep
joshtch

@joshtch好吧.. 正如在此讨论中其他人所说的那样,printf .... | wc -l只会删除一条额外的行(换行符),因此在空行的情况下,结果将为0。正确。但是,如果我们通过2线,其结果将是1,其中传递给同一个变量printf ... | grep "^"将正确地返回2.此外,直接使用printf "$a"是很危险的,因为它会导致无记载错误如果字符串包含意外字符一样%s%d所以on ...如果字符串以短划线开头,则相同。相反,输入2中的参数printf会自动转义
Luke Savefrogs

3

如果grep没有返回结果,则投票最多的答案将失败。

Homer Simpson
Marge Simpson
Bart Simpson
Lisa Simpson
Ned Flanders
Rod Flanders
Todd Flanders
Moe Szyslak

这是错误的方式做到这一点

wiggums=$(grep -iF "Wiggum" characters.txt);
num_wiggums=$(echo "$wiggums" | wc -l);
echo "There are ${num_wiggums} here!";

将会告诉我们,列表中没有1 Wiggum,即使没有。

取而代之的是,您需要做额外的检查,以查看变量是否为空(-z,如“为零”)。如果grep没有返回任何内容,则该变量将为空。

matches=$(grep -iF "VanHouten" characters.txt);

if [ -z "$matches" ]; then
    num_matches=0;
else
    num_matches=$(echo "$matches" | wc -l);
fi

echo "There are ${num_matches} VanHoutens on the list";

2

另一种计算变量行数的方法-假设您确实检查了它是否已成功填充或它不是空的,那么只需检查$?之后VAR子shell结果做作- :

readarray -t tab <<<"${var}"
echo ${#tab[@]}

readarray | mapfile是bash内部命令,用于将输入文件(在这种情况下为字符串)转换为基于换行符的数组。

-t标志可防止在数组单元格的末尾存储换行符,这对于以后使用存储的值很有用

这种方法的优点是:

  • 没有外部命令(wc,grep等)
  • 没有子壳(管道)
  • 没有IFS问题(修改后还原,难以与内部命令的命令限制范围一起使用,...)

1

为了避免在“ wc -l”命令中使用文件名:

lines=$(< "$filename" wc -l)
echo "$lines"
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.