Bash IFS 忽略行尾的定界符

Bash IFS is ignoring the delimiter at the end of line

我有一个充满键值对的文件。我写了这个 shell 脚本,它读取每一行并拆分键值。

while IFS='=' read -r key value
do
   something

done < < application.properties.

其中一个 属性 看起来像这样 Connections/Database/Token=#!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw=

作为 运行 我的脚本,它可以拆分它但它忽略了行尾的字符 =

它给出

key = Connections/Database/Token
value = #!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw

但它应该像这样:

 key = Connections/Database/Token
 value = #!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw=

IFS 方法在这里肯定不好。试试这个。

while read -r item
do
    key="${item%%=*}"
    val="${item#*=}"
    echo "key = $key"
    echo "value = $val"
done < file

好吧,也许 IFS 也可以像这样工作

cat -E file | while IFS== read -r key value
do
    echo "key = $key"
    echo "value = ${value%$}"
done

我在 Ubuntu 18.04.3 LTS,GNU bash,版本 4.4.20(1)-release (x86_64-pc-linux-gnu) 使用了这个测试文件

$ cat file
Connections/Database/Token=#!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw1=
Conn/Database/Token=#!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw2
Connections/Data/Token=#!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw3=
Connection/Data/Token=#!VWdg5neXrFiIbMxtAzOwmH+fM2FNtk6QPLhgOHw3=test

我已经使用这个命令来查看发生了什么

cat -E file | while IFS== read -r key value last; do echo "key = $key"; echo "value = $value"; echo "last = ${last-empty}"; done
cat -E file | while IFS== read -r key value;      do echo "key = $key"; echo "value = $value"; echo "last = ${last-empty}"; done
cat    file | while IFS== read -r key value last; do echo "key = $key"; echo "value = $value"; echo "last = ${last-empty}"; done
cat    file | while IFS== read -r key value;      do echo "key = $key"; echo "value = $value"; echo "last = ${last-empty}"; done

TL;DR 将显式 = 添加到每个输入行的末尾,然后在使用它之前将其从结果值中删除。


为什么它会这样工作

参见 https://mywiki.wooledge.org/BashPitfalls#pf47. In short, the = in IFS is not treated as a field separator, but a field terminator, according to the POSIX definition of field-splitting

写的时候

IFS== read -r key value <<< "foo=var="

输入首先被分成两个字段,"foo"和"var"(不是"foo"、"var"和“”)。变量和字段一样多,所以你只需要得到 key=foo 和 value=var

如果你有

IFS== read -r key value <<< "foo=var=="

现在 三个字段:"foo"、"var" 和“”。因为只有两个变量,那么key=foo,赋值:

  1. 值"var",正常
  2. 输入"var"后的定界符“=”
  3. 来自输入的字段“”
  4. 输入中“”后面的分隔符“=”

有关每个变量的详细信息,请参阅 POSIX specification for read read 在输入进行字段拆分后为其分配一个值。

因此,永远不会有因字段拆分输入而产生的尾随空字段, 只有一个尾随 分隔符 被添加回最终变量。


如何保存输入

要解决此问题,请在您的输入中添加一个显式 =,然后将其从结果值中删除。

$ for input in "foo=bar" "foo=bar=" "foo=bar=="; do
> IFS== read -r name value <<< "$input="
> echo "${value%=}"
> done
bar
bar=
bar==

在你的情况下,这意味着使用一些东西

while IFS='=' read -r key value
do
   value=${value%=}
   ...    
done < < (sed 's/$/=/' application.properties)

或者,按照建议 ,使用参数扩展运算符来拆分输入,而不是让 read 这样做。

while read -r input; do
    key=${input%%=*}
    value=${input#*=}
    ...
done < application.properties

无论哪种方式,请记住, = 被视为此处的分隔符;如果您的属性看起来像 name = value 而不是 name=value.

,您可能需要 trim 键的尾随空格和值的前导空格