Проблема взаимодействия WSL2 вызывает преждевременный выход из цикла чтения в сценарии оболочки

#bash #shell #windows-subsystem-for-linux

Вопрос:

Краткие сведения

Использование цикла чтения, который запускает исполняемый файл Windows в сценарии оболочки WSL, приводит к выходу из цикла после первой итерации.

Подробные сведения

Я был совершенно сбит с толку тем, что кажется проблемой совместимости с запуском исполняемых файлов Windows из сценария оболочки в WSL2. Следующий цикл 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, и т. Д., И он все равно выполнял бы только первую итерацию. Это, по-видимому, проблема именно с чтением, поскольку этот цикл будет работать нормально.

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

Обходной путь

Благодаря этому сообщению я обнаружил, что обходной путь заключается в передаче по каналу фиктивной команды: echo "" | cmd.exe /C "echo "${line}"" . Примечание об этом исправлении заключается в том, что, похоже, работает только трубопровод. Перенаправление вывода или запуск его через другой уровень bash не: /bin/bash -c "cmd.exe /C "echo ${line}"" . Я частично публикую это для улучшения видимости для тех, у кого возникнет эта проблема в будущем, но мне все еще любопытно, есть ли у кого-нибудь какое-либо представление о том, почему существует эта проблема (возможно, из-за окончаний строк?). Спасибо!

Комментарии:

1. Возможно , power_shell читает с stdin , попробуйте использовать другой fd в цикле while read . Отказ от ответственности, я не использую power shell. Вопрос в том, зачем вообще использовать bash? Разве силовая оболочка не может выполнить цикл?

2. Аналогичная проблема возникает при использовании ssh в сценариях оболочки; вам нужно дать ему -n возможность запретить чтение stdin. -NonInteractive Помогает ли опция powershell?

3. Нет, все еще ломается. Однако стоит отметить, что проблема возникает даже с программами, которые (не должны?) буду читать дальше stdin . Например, изменение powershell на explorer.exe откроет только 1 окно вместо 3, как ожидалось.

4.Мое объяснение ниже достаточно длинно даже до того, как мы рассмотрим explorer.exe пример; -), но вполне вероятно, что WSL все еще вызывает CMD (или какой-либо промежуточный процесс взаимодействия с Windows) при вызове любого .exe . Поэтому я не удивлен, что неконсольные, не потребляющие stdin команды все еще «поглощают» stdin.

5. Спасибо за отличное объяснение! Это казалось таким странным, и мне было очень трудно найти какие-либо подобные проблемы в Интернете, поэтому я надеюсь, что это поможет кому-то еще в будущем 🙂

Ответ №1:

Короткий Ответ:

Немного улучшенное решение 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 и Маркплотник находятся на правильном пути в комментариях. Это, по-видимому, та же основная причина (и решение), что и в этом вопросе 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 оператору, но последующие строки являются stdin, которые используются 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, похоже, всегда использует любой stdin, доступный во время вызова, и добавляет его в переменную $input magic. С другой стороны, Bash не использует дополнительный stdin до тех пор, пока его явно не попросят:

 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 заключается в том, чтобы принудительно выполнить второе косвенное обращение, которое используется до желаемого ввода. 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 "" | помощью вместо этого.