Shell中的IFS解惑

1、IFS 介绍php

     Shell 脚本中有个变量叫 IFS(Internal Field Seprator) ,内部域分隔符。完整定义是The shell uses the value stored in IFS, which is the space, tab, and newline characters by default, to delimit words for the read and set commands, when parsing output from command substitution, and when performing variable substitution.html

     Shell 的环境变量分为 set, env 两种,其中 set 变量能够经过 export 工具导入到 env 变量中。其中,set 是显示设置shell变量,仅在本 shell 中有效;env 是显示设置用户环境变量 ,仅在当前会话中有效。换句话说,set 变量里包含了 env 变量,但 set 变量不必定都是 env 变量。这两种变量不一样之处在于变量的做用域不一样。显然,env 变量的做用域要大些,它能够在 subshell 中使用。shell

     而 IFS 是一种 set 变量,当 shell 处理"命令替换"和"参数替换"时,shell 根据 IFS 的值,默认是 space, tab, newline 来拆解读入的变量,而后对特殊字符进行处理,最后从新组合赋值给该变量。bash

2、IFS 简单实例ide

一、查看变量 IFS 的值。
函数

  1. $ echo $IFS  
  2.   
  3. $ echo "$IFS" | od -b  
  4. 0000000 040 011 012 012  
  5. 0000004  

工具

直接输出IFS是看不到的,把它转化为二进制就能够看到了,"040"是空格,"011"是Tab,"012"是换行符"\n" 。最后一个 012 是由于 echo 默认是会换行的。
测试

二、$* 和 $@ 的细微差异
     从下面的例子中能够看出,若是是用冒号引发来,表示这个变量不用IFS替换!!因此能够看到这个变量的"原始值"。反之,若是不加引号,输出时会根据IFS的值来分割后合并输出! $* 是按照IFS中的第一个值来肯定的!下面这两个例子还有细微的差异!
ui

  1. $ IFS=:;  
  2. $ set x y z  
  3. $ echo $*  
  4. x y z  
  5. $ echo "$*"  
  6. x:y:z  
  7. $ echo $@  
  8. x y z  
  9. $ echo "$@"  
  10. x y z  

 spa

上例 set 变量实际上是3个参数,而下面这个例子实质是2个参数,即 set "x y z"  和 set x y z 是彻底不一样的。

  1. $ set "x" "y z"  
  2. $ echo $*  
  3. x y z  
  4. $ echo "$*"  
  5. x:y z  
  6. $ echo $@  
  7. x y z  
  8. $ echo "$@"  
  9. x y z  
  10. $ echo $* |od -b  
  11. 0000000 170 040 171 040 172 012  
  12. 0000006  
  13. $ echo "$*" |od -b  
  14. 0000000 170 072 171 040 172 012  
  15. 0000006  

小结:$* 会根据 IFS 的不一样来组合值,而 $@ 则会将值用" "来组合值!

三、for 循环中的奇怪现象

  1. $ for x in $var ;do echo $x |od -b ;done  
  2. 0000000 012  
  3. 0000001  
  4. 0000000 040 141 012  
  5. 0000003  
  6. 0000000 142 012  
  7. 0000002  
  8. 0000000 012  
  9. 0000001  
  10. 0000000 143 012  
  11. 0000002  

 

先暂且不解释 for 循环的内容!看下面这个输出!IFS 的值同上! var=": a:b::c:"

  1. $ echo $var |od -b  
  2. 0000000 040 040 141 040 142 040 040 143 012  
  3. 0000011  
  4. $ echo "$var" |od -b  
  5. 0000000 072 040 141 072 142 072 072 143 072 012  
  6. 0000012  

"$var"的值应该没作替换,因此仍是 ": a:b::c:" (注 "072" 表示冒号),可是$var 则发生了变化!注意输出的最后一个冒号没有了,也没有替换为空格!Why?

 

使用 $var 时是经历了这样一个过程!首先,按照这样的规则 [变量][IFS][变量][IFS]……根据原始 var 值中全部的分割符(此处是":")划分出变量,若是IFS的值是有多个字符组成,如IFS=":;",那么此处的[IFS]指的是IFS中的任意一个字符($* 是按第一个字符来分隔!),如 ":" 或者 ";" ,后面再也不对[IFS]作相似说明!(注:[IFS]会有多个值,多亏 #blackold 的提醒);而后,获得相似这样的 list, ""   " a"   "b"  ""   "c"  。若是此时 echo $var,则须要在这些变量之间用空格隔开,也就是""  [space]   "  a"  [space]  "b" [space]  "" [space]  "c" ,忽略掉空值最终输出是 [space][space]a[space]b[space][space]c

若是最后一个字符不是分隔符,如 var="a:b",那么最后一个分隔符后的变量就是最后一个变量!

这个地方要注意下!!若是IFS就是空格,那么相似于" [space][space]a[space]b[space][space]c "会合并重复的部分,且去头空格,去尾空格,那么最终输出会变成相似 a[space]b[space]c ,因此,若是IFS是默认值,那么处理的结果就很好算出来,直接合并、忽略多余空格便可!

另外,$* 和 $@ 在函数中的处理过程是这样的(只考虑"原始值"!)!"$@",就是像上面处理后赋值,可是 "$*" 却不同!它的值是用分隔符(如":")而不是空格隔开!具体例子见最后一个例子!

好了,如今来解释 for 循环的内容。for 循环遍历上面这个列表就能够了,因此 for 循环的第一个输出是空!("012"是echo输出的换行符 )。。。。后面的依次类推!不信能够试试下面这个例子,结果是同样的!

  1. $ for x in "" " a" "b" "" "c" ;do echo $x |od -b ;done  
  2. 0000000 012  
  3. 0000001  
  4. 0000000 040 141 012  
  5. 0000003  
  6. 0000000 012  
  7. 0000001  
  8. 0000000 142 012  
  9. 0000002  
  10. 0000000 012  
  11. 0000001  
  12. 0000000 143 012  
  13. 0000002  

3、IFS的其余实例

Example 1:

 

  1. $ IFS=:  
  2. $ var=ab::cd  
  3. $ echo $var  
  4. ab  cd  
  5. $ echo "$var"  
  6. ab::cd  

解释下:x 的值是 "ab::cd",当进行到 echo $x 时,由于$符,因此会进行变量替换。Shell 根据 IFS 的值将 x 分解为 ab "" cd,而后echo,插入空隔,ab[space]""[space]cd,忽略"",输出  ab  cd 。

Example 2 :

  1. $ read a  
  2.        xy  z  
  3. $ echo $a  
  4. xy  z  

 

解释:这是 http://bbs.chinaunix.net/thread-207178-1-1.html 上的一个例子。此时IFS是默认值,本但愿把全部的输入(包括空格)都放入变量a中,可是输出的a却把前面的空格给忽略了!!缘由是:默认的 IFS 会按 space tab newline 来分割。这里须要注意的一点是,read 命令的实现过程,即在读入时已经替换了。解决办法是在开头加上一句 IFS=";" ,这里必须加上双引号,由于分号有特殊含义。

Example 3 :

  1. $ tmp="   xy z"  
  2. $ a=$tmp  
  3. $ echo $a  
  4. $ echo "$a"  

解释:何时会根据 IFS 来"处理"呢?我以为是,对于不加引号的变量,使用时都会参考IFS,可是要注意其原始值!

Example 4 :

  1. #!/bin/bash  
  2. IFS_old=$IFS      #将原IFS值保存,以便用完后恢复  
  3. IFS=$’\n’        #更改IFS值为$’\n’ ,注意,以回车作为分隔符,IFS必须为:$’\n’  
  4. for i in $((cat pwd.txt)) #pwd.txt 来自这个命令:cat /etc/passwd >pwd.txt  
  5. do  
  6.     echo $i  
  7. done  
  8. IFS=$IFS_old      #恢复原IFS值  

另一个例子,把IP地址逆转输出:

Example 5 :

  1. #!/bin/bash  
  2.   
  3. IP=220.112.253.111  
  4. IFS="."  
  5. TMPIP=$(echo $IP)  
  6. IFS=" " # space  
  7. echo $TMPIP  
  8. for x in $TMPIP ;do   
  9.     Xip="${x}.$Xip"  
  10. done  
  11. echo ${Xip%.}  

Complex_Example 1:  http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3660898&page=1#pid21798049

  1. function output_args_ifs(){  
  2.     echo "=$*"  
  3.     echo "="$*  
  4.     for m in $* ;do   
  5.         echo "[$m]"  
  6.     done  
  7. }  
  8.   
  9. IFS=':'  
  10. var='::a:b::c:::'  
  11. output_args_ifs $var  

 

输出为:

  1. =::a:b::c::  # 少了最后一个冒号!看前面就知道为何了  
  2. =  a b  c   
  3. []  
  4. []  
  5. [a]  
  6. [b]  
  7. []  
  8. [c]  
  9. []  

因为 "output_args_ifs $var" 中 $var 没有加引号,因此根据IFS替换!根据IFS划分出变量: ""  ""  "a"  "b"  ""  "c" "" ""(能够经过输出 $# 来测试参数的个数!),重组的结果为

 "$@" 的值是  "" [space] "" [space]  "a" [space]  "b"  [space] "" [space]  "c" [space] "" [space] "",能够经过,echo==>"  a b  c   "
"$*" 的值是   "" [IFS] "" [IFS]  "a" [IFS]  "b"  [IFS] "" [IFS]  "c" [IFS] "" [IFS] "",忽略"",echo=>"::a:b::c::"

注意, $* 和 $@ 的值都是  ""   ""   "a"   "b"   ""   "c"  ""  "" 。能够说是一个列表……由于他们原本就是由 $1 $2 $3……组成的。

因此,《Linux程序设计》里推荐使用 $@,而不是$*

总结:IFS 其实仍是很麻烦的,稍有不慎就会产生很奇怪的结果,所以使用的时候要注意!我也走了很多弯路,只但愿能给后来者一些帮助。本文如有问题,欢迎指正!!谢谢!

相关文章
相关标签/搜索