本网站和 StackOverflow 上的以下几个主题有助于理解IFS
其工作原理:
但我还有一些简短的问题。我决定在同一篇文章中询问他们,因为我认为这可能会帮助更好的未来读者:
Q1. IFS
通常在“字段分割”的上下文中讨论。是场分裂与分词?
问题2:POSIX 规范说:
如果IFS的值为空,则不进行字段分割。
设置IFS=
和设置为null一样吗IFS
?这就是设置为 a 的意思吗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
如果我们将第一行更改为
while read -r line # Use the default IFS value
或者:
while IFS=' ' read -r line
答案1
- 是的,它们是一样的。
- 是的。
- 在 bash 和类似的 shell 中,您可以执行类似的操作
IFS=$' \t\n'
。否则,您可以使用 插入文字控制代码[space] CTRL+V [tab] CTRL+V [enter]
。但是,如果您打算执行此操作,最好使用另一个变量来临时存储旧IFS
值,然后再恢复它(或使用语法暂时覆盖一个命令的旧值var=foo command
)。 -
- 第一个代码片段会将读取的整行逐字放入 中
$line
,因为没有字段分隔符来执行分词。但请记住,由于许多 shell 使用 cstring 来存储字符串,因此 NUL 的第一个实例仍可能导致其出现过早终止的情况。 - 第二个代码片段可能不会将输入的精确副本放入
$line
.例如,如果有多个连续的字段分隔符,它们将被制作为第一个元素的单个实例。这通常被认为是周围空白的丢失。 - 第三个代码片段的作用与第二个相同,只是它只会在空格上分割(不是通常的空格、制表符或换行符)。
- 第一个代码片段会将读取的整行逐字放入 中
答案2
问1:是的。 “字段分割”和“字分割”是同一概念的两个术语。
问2:是的。如果IFS
未设置(即在 之后unset IFS
),则相当于IFS
设置为$' \t\n'
(空格、制表符和换行符)。如果IFS
设置为空值(这就是“null”在这里的意思)(即在IFS=
或IFS=''
或后面IFS=""
),则根本不执行任何字段分割(并且$*
,通常使用 的第一个字符$IFS
,使用空格字符)。
Q3:如果你想有默认IFS
行为,你可以使用unset IFS
.如果要IFS
显式设置此默认值,可以将文字字符空格、制表符、换行符放在单引号中。在 ksh93、bash 或 zsh 中,您可以使用IFS=$' \t\n'
.可移植的是,如果您想避免源文件中出现文字制表符,您可以使用
IFS=" $(echo t | tr t \\t)
"
Q4:IFS
设置为空值时,read -r line
设置line
为除终止换行符之外的整行。使用 时IFS=" "
,行首和行尾的空格将被修剪。使用默认值 时IFS
,制表符和空格将被修剪。
答案3
Q1.场分裂。
字段分割与字分割相同吗?
是的,两者都指向同一个想法。
Q2:什么时候IFS 空?。
设置
IFS=''
与 null 相同,也与空字符串相同吗?
是的,这三个含义相同:不应执行字段/字拆分。此外,这会影响打印字段(与 一样echo "$*"
),所有字段都将连接在一起,没有空格。
Q3:(a 部分)取消设置 IFS。
在 POSIX 规范中,我阅读以下:
如果未设置 IFS,shell 的行为将如同 IFS 的值是<空格><制表符><换行>。
这完全等同于:
使用 时
unset IFS
,shell 的行为就像 IFS 是默认的一样。
这意味着“字段分割”将与默认 IFS 值完全相同,或者未设置。
这并不意味着 IFS 在所有条件下都以相同的方式工作。更具体地说,执行OldIFS=$IFS
会将 var 设置OldIFS
为无效的,不是默认值。尝试将 IFS 设置回来,因为这样,IFS=OldIFS
会将 IFS 设置为 null,而不是像以前一样保持未设置状态。小心 !!。
Q3:(b 部分)恢复 IFS。
如何将 IFS 的值恢复为默认值。假设我想恢复 IFS 的默认值。我怎么做? (更具体地说,我如何参考<选项卡>和<换行>?)
对于 zsh、ksh 和 bash (AFAIK),IFS 可以设置为默认值:
IFS=$' \t\n' # works with zsh, ksh, bash.
完成后,您无需再阅读其他内容。
但如果需要为sh重新设置IFS,可能会变得复杂。
让我们从最简单到没有缺点(复杂性除外)的方式来看看。
1.- 取消设置 IFS。
我们可以unset IFS
(阅读上面的第三季度 a 部分)。
2.- 交换字符。
作为一种解决方法,交换制表符和换行符的值可以更简单地设置 IFS 的值,然后以等效的方式工作。
将 IFS 设置为<空格><换行><制表符>:
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 重置为默认值成为一次相当奇怪的冒险。
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
第一:我不知道echo $line
(未引用 var 的)是否存在于 porpouse 上。它引入了 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
我得到:
$ ./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 为空的下一行不执行任何“字段分割”,但换行符被删除(如预期)。
接下来的三行,由于 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 上进行了测试,具有相同的行为。