我试图读取shell脚本中的TSV文件,发现当IFS设置为制表时,读取跳过空值。一个例子胜过1000个单词:
$ echo -e "atbtc" | while IFS=$'t' read v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
a - b - c
这项工作如预期
$ echo -e "attc" | while IFS=$'t' read v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
a - c -
我本来期望将$v2设置为null,并将$v3设置为"0";c";
$ echo -e "a||c" | while IFS=$'|' read v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
a - - c
以|作为分隔符,$v2得到一个空值,$v3得到一个值";c";正如我所期望的那样。
有人对使用|或\t时的不同行为有解释吗?还有一种让t表现得像|的方式?
有人对使用|或\t时的不同行为有解释吗?
从posix读取:
行应像shell中那样拆分为字段(请参阅字段拆分(;第一个字段应分配给第一个变量var,第二个字段分配给第二个变量var等等。如果指定的var操作数少于字段数,则剩余字段及其中间分隔符应分配给最后一个var。如果字段数少于vars,则剩余的vars应设置为空字符串。
因此,让我们转到posix shell字段拆分(emphasis mine(:
shell应将IFS的每个字符视为分隔符,并使用分隔符将参数扩展和命令替换的结果拆分为字段。
- 如果IFS的值是
<space>
、<tab>
和<newline>
,或者如果未设置。。。[此处不适用]- 如果IFS的值为null。。。[此处也不适用]
- 否则,应按顺序应用以下规则。术语";IFS空白区";用于表示IFS值中的空白字符的任何序列(零个或多个实例((例如,如果IFS包含
<space>
/<comma>
/<tab>
,则<space>
s和<tab>
s的任何序列都被视为IFS空白(。
- IFS空白应在输入的开始和结束处忽略
- 非IFS空白的IFS字符的输入中的每一次出现,以及任何相邻的IFS空白,都应划定一个字段,如前所述
- 非零长度IFS空白应界定字段
当IFS
设置为空白空间的任何组合时,则在分割字段时将这些空白空间连接在一起(即"非zer长度"(。
所以echo -e "attc" | IFS=$'t' read v1 v2 v3
等于echo -e "atttttc" | IFS=$'t' read v1 v2 v3
。因为存在";是比vars"更少的字段;(2对3(,v3
被设置为空字符串。
但是,当IFS
设置为除空白之外的任何其他字符时,该IFS
字符的每次出现都会拆分字段。
还有一个有趣的角落案例,空白字符被特殊处理。
还有一种让\t表现得像|的方式?
在bash中,在读取之前将其替换为唯一的内容。我喜欢使用0x01
字节:
echo -e "attc" |
tr 't' $'x01' |
while IFS=$'x01' read -r v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
记住使用read -r
。