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

开发 前端
我们预期的应该是遇到换行才停止读取,为了达到这个目的,我们可以设置这个标记,即通过设置IFS来达到目的。

[[404180]]

本文转载自微信公众号「编程珠玑」,作者守望先生 。转载本文请联系编程珠玑公众号。

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

  1. 1 12  
  2. 2 23  
  3. 3 34  
  4. 4 56  

当然你可能会这样处理:

  1. awk '{s+=$2} END {print s}' test.data  

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

尝试一

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

  1. #!/usr/bin/env bash 
  2. sum=0 
  3. cat test.data | while read line 
  4. do 
  5.     temp_num=$(echo "$line" | cut -d ' ' -f 2) 
  6.     sum=$(( $sum + $temp_num )) 
  7. done 
  8. echo "we get sum:$sum" 

输出结果:

  1. we get sum:0 

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

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

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

  1. $ shellcheck myscript 
  2.  
  3. Line 3: 
  4. cat test.data | while read line 
  5.     ^-- SC2002: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead. 
  6.                       ^-- SC2162: read without -r will mangle backslashes. 
  7.  
  8. Line 6: 
  9.     sum=$(( $sum + $temp_num )) 
  10.     ^-- SC2030: Modification of sum is local (to subshell caused by pipeline). 
  11.             ^-- SC2004: $/${} is unnecessary on arithmetic variables. 
  12.                    ^-- SC2004: $/${} is unnecessary on arithmetic variables. 
  13.  
  14. Line 8: 
  15. echo "we get sum:$sum" 
  16.                  ^-- SC2031: sum was modified in a subshell. That change might be lost. 
  17.  

尝试二

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

  1. #!/usr/bin/env bash 
  2. sum=0 
  3. for line in $(cat test.data) 
  4. do 
  5.     echo "get line :$line" 
  6.     temp_num=$(echo "$line" | cut -d ' ' -f 2) 
  7.     sum=$(( $sum + $temp_num )) 
  8. done 
  9. echo "we get sum:$sum" 

输出结果:

  1. get line :1 
  2. get line :12 
  3. get line :2 
  4. get line :23 
  5. get line :3 
  6. get line :34 
  7. get line :4 
  8. get line :56 
  9. we get sum:135 

从结果中看出,如果文本中存在空格或者tab等,则看似每次读取一行,实际上是遇到空格,tab或换行就停止读取了,并没有达到我们的目的。

我们预期的应该是遇到换行才停止读取,为了达到这个目的,我们可以设置这个标记,即通过设置IFS来达到目的。在上面的shell开头加上:

  1. IFS=$'\n' 

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

尝试三

让我们再换一种方式:

  1. #!/usr/bin/env bash 
  2. sum=0 
  3. while read line 
  4. do 
  5.     echo "line $line" 
  6.     temp_num=$(echo "$line" | cut -d ' ' -f 2) 
  7.     sum=$(( $sum + $temp_num )) 
  8. done < "test.data" 
  9. echo "we get sum:$sum" 

这种方式我们是能得到正确结果的。

当然,如果你要读取指定列,你还可以像下面这样做:

  1. #!/usr/bin/env bash 
  2. sum=0 
  3. while read col1 col2 
  4. do 
  5.     sum=$(( $sum + $col2 )) 
  6. done < "test.data" 
  7. echo "we get sum:$sum" 

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

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

  1. \n 12 
  2. \n 23 
  3. \n 34 
  4. \n 56 

执行结果:

  1. line  
  2.  12 
  3. line  
  4.  23 
  5. line  
  6.  34 
  7. line  
  8.  56 
  9. we get sum:125 

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

  1. while read -r line 

总结

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

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

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

  1. Line 3: 
  2. for line in $(cat test.data) 
  3.             ^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop. 

作者:守望,linux应用开发者,目前在公众号【编程珠玑】,分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源

原文链接:https://mp.weixin.qq.com/s/rW0Va8g0U3apxNwR0Ziw9w

 

责任编辑:武晓燕 来源: 编程珠玑
相关推荐

2021-06-09 07:15:20

Shell逐行处理

2021-06-22 09:32:40

Linuxshell命令

2021-04-21 08:03:34

脚本Shell读取

2021-08-20 10:46:25

Shell脚本文件Linux

2017-03-02 18:10:20

LinuxShell命令

2016-03-30 11:16:33

2024-09-12 17:39:27

2021-09-10 16:30:29

LinuxShell文本

2024-01-07 16:46:19

FiberHTTPWeb

2019-10-14 11:31:51

工具代码开发

2024-08-12 09:43:42

2013-07-16 16:37:12

91百度

2020-11-21 19:04:33

技术开发指标

2013-08-30 10:25:22

Shell主机监控

2018-12-28 10:45:08

Linux文本行命令

2021-03-28 08:57:57

Python 文本数据

2020-12-23 11:08:10

Python代码文本

2013-06-03 11:28:05

shell命令

2021-06-28 10:06:21

开源文本识别pyWhat

2011-04-14 14:37:06

职场
点赞
收藏

51CTO技术栈公众号