【Linux】IFS是个什么鬼

一、IFS引入

        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 substituioin.”。

        IFS有什么用?IFS的默认值是空格、制表符和换行符,此处不再赘述,请参看:

        《Shell中的IFS解惑》

        《shell中的IFS变量》

        IFS和参数输入牵扯颇多,举个最简单的例子,为什么给变量赋值的时候,如果有空格,需要将value用引号包起来。了解IFS,对shell编程助益颇多。


二、IFS,TMD撞鬼了

        今天整理Linux环境变量的时候,发现粗大事了,IFS撞鬼了,从早上求证到现在,没有答案。

1. 背景

a. IFS放在哪里

        登录系统之后,找寻IFS的身影。查找登录shell中全局环境变量,env |grep IFS,不在;查找登录shell中局部环境变量,set |grep IFS,存在。也就是说,IFS存在于登录shell的局部环境变量中

[work@localhost ~]$ env |grep IFS
[work@localhost ~]$ set |grep IFS
IFS=$' \t\n'

b. IFS用在哪里

        结合上一篇博客《完全解读Linux环境变量》已验证的内容可知,局部变量的作用域只在当前的shell进程环境中,不能用在父shell环境,也不能用在子shell环境。那么IFS作为登录shell的局部环境变量,其作用域只在登录shell环境中。此处,再次验证一下,在登录shell换进各种设置局部变量testing="zhangsan",然后在脚本中访问该局部变量,如果访问不到,则证明局部变量没有被fork出的子shell环境拥有。

[work@localhost local]$ testing="zhangsan"
[work@localhost local]$ cat run.sh 
#!/bin/bash

echo $testing
[work@localhost local]$ sh run.sh 

[work@localhost local]$ 
        最终执行脚本,没有访问到定义的局部变量testing,证明 局部环境变量不能被子shell进程拥有

c. 没有个性化

        结合上一篇博客《完全解读Linux环境变量》已验证的内容可知,启动shell、登录shell、交互shell,在进入的时候,都会预先加载、执行一些文件,这个过程可以理解成“环境初始化”或者“个性化”,当然,非交互shell(也就是执行脚本)也会有这么一个过程,即环境变量BASE_ENV,但是默认情况下这个环境变量没有设置,是不存在的,也就是说,通常情况下,执行脚本是没有个性化的。

2. 疑问

        如果已经弄清楚背景所述的两个前提,那么问题来了。

       -------------------------------------------------------------------------------

        1. IFS作为登录shell的局部环境变量;

        2. 局部环境变量的作用域是当前shell进程;

        3. 执行脚本的时候,是没有个性化(或者”环境初始化“)的;

        -------------------------------------------------------------------------------

        problem:为什么脚本中可以使用IFS?

        重述一遍问题:IFS作为登录shell的局部环境变量,为什么能在脚本中使用?

        -------------------------------------------------------------------------------

        事实上,你确实可以在IFS中使用,而且IFS的值和登录shell中是一致的,这不是粗大事撞鬼了吗?我是在是不能理解,搞了一天了也没弄明白,求大神解答!!!

3. 解答

  Linux中每个进程都有自己的环境(main函数的char *env[]参数指向),环境是由一组变量组成的,这些变量中存有进程可能需要引用的上下文信息。bash将环境变量的复本保存在variables.c中名为shell_variables的全局VAR_CONTEXT结构中。要导出给子进程的变量由全局字符串指针char **export_env记录,形式是“名=值”字符串数组,也就是键入export命令看到的内容。

  bash启动后,调用variables.c中的initialize_shell_variables()函数,传入来自main函数的env参数,将env中的环境变量存入shell_variables。对于PATH、IFS、PS1之类bash本身要使用的环境变量,如果env中尚无,则在此时建立。另外一些有关bash版本、命令历史、邮件检查等内部辅助功能的环境变量也在这里建立。

  execute_cmd.c中调用各类命令的函数在执行命令之前,首先调用variables.c中的maybe_make_export_env()函数,构建导出给子进程的环境,即export_env。shell_execve()执行外部命令时使用的是exec族中的execve()函数,因此可以将export_env传递给bash启动的子进程。

  凡需要增改环境变量的地方,调用variables.c中的bind_variable()函数实现。例如在cd命令执行后需要重设PWD。

摘自:点击打开链接


三、IFS和$巧妙的苟合

        说IFS和$苟合,这完全是诬陷。

1. 我们是两码事

        IFS和$、单引号、双引号那是两码事,一个是男人,一个是女人,他们只是见了一面握了一下手,然后被不明真相的人看到了,就传说成了“苟合”。

        IFS常常在read命令、参数扩展和命令替换中用于分隔数据,而$' '和$" "是bash shell中Quoting的语法规定。你可以通过man bash指令查看Quoting一节了解更详细的内容,下图摘选了与本文有关的部分内容截图。


        鉴于CSND图片上传太烂了(后台在新老版本交替的情况下,bug无限啊),常常出现大小图的情况,我直接粘贴文本过来吧。

Words  of  the form $'string' are treated specially.  The word expands to string, with backslash-escaped characters replaced as specified
by the ANSI C standard.  Backslash escape sequences, if present, are decoded as follows:
	  \a     alert (bell)
	  \b     backspace
	  \e
	  \E     an escape character
	  \f     form feed
	  \n     new line
	  \r     carriage return
	  \t     horizontal tab
	  \v     vertical tab
	  \\     backslash
	  \'     single quote
	  \"     double quote
	  \nnn   the eight-bit character whose value is the octal value nnn (one to three digits)
	  \xHH   the eight-bit character whose value is the hexadecimal value HH (one or two hex digits)
	  \cx    a control-x character

The expanded result is single-quoted, as if the dollar sign had not been present.

A double-quoted string preceded by a dollar sign ($"string") will cause the string to be translated according to the current locale.   If
the  current  locale  is  C  or  POSIX, the dollar sign is ignored.  If the string is translated and replaced, the replacement is double-
quoted.

2. 我们在一起了

        常常把IFS和$、单双引号放在一起讨论,一个原因是IFS的默认值定义IFS=$' \t\n',另一个主要原因就是大多数人在写shell脚本的时候,需要修改IFS的值,会纠结要不要加上“$“符号,加了能用,不加好像也是能用的,那到底是加还是不加呢?

        加还是不加,根据需要来决定,因为中间有那么一小丢丢的差别。请往下看。

3. IFS、$和单引号

        为了方便数据处理,在脚本中往往需要修改value,举个栗子,如果你不想使用默认值来分隔,而是需要使用逗号来分隔,你可以这么定义IFS=',',你也可以这么定义IFS=$',',你还可以这么定义IFS=$",",那他们有什么区别?首先不要把简单问题复杂化,这三句话都是Linux shell中的赋值语句,将左边的语句翻译后赋值给右边,所以直接探讨右边的事情会更直接(如以下代码所示),但是不好意思,在这里他们没区别,都是指代一个简简单单普普通通的逗号。

[work@localhost bin]$ echo ","
,
[work@localhost bin]$ echo $','
,
[work@localhost bin]$ echo $","
,
        意外来了。IFS默认值是空格、制表符和换行符,但是如果你不想用那么多,只想要用制表符或者换行符中的一个来做分隔(这里以换行符来进行讨论,因为比较容易观察),你同样可以用以上三种方式进行IFS的重新赋值,IFS='\n',IFS=$"\n",IFS=$'\n',这一下就有区别了,第一个就是普通字符反斜杠和n,第二个是换行符,不过只有在执行的时候才会进行转化,第三个是一个已经转换了的回车符NL,在屏幕上直接就回车了。

[work@localhost bin]$ echo '\n' 
\n
[work@localhost bin]$ echo $"\n"
\n
[work@localhost bin]$ echo $'\n'


[work@localhost bin]$ 
        回顾上文中man bash的定义,$、转义字符、单引号在一起,“Backslash escape sequences, if present, are decoded as follows:选项, The expanded result is single-quoted, as if the dollar sign had not been present.”,反斜线转译字符,如果这个字符存在于以下选项中,那么就进行解码,因此$'\n'最终被解码成了Linux的换行命令。

        ------------------------------------------------------------------------------------------------------------------------------

        伪结论:

                IFS的值如果是普通字符,那么加不加$都没有关系,因为表现是一致的;

                IFS的值如果是系统定义的以上(截图或引用的代码中有示例)特殊字符,那么加上$是会被系统解码转译的,其表现的的确确就是按照换行符来分隔;不加那就是普通字符,但是在解析的过程中又被转译字符转译,所以在做分隔的时候,除了数据本身,”\n"也被认为是数据,因此也会被单独的分割出来,请看以下脚本调试信息,"\n"被当作数据对待,也被分隔出来了,但其本身不作为分隔符,因为转译字符的缘故,在被解析的时候解析成了换行。更多有趣的验证代码,在此不一一列举。

#!/bin/bash

IFS='\n'
str="a\nb\nc\nd\\n"
echo $str
for i in $str
do
        echo $i
done

echo "------"

str2="anbncndn"
echo $str2
for ii in $str2
do
        echo $ii
done
[work@localhost local]$ sh -x run2.sh 
+ IFS='\n'
+ str='a\nb\nc\nd\n'
+ echo a '' b '' c '' d ''
a  b  c  d 
+ for i in '$str'
+ echo a
a
+ for i in '$str'
+ echo

+ for i in '$str'
+ echo b
b
+ for i in '$str'
+ echo

+ for i in '$str'
+ echo c
c
+ for i in '$str'
+ echo

+ for i in '$str'
+ echo d
d
+ for i in '$str'
+ echo

+ echo ------
------
+ str2=anbncndn
+ echo a b c d
a b c d
+ for ii in '$str2'
+ echo a
a
+ for ii in '$str2'
+ echo b
b
+ for ii in '$str2'
+ echo c
c
+ for ii in '$str2'
+ echo d
d
        ------------------------------------------------------------------------------------------------------------------------------

        结论:

         1. IFS中的字符可以是空白符(空格、制表符、回车符)和非空白符,如果是空白符,则数据前后的空白符会被忽略;如果是非空白符,则不会被忽略;

         2. 如果有多个连续的空白符,则并列成为一个分隔符,而多个连续的非空白符,则被认为是多个分隔符;

         3. 如果你定义的IFS值和系统预留的特殊字符相同,并且你希望其能预先解码成系统特殊字符使用,那么请用$修饰;否则,其效果和直接在code中写特殊字符一样,会等到使用的时候才转码成系统特殊字符;

        ------------------------------------------------------------------------------------------------------------------------------

4. IFS、$和双引号

        在Linux shell中的表现和没有$符号一致,此处我也理解不深刻,略了,sorry···

5. 为苟合正名

        IFS和$在一起,不是苟合


附注:

        本文如有错漏,烦请不吝指正,谢谢!

        本文第二节所提疑问,烦请大神不吝赐教,在下不甚感激!

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值