了解IFS


71

该站点和StackOverflow上的以下几个线程有助于理解其IFS工作原理:

但是我还有一些简短的问题。我决定在同一篇文章中问他们,因为我认为这可能会对将来的读者有所帮助:

Q1。 IFS通常在“场分割”的上下文中讨论“场”。是场分裂一样的分词

Q2: POSIX规范

如果IFS的值为空,则不执行任何字段拆分。

设置IFS=是否与设置IFS为null 相同?这是将它设置为empty string太高的意思吗?

Q3:在POSIX规范中,我阅读以下内容:

如果未设置IFS,则外壳的行为应类似于IFS的值为 <space>, <tab> and <newline>

说我要还原默认值IFS。我怎么做?(更具体地说,我该如何指代<tab><newline>?)

Q4:最后,此代码将如何:

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file

如果我们将第一行更改为

while read -r line # Use the default IFS value

或者:

while IFS=' ' read -r line

Answers:


28
  1. 是的,它们是相同的。
  2. 是。
  3. 在bash和类似的shell中,您可以执行类似的操作IFS=$' \t\n'。否则,您可以使用插入文字控制代码[space] CTRL+V [tab] CTRL+V [enter]。但是,如果您打算这样做,最好使用另一个变量来临时存储旧IFS值,然后再将其还原(或使用var=foo command语法临时覆盖一个命令)。
    • 第一个代码段会将整行逐字读取到中$line,因为没有字段分隔符来执行单词拆分。但是请记住,由于许多外壳程序都使用cstrings存储字符串,因此NUL的第一个实例可能仍会导致其外观过早终止。
    • 第二个代码段可能未将输入的确切副本放入$line。例如,如果有多个连续的字段分隔符,它们将被制成第一个元素的单个实例。通常认为这是周围空白的损失。
    • 第三个代码段的作用与第二个代码段相同,只是它仅在一个空格(而不是通常的空格,制表符或换行符)上分割。

3
Q2的答案是错误的:空值IFS和未设置值IFS有很大不同。问题4的答案在某种程度上是错误的:这里没有触及内部分隔符,只有前导和尾随的分隔符。
吉尔斯

3
@吉尔斯:在第二季度中,三个给定的教派都没有提到一个未设置的教派IFS,所有教派都表示IFS=
斯特凡希门尼斯

@吉尔斯在第二季度,我从未说过他们是一样的。并触及内部分隔符,如下所示:IFS=' ' ; foo=( bar baz qux ) ; echo "${#foo[@]}"。(嗯,什么?那里应该有多个空格分隔符,所以SO引擎会继续剥离它们)。
克里斯·唐纳

2
@StéphaneGimenez,克里斯:哦,对,对不起,第二季度,我误解了问题。对于第四季度,我们正在谈论read;最后一个变量将捕获除最后一个分隔符以外的所有剩余内容,并在其中保留内部分隔符。
Gilles

1
Gilles对于未通过读取删除的空格部分正确。阅读我的答案以获取详细信息。

22

问题1:是的。“字段拆分”和“单词拆分”是同一概念的两个术语。

问题2:是的。如果IFS未设置(即unset IFS),则等效IFS于设置为$' \t\n'(空格,制表符和换行符)。如果IFS将if 设置为空值(这就是“ null”在这里的意思)(即,在IFS=or IFS=''或之后IFS=""),则根本不执行字段拆分(并且$*,通常使用的第一个字符$IFS,并且使用空格字符)。

问题3:如果要使用默认IFS行为,可以使用unset IFS。如果要IFS显式设置为此默认值,可以将文字字符的空格,制表符,换行符放在单引号中。在ksh93,bash或zsh中,可以使用IFS=$' \t\n'。可移植的是,如果您想避免在源文件中使用文字制表符,则可以使用

IFS=" $(echo t | tr t \\t)
"

Q4:IFS设置为空值时,read -r line设置line为除终止新行以外的整行。使用IFS=" ",将修剪行的开头和结尾的空格。使用默认值时IFS,会修剪制表符和空格。


2
Q2部分错误。如果IFS为空,则不带分隔符的情况下联接“ $ *”。(对于$@,非壳上下文中的shell之间会有一些变化IFS=; var=$@)。应当注意的是,当IFS为空时,不会进行分词,但是当$ var为空时,$ var仍会扩展为no参数,而不是空参数,并且仍然适用于glob,因此您仍然需要引用变量(即使您禁用通配符)
斯特凡Chazelas

13

Q1。字段拆分。

字段拆分与单词拆分一样吗?

是的,两者都指向同一个想法。

问题2:IFS何时为null

是否设置IFS=''为null,也设置为空字符串?

是的,所有三个含义相同:不得执行字段/单词拆分。此外,这会影响印刷领域(如echo "$*")的所有字段将与没有空间并置。

问题3 :(部分a)未设置IFS。

在POSIX规范中,我阅读以下内容

如果未设置IFS,则外壳的行为应类似于IFS的值为<space> <tab> <newline>

完全等同于:

如果使用unset IFS,则外壳将表现为默认IFS。

这意味着“字段拆分”将与默认的IFS值完全相同,或者未设置。
这并不意味着IFS在所有情况下都将以相同的方式工作。更具体地说,执行OldIFS=$IFS会将var设置OldIFSnull,而不是默认值。这样,尝试将IFS设置回去,IFS=OldIFS会将IFS设置为null,而不是像以前一样将其保持为未设置状态。小心 !!。

问题3:(b部分)还原IFS。

如何将IFS的值恢复为默认值。说我想恢复IFS的默认值。我怎么做?(更具体地说,如何引用<tab><newline>?)

对于zsh,ksh和bash(AFAIK),IFS可以设置为默认值,例如:

IFS=$' \t\n'        # works with zsh, ksh, bash.

完成后,您无需再阅读其他任何内容。

但是,如果需要为sh重新设置IFS,它可能会变得很复杂。

让我们从最简单的角度看一看,没有缺点(复杂度除外)。

1.-取消IFS。

我们可以unset IFS(请参阅上面的Q3部分a)。

2.-交换字符。

作为一种解决方法,交换tab和换行符的值可使设置IFS的值更简单,然后以等效的方式工作。

将IFS设置为<space> <newline> <tab>

sh -c 'IFS=$(echo " \n\t"); printf "%s" "$IFS"|xxd'      # Works.

3.-简单吗?解:

如果存在需要正确设置IFS的子脚本,则始终可以手动编写:

IFS ='   
'

手动键入的序列为:IFS='spacetabnewline',实际上已在上面正确键入的序列(如果需要确认,请编辑此答案)。但是从浏览器复制/粘贴将中断,因为浏览器将挤压/隐藏空白。很难共享上面编写的代码。

4.-完整的解决方案。

编写可以安全复制的代码通常涉及明确的可打印转义符。

我们需要一些“产生”期望值的代码。但是,即使在概念上正确,此代码也不会设置结尾\n

sh -c 'IFS=$(echo " \t\n"); printf "%s" "$IFS"|xxd'      # wrong.

发生这种情况的原因是,在大多数shell下,扩展时会删除所有结尾的换行符$(...)`...`命令替换。

我们需要对sh 使用技巧

sh -c 'IFS="$(printf " \t\nx")"; IFS="${IFS%x}"; printf "$IFS"|xxd'  # Correct.

另一种方法是将IFS设置为bash的环境值(例如),然后调用sh(接受通过环境设置的IFS的版本),如下所示:

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'

简而言之,sh使将IFS重置为默认值是一件很奇怪的事情。

问题4:在实际代码中:

最后,这段代码将如何:

while IFS= read -r line
do
    echo $line
done < /path_to_text_file

如果我们将第一行更改为

while read -r line # Use the default IFS value

或者:

while IFS=' ' read -r line

第一:我不知道echo $line豚鼠上是否有(带有VAR NOT引号)。它引入了读取所没有的第二级“字段拆分”。所以我都会回答。:)

使用此代码(以便您可以确认)。您将需要有用的xxd

#!/bin/ksh
# Correctly set IFS as described above.
defIFS="$(printf " \t\nx")"; defIFS="${defIFS%x}";
IFS="$defIFS"
printf "IFS value: "
printf "%s" "$IFS"| xxd -p

a='   bar   baz   quz   '; l="${#a}"
printf "var value          : %${l}s-" "$a" ; printf "%s\n" "$a" | xxd -p

printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x--          : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf 'Values      quoted :\n' ""  # With values quoted:
printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null    quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS default quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf '%s\n' "Values unquoted :"   # Now with values unquoted:
printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x-- unquoted : "
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null  unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS defau unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

我得到:

$ ./stackexchange-Understanding-IFS.sh
IFS value: 20090a
var value          :    bar   baz   quz   -20202062617220202062617a20202071757a2020200a
IFS --x--          :    bar   baz   quz   -20202062617220202062617a20202071757a202020
Values      quoted :
IFS null    quoted :    bar   baz   quz   -20202062617220202062617a20202071757a202020
IFS default quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS unset   quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS space   quoted :       bar   baz   quz-62617220202062617a20202071757a
Values unquoted :
IFS --x-- unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS null  unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS defau unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS unset unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS space unquoted : bar, baz, quz, 6261722c62617a2c71757a2c

第一个值只是的正确值 IFS='spacetabnewline'

下一行是var $a具有的所有十六进制值,最后是换行符'0a',因为它将被赋予每个读取命令。

IFS为null的下一行不执行任何“字段拆分”,但是将换行符删除(按预期方式)。

接下来的三行,因为IFS包含空格,请删除初始空格,并将var行设置为剩余余额。

最后四行显示未引用的变量将执行的操作。这些值将在(几个)空格上分开,并打印为:bar,baz,qux,


4

unset IFS 确实清除了IFS,即使此后IFS被假定为“ \ t \ n”:

$ echo "'$IFS'"
'   
'
$ IFS=""
$ echo "'$IFS'"
''
$ unset IFS
$ echo "'$IFS'"
''
$ IFS=$' \t\n'
$ echo "'$IFS'"
'   
'
$

在bash版本4.2.45和3.2.25上进行了测试,具有相同的行为。


现在的问题,以及相关的文档不谈论unsetIFS,因为在这里接受的答案的评论解释。
ILMostro_18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.