shell 脚本的学习 IFS解惑

一、IFS 介绍

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.

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

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

二、IFS 简单实例

1、查看变量 IFS 的值。

[plain] view plaincopyprint?

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

$ echo "$IFS" | od -b
0000000 040 011 012 012
0000004

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

2、$* 和 [email protected] 的细微差别
     从下面的例子中可以看出,如果是用冒号引起来,表示这个变量不用IFS替换!!所以可以看到这个变量的"原始值"。反之,如果不加引号,输出时会根据IFS的值来分割后合并输出! $* 是按照IFS中的第一个值来确定的!下面这两个例子还有细微的差别!

[plain] view plaincopyprint?

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

上例 set 变量其实是3个参数,而下面这个例子实质是2个参数,即 set "x y z"  和 set x y z 是完全不同的。

[plain] view plaincopyprint?

  1. $ set "x" "y z"
  2. $ echo $*
  3. x y z
  4. $ echo "$*"
  5. x:y z
  6. $ echo [email protected]
  7. x y z
  8. $ echo "[email protected]"
  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
$ set "x" "y z"
$ echo $*
x y z
$ echo "$*"
x:y z
$ echo [email protected]
x y z
$ echo "[email protected]"
x y z
$ echo $* |od -b
0000000 170 040 171 040 172 012
0000006
$ echo "$*" |od -b
0000000 170 072 171 040 172 012
0000006

小结:$* 会根据 IFS 的不同来组合值,而 [email protected] 则会将值用" "来组合值!

3、for 循环中的奇怪现象

[plain] view plaincopyprint?

  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 x in $var ;do echo $x |od -b ;done
0000000 012
0000001
0000000 040 141 012
0000003
0000000 142 012
0000002
0000000 012
0000001
0000000 143 012
0000002

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

[plain] view plaincopyprint?

  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
$ echo $var |od -b
0000000 040 040 141 040 142 040 040 143 012
0000011
$ echo "$var" |od -b
0000000 072 040 141 072 142 072 072 143 072 012
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是默认值,那么处理的结果就很好算出来,直接合并、忽略多余空格即可!

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

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

[plain] view plaincopyprint?

  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
$ for x in "" " a" "b" "" "c" ;do echo $x |od -b ;done
0000000 012
0000001
0000000 040 141 012
0000003
0000000 012
0000001
0000000 142 012
0000002
0000000 012
0000001
0000000 143 012
0000002

三、IFS的其他实例

Example 1:

[plain] view plaincopyprint?

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

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

Example 2 :

[plain] view plaincopyprint?

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

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

Example 3 :

[plain] view plaincopyprint?

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

解释:什么时候会根据 IFS 来"处理"呢?我觉得是,对于不加引号的变量,使用时都会参考IFS,但是要注意其原始值!

Example 4 :

[plain] view plaincopyprint?

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

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

Example 5 :

[plain] view plaincopyprint?

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

IP=220.112.253.111
IFS="."
TMPIP=$(echo $IP)
IFS=" " # space
echo $TMPIP
for x in $TMPIP ;do
    Xip="${x}.$Xip"
done
echo ${Xip%.}

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

[plain] view plaincopyprint?

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

IFS=‘:‘
var=‘::a:b::c:::‘
output_args_ifs $var

输出为:

[plain] view plaincopyprint?

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

由于 "output_args_ifs $var" 中 $var 没有加引号,所以根据IFS替换!根据IFS划分出变量: ""  ""  "a"  "b"  ""  "c" "" ""(可以通过输出 $# 来测试参数的个数!),重组的结果为

"[email protected]" 的值是  "" [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::"

注意, $* 和 [email protected] 的值都是  ""   ""   "a"   "b"   ""   "c"  ""  "" 。可以说是一个列表……因为他们本来就是由 $1 $2 $3……组成的。

所以,《Linux程序设计》里推荐使用 [email protected],而不是$*

http://blog.csdn.net/whuslei/article/details/7187639

shell 脚本的学习 IFS解惑

时间: 2024-08-27 15:48:20

shell 脚本的学习 IFS解惑的相关文章

Linux Shell脚本编程学习笔记和实战

http://www.1987.name/141.html shell基础 终端打印.算术运算.常用变量 Linux下搜索指定目录下特定字符串并高亮显示匹配关键词 从键盘或文件中获取标准输入 [read命令] 文件的描述符和重定向 数组.关联数组和别名使用 函数的定义.执行.传参和递归函数 条件测试操作与流程控制语句 获取时间日期格式和延时 [date.sleep命令] 内部字段分隔符IFS和脚本的调试DEBUG 显示.读取或拼接文件内容 [cat命令] 文件查找与打印文件列表 [find命令]

Linux shell脚本基础学习详细介绍(完整版)一

Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提.1. Linux 脚本编写基础◆1.1 语法基本介绍 1.1.1 开头 程序必须以下面的行开始(必须方在文件的第一行): #!/bin/sh 符号#!用来告诉系统它后面的参数是用来执行该文件的程序.在这个例子中我们使用/bin/sh来执行程序. 当编辑好脚本时,如果要执行该脚本,还必须使其可执行. 要使脚本可执

Linux shell脚本基础学习详细介绍(完整版)二

详细介绍Linux shell脚本基础学习(五) Linux shell脚本基础前面我们在介绍Linux shell脚本的控制流程时,还有一部分内容没讲就是有关here document的内容这里继续. Linux shell脚本基础已经被分成好几个部分了,这里对控制流程的内容也就马上讲完了,这是最后一部分关于here document,这里举例稍微有点复杂,我们慢慢来分析这个复杂Linux shell脚本. 6. Here documents 当要将几行文字传递给一个命令时,here docu

Shell脚本的学习(一)

Shell脚本的学习(一) 一)代码式shell脚本简介 1.下载 Xshell 5 建一个文件夹 mkdri home/data ; 1)查看一个在data里建一个1.sh 查看是否建立成功. 2)并编辑echo hello word,并保存推出 3)运行脚本时发现没有权限.添加权限.   ```绿色可执行脚本 二)shell脚本命令 1>下载EverEdit编辑器,其他编辑器也可以的 安装后     2>变量: 1.当没有那个变量是结果取值为空,取值时${name}括号可加可不加    

shell脚本编程学习笔记(1)

在linux上编程,离不开shell,计划好好看看shell编程,并在这里做些笔记以供有相同兴趣的人分享,主要参考<shell脚本学习指南>. 学习shell脚本编程之前,需要了解脚本编程语言和编译型语言的概念. 一般很多中型.大型的程序是用编译型语言写成的,比如C.C++.Java等.这类程序从源代码编译成目标代码,直接通过计算机执行.编译型语言执行效率比较高,大多运作于底层,处理的是字节.整数.浮点数等机器层级的对象,因此实现一个具体的功能,比如"将一个目录里的所有文件复制到另外

Linux shell脚本-基础学习笔记

Linux脚本能力不是太强,最近再补习下,毕竟linux shell在日常工作中还是很普遍的, 用起来更方便.省时省力. 以下是学习笔记,偏理论,后面有几个例子,供参考. shell脚本组成元素系统命令.文本处理工具(grep\sed等).变量.条件判断.循环结构和函数 -------------------------------------------- 三剑客:grep,sed,awk,还有wc,sort,head等 ------------------------------------

shell脚本基础学习(转)

看到别人的学习总结,觉得不错转了过来(转自TryFly) 一.shell脚本基础 ? ?shell脚本是利用shell的功能所写的一个程序,这个程序是使用纯文本文件,将一些shell的语法与指令写在里面,然后用正则表达式,管道命令以及重定向向等功能,以达到我们所想要的处理目的.它的基本用途有: 1.自动化常用命令 2.执行系统管理和故障排除 3.创建简单的应用程序 4.处理文本或文件 ... 二.创建shell脚本 第一步.使用文本编辑器来创建文本文件 第一行必须包括shell 声明序列:#!

linux中shell脚本的学习(一)

linux中shell是一个特殊的应用程序.它介于系统的内核与用户之间.充当命令"解释器"的作用角色.负责接收用户输入的操作指令,并进行解释.将需要执行的操作传递给内核.并输出执行结果: 下面我们来看一下当前系统所支持的shell的种类: 其中/bin/shell 是目前大多数linux中采用的默认shell.我们主要学习bash. 我们来写第一个脚本文件用作测试. vim first.sh cd /boot/ pwd ls -lh * chmod +x first.sh shell脚

Shell脚本入门学习

2018-04-29 00:34:18编辑 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Linux 的 Shell 种类众多,常见的有: Bourne Shell(/usr/bin/sh或/bin/sh) Bourne Again Shell(/bin/bash) C Shell(/usr/bin/csh) K Shell(/usr/bin/ksh) Shell for Root(/sbin/sh) 由于易用和