问题描述
我试图在 shell 脚本中读取 TSV 文件,发现当 IFS 设置为制表时,读取跳过空值。一个例子胜过 1000 字:
$ echo -e "a\tb\tc" | while IFS=$'\t' read v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
a - b - c
这项工作符合预期
$ echo -e "a\t\tc" | while IFS=$'\t' read v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
a - c -
我希望将 $v2 设置为 null,将 $v3 设置为 "c"
$ echo -e "a||c" | while IFS=$'|' read v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
a - - c
与 |作为分隔符,$v2 获得空值,$v3 获得值“c”,正如我所期望的。
任何人都可以解释使用 | 时的不同行为或 \t ?还有一种让 \t 表现得像 for | 的方法?
解决方法
任何人都可以解释使用 | 时的不同行为或 \t ?
来自posix read:
该行应像在 shell 中一样拆分为字段(参见 Field Splitting);第一个字段应分配给第一个变量 var,第二个字段应分配给第二个变量 var,依此类推。如果指定的 var 操作数少于字段数,则剩余的字段及其中间分隔符应分配给最后一个 var。如果字段少于 vars,则剩余的 vars 应设置为空字符串。
那么让我们转到posix shell field splitting(强调我的):
Shell 将 IFS 的每个字符视为一个分隔符,并使用分隔符将参数扩展和命令替换的结果拆分为字段。
- 如果 IFS 的值是
<space>
、<tab>
和<newline>
,或者如果未设置,... [不适用这里] - 如果 IFS 的值为 null,... [这里也不适用]
- 否则,应依次应用以下规则。术语“IFS 空格”用于表示 IFS 值中的任何空格字符序列(零个或多个实例)(例如,如果 IFS 包含
<space>
/ { {1}}/<comma>
,任何<tab>
和<space>
序列都被视为 IFS 空白)。- 应忽略输入开头和结尾的 IFS 空格。
- 每次出现IFS 字符非 IFS 空格,以及任何相邻的IFS em> 空格,应界定一个字段,如前所述。
- 非零长度的 IFS 空格应界定一个字段。
当 <tab>
设置为任意空格组合时,这些空格在拆分字段时会连接在一起(即“非零长度”)。
所以 IFS
等于 echo -e "a\t\tc" | IFS=$'\t' read v1 v2 v3
。因为“字段比 var 少”(2 对 3),echo -e "a\t\t\t\t\tc" | IFS=$'\t' read v1 v2 v3
被设置为空字符串。
但是当 v3
设置为除空格之外的任何其他内容时,该 IFS
字符的每次出现都会拆分字段。
另一个有趣的角落案例,其中空白字符被特殊对待。
还有一种让 \t 表现得像 for | 的方法?
在 bash 中,在阅读之前将其替换为独特的内容。我喜欢使用 IFS
字节:
0x01
记得使用 echo -e "a\t\tc" |
tr '\t' $'\x01' |
while IFS=$'\x01' read -r v1 v2 v3; do echo "$v1 - $v2 - $v3"; done
。