WSL2 互操作问题导致 shell 脚本中的读取循环过早退出

WSL2 interop issue causes premature exit from read loop in shell script

总结

使用 WSL shell 脚本中 运行 的 windows 可执行文件的读取循环导致它在第一次迭代后退出循环。

详情

WSL2 中 shell 脚本中的 运行ning windows 可执行文件似乎存在互操作性问题,这让我感到非常困惑。下面的 while 循环应该打印 3 行,但它只会打印“第 1 行”。它已经在 Ubuntu 20.04 的 dash、bash 和 zsh 上进行了测试。

while read -r line; do
       powershell.exe /C "echo \"${line}\""
done << EOF
line 1
line 2
line 3
EOF

从文件而不是 heredoc 读取行时也会出现此问题,即使该文件具有 windows 行结尾。请注意,如果 powershell 更改为 /bin/bash 或任何其他本机可执行文件,这将打印 3 行。 powershell 也可以替换为任何 windows 可执行文件 cmd.exe、explorer.exe 等,它仍然只会 运行 第一次迭代。这似乎是 read specifically 的一个问题,因为这个循环可以正常工作。

for line in "line 1" "line 2" "line 3"
do
    powershell.exe /C "echo \"${line}\""
done

解决方法

感谢 this post 我发现了一个变通方法是通过一个虚拟命令进行管道传输:echo "" | cmd.exe /C "echo \"${line}\""。关于此修复的注意事项是只有管道似乎可以工作。将输出重定向或 运行 通过另一层 bash 将其 bash 不会:/bin/bash -c "cmd.exe /C \"echo ${line}\""。我部分 post 这样做是为了提高将来遇到此问题的任何人的可见性,但我仍然很好奇是否有人对为什么存在此问题有任何见解(可能是由于行尾?)。谢谢!

简答:

相对于 echo "" | 稍微改进的解决方案是从 /dev/null 进行二次重定向。这避免了 echo 的潜在换行问题,但还有其他解决方案:

while read -r line; do
       powershell.exe /C "echo \"${line}\"" < /dev/null
done << EOF
line 1
line 2
line 3
EOF

解释:

好吧,你已经有了解决方案,但你真正想要的是解释。

Jetchisel 和 MarkPlotnick 在评论中走在正确的轨道上。这似乎与 this question about ssh 中的根本原因(和解决方案)相同。要使用 ssh 复制您的示例(假设 ssh-agent 中有一个密钥,这样就不会生成密码提示):

while read -r line; do
  ssh hostname echo ${line}
  done << EOF
line 1
line 2
line 3
EOF

您将看到与 PowerShell 相同的结果 -- 仅显示“第 1 行”。

在这两种情况下,第一行转到 read 语句,但后续行是标准输入,由 powershell.exe(或 ssh)本身使用。

您可以通过对您的脚本稍作修改,在 PowerShell 中看到此“已证明”:

while read -r line; do
  powershell.exe -c "echo \"--- ${line} ---\"; $input"
  done << EOF
line 1
line 2
line 3
EOF

结果:

--- line 1 ---
line 2
line 3

后续问题是,恕我直言,为什么 bash 没有这个问题。答案是 PowerShell 似乎 总是 使用调用时可​​用的任何标准输入,并将其添加到 $input 魔术变量。另一方面,Bash 在明确询问之前不会消耗额外的标准输入:

while read -r line; do
  bash -c "echo --- \"${line}\" ---; cat /dev/stdin"
  done << EOF
line 1
line 2
line 3
EOF

生成与前面的 PowerShell 示例相同的结果:

--- line 1 ---
line 2
line 3

最终,PowerShell 的主要解决方案是强制在您的 desired 输入之前使用第二个间接寻址。 echo "" |可以这样做,但要注意:

while read -r line; do
  echo "" | powershell.exe -c "echo \"--- ${line} ---\"; $input" 
  done << EOF
line 1
line 2
line 3
EOF

结果:

--- line 1 ---

--- line 2 ---

--- line 3 ---

< /dev/null 没有这个问题,但你也可以用 echo -n "" | 来处理它。