我有一个bash脚本,我用作助手脚本来生成git-bash中的差异。该脚本利用了我编写的名为菜单
的程序,该程序列出了作为命令行Args中传递的选择,并提示您选择一个。在这种情况下,选择是git提交。我在我的bash脚本中使用命令替换来调用菜单
程序,例如:
commit=$(menu --title "Choose a commit:" "CommitA" "CommitB" "CommitC")
菜单
程序正在使用 fgets(line,256,stdin);
要读取用户的选择,并将选项打印到 stderr
,以便在我的脚本中不会被命令替换捕获。当用户选择其中一个选项时,菜单
将该选项打印到 stdin
,以便可以通过命令替换来捕获。在下面的示例代码中,我将打印省略为 stdout
,因为它对后续 read read
命令的行为没有影响。之后,我提示用户使用读取
内置的输出目录:
read -p "Enter the output directory: " outputDir
出于某种原因,当我在cygwin中运行它时,它可以正常工作,但是当我在git-bash中运行它时,读取
立即命中EOF,不等待我输入文本。
菜单
程序的代码太长在这里列出,因此我将整个内容归结为最小可重现的示例。这是示例脚本和C代码。在示例中,我没有调用我的菜单
程序,而是写了一个名为提示
的小C程序,该程序只是打印一个简单的提示然后做 fgets
,它或多或少地模拟了菜单
程序正在做的事情。
> cat test.sh
x=$(./prompt)
read -p "read> " x
> cat prompt.c
#include <stdio.h>
int main(int argc, char **argv)
{
char line[256];
fprintf(stderr, "fgets> ");
fflush(stderr);
fgets(line, 256, stdin);
return 0;
}
当我运行此示例时,我在cygwin中获得的结果与git -bassh中的结果不同:
# Cygwin
> ./test.sh
fgets> foo
read> bar
# git-bash
> ./test.sh
fgets> foo
read> # <== read returns immediately and does not wait for input
我认为命令替代可能会留下一个角色粘在 stdin
buffer中,但是执行 echo -echo- n“ $ x” | wc -c
返回0,这表明没有读取输入。无论如何,在读取
之后,一切都开始按预期工作。例如,如果我在命令替换后立即添加一个虚拟读,则该脚本在git-bassh中按预期工作:
> cat test.sh
x=$(./prompt)
# Dummy read to clear stdin
read
# This works fine now in git-bash
read -p "read> " x
我不知所措地解释行为上的差异。同时,我找到了一个解决方法,该解决方法是使用 timeout
的读取
的读取
,以0.1秒的超时,以便它将从 stdin
中消耗外部 eof
,而无需要求用户击中Enter即可超越虚拟读取:
x=$(./prompt)
# Dummy read using timeout
read -t 0.1
read -p "read> " x
但是,似乎这应该可以正常工作,但没有虚拟阅读。
这是 bash
涉及的版本:
# Cygwin
> bash --version
GNU bash, version 4.4.12(3)-release (x86_64-unknown-cygwin)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
# git-bash
> bash --version
GNU bash, version 4.4.23(1)-release (x86_64-pc-msys)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
我还在Ubuntu 20.04上测试了此示例,运行Bash 5.0.17,并且效果很好。
I have a bash script that I use as a helper script for generating diffs in git-bash. The script makes use of a program I wrote called menu
that lists choices passed in as command line args, and prompts you to choose one. In this case, the choices are git commits. I call the menu
program using command substitution in my bash script, like so:
commit=$(menu --title "Choose a commit:" "CommitA" "CommitB" "CommitC")
The menu
program is using fgets(line, 256, stdin);
to read the user's selection, and it prints the choices to stderr
so that they won't be captured by the command substitution in my script. When the user selects one of the options, menu
prints that option to stdin
so it can be captured by the command substitution. In my example code below, I omit the printing to stdout
, because it has no effect on the behavior of the subsequent read
command. Afterwards, I prompt the user for an output directory using the read
builtin:
read -p "Enter the output directory: " outputDir
For some reason, when I run this in Cygwin, it works fine, but when I run it in git-bash, the read
hits EOF immediately and does not wait for me to enter text.
The code for the menu
program is too long to list here, so I've boiled the whole thing down to a minimal reproducible example. Here's the example script and C code. In the example, instead of calling my menu
program, I wrote a small C program called prompt
, which just prints a simple prompt to stderr
and then does an fgets
, which more or less simulates what the menu
program is doing.
> cat test.sh
x=$(./prompt)
read -p "read> " x
> cat prompt.c
#include <stdio.h>
int main(int argc, char **argv)
{
char line[256];
fprintf(stderr, "fgets> ");
fflush(stderr);
fgets(line, 256, stdin);
return 0;
}
When I run this example, I get different results in Cygwin than I do in git-bash:
# Cygwin
> ./test.sh
fgets> foo
read> bar
# git-bash
> ./test.sh
fgets> foo
read> # <== read returns immediately and does not wait for input
I thought perhaps the command substitution was leaving a character stuck in the stdin
buffer, but doing echo -n "$x" | wc -c
returns 0, which indicates no input was read. In any case, after the read
, everything starts working as expected again. For instance, if I add a dummy read
right after the command substitution, then the script works as expected in git-bash:
> cat test.sh
x=$(./prompt)
# Dummy read to clear stdin
read
# This works fine now in git-bash
read -p "read> " x
I am at a loss to explain this difference in behavior. In the meantime, I have found a workaround, which is to use the -t timeout
option of read
for the dummy read, with a timeout of 0.1 seconds, so that it will consume the extraneous EOF
from stdin
without requiring the user to hit Enter to get past the dummy read:
x=$(./prompt)
# Dummy read using timeout
read -t 0.1
read -p "read> " x
It seems like this should just work though, without the dummy read.
Here are the bash
versions involved:
# Cygwin
> bash --version
GNU bash, version 4.4.12(3)-release (x86_64-unknown-cygwin)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
# git-bash
> bash --version
GNU bash, version 4.4.23(1)-release (x86_64-pc-msys)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
I also tested this example on Ubuntu 20.04, running bash 5.0.17, and it works fine.
发布评论