shell:echo $var的一个陷阱

    最近我在实现一个功能,按行读取文件,对每一行进行判断,如果符合条件就加入待发送的字符串中,并且每次加入会添上\r\n作为分隔标记符,发送形式为UDP。

 

    如果不考虑条件判断,此功能可以简单地描述为:将多个子字符串串联起来,子字符串之间用\r\n连接,然后将组成的字符串通过UDP发送出去。例如:

("str1" "str2" "str3")串联后的字符串为"str1\r\nstr2\r\nstr3",
将"str1\r\nstr2\r\nstr3"通过netcat以UDP方式发送出去。

再开始正文之前,先动手看个相关的例子:

var=hello$'\n'world   ##  在shell环境无法直接输入换行;$'...'中的...内容以ANSI C标准进行转义

echo $var
输出为:
hello world

echo "$var"
输出为:
hello
world

可以看到echo $var和echo "$var"得到的结果并不相同。下面开始正文部分:

 

1、 为了实现文章开头描述的功能,最初我的实现的形式如下:

1 #!/bin/bash
2 
3 sendStr=
4 str1="str1"
5 str2="str2"
6 str3="str3"
7 
8 sendStr=$str1$'\r\n'$str2'\r\n'$str3'\r\n' ### 将字符串拼接起来,$'...'中的内容会以ANSI C标准进行转义
9 echo -n $sendStr | nc -u 192.168.100.1 5656 ### 使用netcat将字符串通过udp发送出去

  接着我抓包看是否正确发送,截图如下:

    

    由抓包可以看到,字符串之间的连接符为\0d\20,查ACSII码表知,为"\r\space",而程序中实际的值为"\r\n"!!

 

2、 进一步的试验如下:

 

1 #!/bin/bash
2 
3 echo -n $str1$'\r\n'$str2$'\r\n'$str3 | nc -u 192.168.100.1 5656

 

    

    可以看到字符串之间的连接符变成了\0d\0a,即"\r\n",与程序中相同!!通过这次试验说明发送"\r\n"这种分割符是可行的。

 

3、由于我自己实际使用时需要对每条字符串进行判断,判断成功才会添加到sendStr中,因此每个str必须分开处理。上面的例子是一条命令包含3个str,显然无法完成功能。经过一番摸索我使用了一种非常冗长的实现方式:

 1 #!/bin/bash
 2 
 3 str1="str1"
 4 str2="str2"
 5 str3="str3"
 6 {
 7     echo -n $str1
 8     echo -n $'\r\n'
 9     echo -n $str2
10     echo -n $'\r\n'
11     echo -n $str3  
12 } | nc -u 192.168.100.1 5656

    这一次的试验中,抓包结果显示分隔符为"\r\n",功能达到。

 

4、功能算是完成了,但心中疑惑解不开,为什么通过echo $sendStr的形式就是不行呢?

    我突然想到在调用sendStr时,加上引用后,会如何呢。实现如下:

 1 #!/bin/bash
 2 
 3 #跟试验1唯一的不同是,sendStr调用时加上了双引号进行引用
 4 sendStr=
 5 str1="str1"
 6 str2="str2"
 7 str3="str3"
 8  
 9 sendStr=$str1$'\r\n'$str2'\r\n'$str3'\r\n'
10 echo -n "$sendStr" | nc -u 192.168.100.1 5656 ## 注意sendStr的双引号

    抓包结果显示分割符为"\r\n",是正确的。也就是说echo $sendStr产生的分割符是"\r\space",而echo "$sendStr"是"\r\n"。

 

5、为何echo $sendStr的分割符是"\r\space",echo "$sendStr"是"\r\n"?

    这一段就是本文的主题了,echo $sendStr 和echo "$sendStr"为什么会存在差异呢。经过一番冥想以及在bash 4.0手册(中文)上找灵感,终于想到为什么了。

    在shell环境中有个系统变量叫IFS,用于shell命令解析时进行命令和参数的分割,比如echo -E hello world这条命令会被解析成4部分,echo、-E、hello、world,然后shell对第一个字段进行查找,一次判定是否为保留字(像while、if之类),内部命令(cd,ls之类),函数(function定义),外部命令(比如我试验用的nc)。判定成功后进行进一步的命令解析了。总之,使用IFS进行命令分割是解析shell命令时的第一步。

    为了解释echo $sendStr和echo "$sendStr"的区别,须依据下述三条信息:

    1)默认的IFS配置为空白符,空白符包括3个字符:空格、tab和换行,将其ASSII码用十六进制打印出来就是:20 09 0a。注意:\r不在空白符之列!

    2)双引中的内容会禁用掉大部分特殊字符的特殊功能,让这些特殊字符保持为原本字符。IFS会被禁用。

    3) 将echo命令解析后的形式为echo param1 param2 ... paramN,那么会显示所有param,并且在param之间插入空格。

    在非双引情况下,\n会被识别为分割符。echo $sendStr进行命令分解后变为:echo str1\r str2\r str3,此时命令中带有3个输出参数,然而,输出参数之间会被插入空格,此时echo的输出为str1\r\spacestr2\r\space\str3。这就解释了为何echo $sendStr中"\r\n"被替换为"\r\space"了。

    在双引时,双引中的空白符仍保持原字符含义,不做IFS的功能。echo "$sendStr"进行命令解析后变为 echo str1\r\nstr2\r\nstr3,此时命令中仅存在1个输出参数

    由此我们可以引申出,当sendStr中带有特殊功能字符时,echo $var和echo "$var"行为可能会不一致。下面举例:

例1,\t和\n被替换为空格:

var=hello$'\n'world'\t'!
echo $var
输出为:
hello world !   ##每个字符串之间为空格

echo "$var"
输出为:
hello              ##此处有换行
world    !    ##注意此处的空白,比一个空格要大

例2,*会执行参数扩展

1 var="*.txt" 
2 echo $var
3 ##具体输出跟当前目录下存在哪些txt文件有关。
4 输出为:
20k.txt Messages.txt retest.txt scps_init.txt tmp.txt unname.txt
5 6 echo "$var" 7 输出为:
*.txt

 

6、总结

    如果var中包含一些特殊的功能字符,那么echo $var和echo "$var"的显示值很可能不同,因为echo $var中命令解析时特殊字符发挥作用;而如果var中不包含特殊功能字符时,两者行为是一致的。通常我们使用的变量几乎不会存在特殊字符,在对变量使用echo时,没有注意到 echo $var和echo "$var"的差别。

   上面都是解释echo $var和echo "$var"在包含特殊字符时行为会不一致。最后再举一个行为一致的例子:

 1 str=10
 2 var=$'\x24'str  ##\x24是$对应的ASCII码
 3 echo $var
 4 输出为:
 5 $str
 6  
 7 echo "$var"
 8 输出为:
 9 $str
10 
11 eval echo $var
12 输出为:
13 10
14 
15 eval echo "$var"
16 输出为:
17 10

    为什么echo $var时没有扩展为str的变量值,即10呢?原因在于shell的命令解析只会进行一次变量替换,除非加上eval关键字,在解析shell命令时才会进行再一次的扩展解析。

    eval echo "$var"同样得到10的值,是因为双引并不会禁用$字符的特殊功能。(题外话:单引会禁用$的特殊功能)。

 

    下一篇博文将会具体解释,在什么情况下echo $var和echo "$var"行为会不一致。敬请期待!

 

文毕。我还只是shell新手,文中可能有诸多理论错误之处,高人见到请指出。

 

 

 

    

 

 

转载于:https://www.cnblogs.com/hellowangsai/archive/2013/04/17/3026808.html

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值