【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和參數輸入牽扯頗多,舉個最簡單的例子,為什麼給變數賦值的時候,如果有空格,需要將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 IFSIFS=$' \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/bashecho $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中是一致的,這不是粗大事撞鬼了嗎?我是在是不能理解,搞了一天了也沒弄明白,求大神解答!!!
三、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 specifiedby 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 characterThe 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. Ifthe 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/bashIFS='\n'str="a\nb\nc\nd\\n"echo $strfor i in $strdo echo $idoneecho "------"str2="anbncndn"echo $str2for ii in $str2do echo $iidone
[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 aa+ for i in '$str'+ echo+ for i in '$str'+ echo bb+ for i in '$str'+ echo+ for i in '$str'+ echo cc+ for i in '$str'+ echo+ for i in '$str'+ echo dd+ for i in '$str'+ echo+ echo ------------+ str2=anbncndn+ echo a b c da b c d+ for ii in '$str2'+ echo aa+ for ii in '$str2'+ echo bb+ for ii in '$str2'+ echo cc+ for ii in '$str2'+ echo dd
------------------------------------------------------------------------------------------------------------------------------
結論:
1. IFS中的字元可以是空白符(空格、定位字元、斷行符號符)和非空白符,如果是空白符,則資料前後的空白符會被忽略;如果是非空白符,則不會被忽略;
2. 如果有多個連續的空白符,則並列成為一個分隔字元,而多個連續的非空白符,則被認為是多個分隔字元;
------------------------------------------------------------------------------------------------------------------------------
4. IFS、$和雙引號
在Linux shell中的表現和沒有$符號一致,此處我也理解不深刻,略了,sorry···
5. 為苟合正名
IFS和$在一起,不是苟合!