了解 IFS

Ame*_*ina 83 shell

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

但我还有一些简短的问题。我决定在同一篇文章中问他们,因为我认为这可能有助于更好的未来读者:

一季度。 IFS通常在“字段拆分”的上下文中讨论。是场分裂一样的分词

Q2: POSIX 规范

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

设置IFS=是否与设置IFS为空相同?这也是将其设置为 an 的意思empty string吗?

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

如果没有设置 IFS,shell 的行为就像 IFS 的值是 <space>, <tab> and <newline>

假设我想恢复IFS. 我怎么做?(更具体地说,我如何引用<tab><newline>?)

Q4:最后,这段代码如何:

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file
Run Code Online (Sandbox Code Playgroud)

如果我们将第一行更改为

while read -r line # Use the default IFS value
Run Code Online (Sandbox Code Playgroud)

或者:

while IFS=' ' read -r line
Run Code Online (Sandbox Code Playgroud)

Chr*_*own 31

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

  • Q2 的答案是错误的:空的“IFS”和未设置的“IFS”是非常不同的。Q4 的答案部分错误:这里没有触及内部分隔符,只有前导和尾随分隔符。 (3认同)
  • @Gilles:在第二季度,三个给定的面额都没有提到未设置的“IFS”,它们都表示“IFS=”。 (3认同)
  • @StéphaneGimenez, Chris:哦,对了,对 Q2 感到抱歉,我误读了这个问题。对于 Q4,我们谈论的是“阅读”;最后一个变量获取除最后一个分隔符之外的所有内容,并将内部分隔符留在里面。 (2认同)

Gil*_*il' 24

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

Q2:是的。如果IFS未设置(即在 之后unset IFS),则等效IFS于设置为$' \t\n'(空格、制表符和换行符)。如果IFS设置为空值(这就是“空”在这里的意思)(即后IFS=IFS=''IFS=""),无场分裂全部执行(和$*,通常使用的第一个字符$IFS,使用空格字符)。

Q3:如果你想拥有默认IFS行为,你可以使用unset IFS. 如果要IFS明确设置为该默认值,可以将文字字符空格、制表符、换行符放在单引号中。在 ksh93、bash 或 zsh 中,您可以使用IFS=$' \t\n'. 可移植地,如果您想避免在源文件中包含文字制表符,您可以使用

IFS=" $(echo t | tr t \\t)
"
Run Code Online (Sandbox Code Playgroud)

Q4:IFS设置为空值时,read -r line设置line为除终止换行符外的整行。使用IFS=" ",可以修剪行首和行尾的空格。使用默认值IFS,制表符和空格被修剪。

  • Q2 部分错误。如果 IFS 为空,则“$*”不带分隔符连接。(对于`$@`,在非列表上下文中的shell 之间存在一些差异,例如`IFS=; var=$@`)。需要注意的是,当 IFS 为空时,不进行分词,但 $var 为空时 $var 仍然扩展为 _no argument_ 而不是空参数,并且 globbing 仍然适用,因此您仍然需要引用变量(即使您禁用通配) (2认同)

小智 17

一季度。场分裂。

字段拆分与单词拆分相同吗?

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

Q2:IFS什么时候为

设置IFS=''是否与空相同,也与空字符串相同?

是的,这三个意思都是一样的:不得进行字段/单词拆分。此外,这会影响打印字段(如echo "$*"),所有字段都将连接在一起,没有空格。

Q3:(a 部分)未设置 IFS。

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

如果没有设置 IFS,shell 的行为就像 IFS 的值是<space><tab><newline>

这完全等同于:

使用unset IFS,shell 的行为就像 IFS 是默认值一样。

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

Q3:(b 部分)恢复 IFS。

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

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

IFS=$' \t\n'        # works with zsh, ksh, bash.
Run Code Online (Sandbox Code Playgroud)

完成了,您无需阅读其他内容。

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

让我们从没有缺点(复杂性除外)的最简单到完整来看看。

1.- 取消设置 IFS。

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

2.- 交换字符。

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

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

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

3.- 一个简单的?解决方案:

如果有需要正确设置 IFS 的子脚本,您可以随时手动编写:

IFS=”   
'

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

4.- 完整的解决方案。

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

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

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

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

我们需要对 sh使用一个技巧

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

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

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'
Run Code Online (Sandbox Code Playgroud)

简而言之,sh 使将 IFS 重置为默认值非常奇怪。

Q4:在实际代码中:

最后,这段代码如何:

while IFS= read -r line
do
    echo $line
done < /path_to_text_file
Run Code Online (Sandbox Code Playgroud)

如果我们将第一行更改为

while read -r line # Use the default IFS value
Run Code Online (Sandbox Code Playgroud)

或者:

while IFS=' ' read -r line
Run Code Online (Sandbox Code Playgroud)

第一:我不知道echo $line(没有引用 var)是否存在于海豚身上。它引入了 read 没有的第二级“字段拆分”。所以我会一一回答。:)

使用此代码(以便您确认)。您将需要有用的 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
Run Code Online (Sandbox Code Playgroud)

我得到:

$ ./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
Run Code Online (Sandbox Code Playgroud)

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

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

下一行,IFS 为空,不执行任何“字段拆分”,但换行符被删除(如预期的那样)。

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

最后四行显示了一个不带引号的变量会做什么。这些值将在(几个)空格上拆分,并将打印为:bar,baz,qux,