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 "" |
来处理它。
总结
使用 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 "" |
来处理它。