与 '\r' 字符的两次交锋

背景

众所周知,不同的操作系统中,对文本中换行的表示是不同的,在 windows 中用 \r\n 表示,在 Linux 中用 \n 表示,而在 Mac OS 中用 \r 表示。对于像笔者这种以 Windows 作为开发环境,Linux 作为执行环境的程序员来说,经常会不可避免地碰到由换行符不一致而引起到奇怪问题,下面就回溯一下我曾遇到并解决的两个相关的问题,希望能给读者来一点启发。

奇怪的脚本

这是同事遇到的问题,其当时在开发一个 shell 脚本,需要一个从文件中查找关键字的功能,比如文件名叫 data.txt,要查找到关键字是 warn,很容易写出这样的命令:

grep warn data.txt

假如文件的内容是:

warn: wrong
info: good
warn: not good

那么该命令因该返回两行:

warn: wrong
warn: not good

到这里,一切都正常,但是这位同事不满足于只将结果打印到标准输出,而是要用一个变量存储,以便后续的操作,于是写下了这样两行脚本:

result=$(grep warn data.txt)
echo $result

这时奇怪的事发生了,执行这个脚本得到的结果是:

_warn: not good  # 注意 'warn' 前面有一个空格,为了醒目,用 '_' 表示

相信读者已经猜到,导致这个现象的原因,就是 \r 字符。下面解释一下原因。
这个文件的出处现已无从考证,但其中每行都是以 \r\n 结束, 推测应该是从 Windows 平台拷贝过来的。但是在 Linux 中,只有 \n 被作为换行符,其前面的 \r 字符将被视为普通的文本内容处理,所以 grep 命令返回到结果应该为:

warn: wrong\r
warn: not good\r

在向屏幕打印这段内容时,\r 字符会被转义为回车符,其作用是将光标即下一个字符的输出位置移动到本行行首(Windows 上的 \r\n 可以理解为先将输出位置移动到行首,再用 \n 移动到下一行),但由于 \r 后面再无其它字符,所以对用户来说,仅光标位置的移动是感知不到的。
但在脚本中,情况有了些许的变化—— grep 的结果不直接打印,而是先赋值给变量,再打印变量。区别在于脚本中会将 result 中的内容按行分割,再依次传给 echo ,实际执行的命令为:

echo warn: wrong\r warn: not good\r

上面已经说过,\r 字符会使光标回到行首,所以第一个 \r 字符前的内容会被后面的内容覆盖,这便是问题所在。
其实只要将输出命令写成:

echo "$result"

而不是

echo $result

就不会有这个问题,因为双引号中的内容会被当作纯粹的字符串处理,不做转义,实际的命令为:

echo "warn: wrong\r
warn: not good\r"

内容分为两行打印,和直接用 grep 输出的结果一样。

链不到的库

这次到问题出现在另一同事的脚本中,其内容只有两行编译命令,被打在一个 rar 包中发给我。两行命令的内容都查不多,但命令的内容无关紧要,假设为:

g++ -Wall source.cpp -I...-L... -o source -lxxx

其中的 xxx 是一个本项目的动态库。我直接在本地执行该脚本,但链接失败,报 ‘xxx’ 库找不到。
第一反应当然是库所在的目录没有被加到动态库的搜索路径中,但检查了一遍发现不是。又怀疑命令写的有问题、动态库文件没有读权限、文件名写错,但都验证通过了。折磨了有半个小时,突然灵光一现,用 dos2unix 命令转换了一下编译脚本,随即编译通过。
原因很简单,这个脚本中每一行的末尾都有一个 \r,恰好跟在 ‘xxx’ 后面,变成了 ‘xxx\r’,链接时实际查找的是 ‘xxx\r’ 文件,当然找不到了。但由于打印到屏幕上的内容是一样到,所以很难发现。

经验教训

  • 如果环境不能一致,起码尽量做到配置和工具一致,许多 Windows 上到编辑器都支持将默认换行符都配置为 \n,Linux 上也有 zip、gzip 等压缩工具
  • 不可轻信外来的代码和数据,要做校验
  • shell 编程中,如果变量的内容是纯粹的数据,就用引号括起来
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/github_34978079/article/details/51552330
个人分类: debug
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭