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

122
我有一个存储字符串的变量,并且需要检查它是否包含多行内容:
var=`ls "$sdir" | grep "$input"`

伪代码:

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

这是我的检查方法。使用echo $var | wc -l无法正常工作-它总是显示1,即使有3个。

echo -e也无法正常工作。

9个回答

144

引号很重要。

echo "$var" | wc -l

3
这里引号不是用于引用命令,而是用于引用命令替换的结果。这些引号成对出现不会相互干扰。 - Ignacio Vazquez-Abrams
63
这句话有些微妙。一个空字符串会返回1,因为在空字符串上使用echo会打印一个换行符。 - Andrew Nguyen
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}") - lucifurious
7
@Tim 的命令 echo -n 只会将计数减少一次。 @lucifurious 命令 echo $(wc -l <<< "${NONEXISTENTVAR}") 仍然返回 1,而不是0。 - Julian
显示剩余5条评论

126

这里发布的被采纳的答案和其他答案在变量为空(未定义或空字符串)的情况下不起作用。

以下方法可行:

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

1
你说得对。我发誓我测试过了,但可能错过了双引号或打错了什么字。一个或多个空格字符确实会被计算为一行,这是应该预期的。我删除了早期的评论,以避免让人困惑。 - PolyTekPatrick
7
太好了!这应该是被接受的答案。到目前为止,这是唯一一个正确回答所有情况的解决方案。感谢您展示测试案例来证明它。 - PolyTekPatrick
5
是的!我也认为这应该成为被接受的答案。 - Martin Joiner
1
这是正确的,但可以使用printf而不是echo -n来简化。我已经添加了这个作为备选答案,以便包括测试结果。请参见下文。 - Stilez
1
它说“建议编辑队列已满”。请查看答案队列。我们也想添加! - Artfaith
显示剩余3条评论

34

在bash中,另一种使用here strings的方法:

wc -l <<< "$var"

正如在这条评论中提到的那样,一个空的$var将导致有1行而不是0行,因为在这种情况下,here strings会添加一个换行符 (解释)。


1
我必须这样做才能得到正确的答案: wc -l <<<"$(echo "$var")" (是的,每个符号都是必要的) - Nicolai S
var="a\nb\nc" 经过 wc -l 命令后会得到 1。 - Nicolai S
1
@NicolaiS 这是正确的,因为你的 var 只包含了 _一行_:你没有用任何东西来解释 \n。将多行放入你的 var 中,它就会工作,例如使用 var="foo<ENTER>bar<ENTER>baz"<ENTER> - speakr
2
xxd <<< '' 创建这个十六进制转储 00000000: 0a。因此,<<<(Here Strings)会向任何内容添加换行符。 - MiniMax
1
@MiniMax 谢谢,我已经将你的输入添加到我的答案中。你可以在这里找到关于这种行为的解释:(https://unix.stackexchange.com/questions/20157/why-does-a-bash-here-string-add-a-trailing-newline-char)。 - speakr
显示剩余6条评论

14

没有人提到参数扩展,这里提供一些使用纯Bash的方法。

方法1

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

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

方法二

将其转换为数组,然后获取数组长度。要使此方法生效,请勿使用引号

 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}) - untore
我喜欢方法2,因为它的开销最小:没有外部命令,甚至连内置命令都看不到。而且它也很易读。 - Dima Korobskiy
1
ShellCheck 抱怨了:https://github.com/koalaman/shellcheck/wiki/SC2206。它指出了问题。需要使用 set -f 来避免不必要的通配符扩展。尝试在 var=$'*\n.*' 上使用第二种方法。 - Dima Korobskiy
1
纯内部bash自动奖励,无需外部wc -l - nhed

13

一个更简单的@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
+1. 将标志传递给 echo (例如 echo -n)不是标准的做法,而且可能在不同的实现中产生不同的结果。然而,printf 默认情况下会执行我们想要的操作。作为奖励,使用 printf 可以节省一个进程,因为它是 shell 内建命令。(建议:printf "$a" | wc -l 更加简洁,避免不必要地使用 grep - joshtch
@joshtch 嗯...不是的。正如其他人在讨论中所说,printf .... | wc -l只会删除一个额外的换行符(newline),因此在空行的情况下结果将为0。正确。但如果我们传递2行,结果将为1,在相同的变量传递到printf ... | grep "^"时,将正确返回2。此外,直接使用printf "$a"非常危险,因为如果字符串意外包含像%s%d等字符,它可能会导致静默错误...如果字符串以破折号开头也是一样。相反,在printf中使用第二个参数会自动转义。 - LukeSavefrogs
这不是完全正确的。行 a="$(printf "\n")"a 设置为空字符串,而不是裸换行符。要在 bash 中运行换行测试,请尝试 a=$'\n'; printf "%s" "$a" | grep -c "^"。计数将为 1。如果您想从计数中省略 所有 空行,请将 grep 测试从 "^" 更改为 "."。最后,关于 printf vs. echo -n,技术上说 printf 更具可移植性。但在大多数情况下,echo -n 都可以正常工作。任何一种命令都会导致每个测试用例的计数相同。 - Bryan Roach

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

1
“wc -l”解决方案即使输入变量为空也会输出1,因此最好使用“wc -w”。 - Kadir

5

另一种计算变量中行数的方法——假设您已检查它是否成功填充或非空,只需在var子shell结果分配后检查$?即可:

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

readarray|mapfile是bash的内部命令,可根据换行符将输入文件(或在此情况下的here string)转换为数组。

-t标志防止存储单元格末尾的换行符,在稍后使用存储的值时非常有用。

这种方法的优点是:

  • 没有外部命令(例如wc、grep等)
  • 没有子shell(管道)
  • 没有IFS问题(修改后恢复,与内部命令的命令限定范围一起使用比较棘手等等)

一个临时文件的额外进程(由于here string)通常更昂贵? - Britton Kerin

4
为了避免在 wc -l 命令中出现文件名:
lines=$(< "$filename" wc -l)
echo "$lines"

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!";

这段内容会告诉我们,在列表中有1Wiggum,即使没有任何一个。

相反地,您需要进行一次额外的检查以查看变量是否为空(-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";

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