前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >shell逐行处理文本求和,我人傻了...

shell逐行处理文本求和,我人傻了...

作者头像
编程珠玑
发布2021-06-21 20:07:48
1.3K0
发布2021-06-21 20:07:48
举报
文章被收录于专栏:编程珠玑编程珠玑

要要计算文本test.data的第二列的数字之和:

代码语言:javascript
复制
1 12 
2 23 
3 34 
4 56 

当然你可能会这样处理:

代码语言:javascript
复制
awk '{s+=$2} END {print s}' test.data 

很快就得到了结果。不过,本文要说的点与awk无关。我们通过另外一种方式来计算,即逐行分析处理的方式。

尝试一

我们尝试第一种方式,shell实现如下:

代码语言:javascript
复制
#!/usr/bin/env bash
sum=0
cat test.data | while read line
do
    temp_num=$(echo "$line" | cut -d ' ' -f 2)
    sum=$(( $sum + $temp_num ))
done
echo "we get sum:$sum"

输出结果:

代码语言:javascript
复制
we get sum:0

这是为什么!为什么得到的结果会是0呢?

这事坏就坏在脚本中的|,众所周知,这是一个管道命令,而这也就意味着,while循环的执行结果都是在一个subshell中,一旦这个subsell退出了,它里面的结果也就没有了。

其实这个问题利用有了这个神器,再也不怕shell写得不对了中提到的工具很容易发现:

代码语言:javascript
复制
$ shellcheck myscript

Line 3:
cat test.data | while read line
    ^-- SC2002: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead.
                      ^-- SC2162: read without -r will mangle backslashes.

Line 6:
    sum=$(( $sum + $temp_num ))
    ^-- SC2030: Modification of sum is local (to subshell caused by pipeline).
            ^-- SC2004: $/${} is unnecessary on arithmetic variables.
                   ^-- SC2004: $/${} is unnecessary on arithmetic variables.

Line 8:
echo "we get sum:$sum"
                 ^-- SC2031: sum was modified in a subshell. That change might be lost.

$

尝试二

既然管道命令不建议用,那么我们使用下面的方式看看:

代码语言:javascript
复制
#!/usr/bin/env bash
sum=0
for line in $(cat test.data)
do
    echo "get line :$line"
    temp_num=$(echo "$line" | cut -d ' ' -f 2)
    sum=$(( $sum + $temp_num ))
done
echo "we get sum:$sum"

输出结果:

代码语言:javascript
复制
get line :1
get line :12
get line :2
get line :23
get line :3
get line :34
get line :4
get line :56
we get sum:135

从结果中看出,如果文本中存在空格或者tab等,则看似每次读取一行,实际上是遇到空格,tab或换行就停止读取了,并没有达到我们的目的。 我们预期的应该是遇到换行才停止读取,为了达到这个目的,我们可以设置这个标记,即通过设置IFS来达到目的。在上面的shell开头加上:

代码语言:javascript
复制
IFS=$'\n'

但是修改为这样之后,在自己的系统上并没有得到我想要的效果,有知道的读者可以告知一下。

尝试三

让我们再换一种方式:

代码语言:javascript
复制
#!/usr/bin/env bash
sum=0
while read line
do
    echo "line $line"
    temp_num=$(echo "$line" | cut -d ' ' -f 2)
    sum=$(( $sum + $temp_num ))
done < "test.data"
echo "we get sum:$sum"

这种方式我们是能得到正确结果的。 当然,如果你要读取指定列,你还可以像下面这样做:

代码语言:javascript
复制
#!/usr/bin/env bash
sum=0
while read col1 col2
do
    sum=$(( $sum + $col2 ))
done < "test.data"
echo "we get sum:$sum"

其中col1,col2就分别代表了第一列,第二列,使用的时候,可以直接使用对应列的内容。

但是,如果我们要读取的内容包括了转义字符会怎么办?例如:

代码语言:javascript
复制
\n 12
\n 23
\n 34
\n 56

执行结果:

代码语言:javascript
复制
line 
 12
line 
 23
line 
 34
line 
 56
we get sum:125

从结果可以看到,虽然内容能否读取到,但是内容被打印出来的时候,已经变了,\被当成转义字符处理了,如果不想让它转义处理怎么办?只需要加上-r参数即可:

代码语言:javascript
复制
while read -r line

总结

在逐行处理文本过程中,主要关注以下几种情况:

  • 行中有空格,tab
  • 行中有转义字符

另外,通过shellcheck工具也会发现,它并不推荐for in file这种方式逐行处理文本:

代码语言:javascript
复制
Line 3:
for line in $(cat test.data)
            ^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.

相关精彩推荐

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 尝试一
  • 尝试二
  • 尝试三
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档