Select case doesn't work as expected
I wrote this case select statement which refuses to work and I am unable to figure out what I am doing wrong. Any help is appreciated. Thanks.
echo; echo "Did you see a display?"
select YN in "yes" "no" ; do
case $YN in
yes)
echo "$2 at $X x $Y @$REF Hz">>/root/ran.log
break
;;
no)
echo "$2 at $X x $Y @$REF Hz">>/root/didntrun.log
break
;;
esac
done
This is the bash -x output of this part of the code. #? repeats many times on the screen and skips prompting the user and jumps to the next part of the code.
+ echo 'Did you see a display?'
Did you see a display?
+ select yn in '"yes"' '"no"'
1) yes
2) no
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#? + case $yn in
#?
EDIT: Figured this happens because the select code is being called in between a loop that reads lines from a file. The change in stdin to the file causes sel开发者_如何学编程ect to skip user prompt and tries to read from file. Now I need a way to sort this out.
EDIT 2: This is the code that makes the calls the fnUseResolution function inside which is the select statement.
res.log has resolution in each line in the format e.g. 1920 1080 60
FILE=res.log
BAKIFS=$IFS
IFS=$(echo -en "\n\b")
exec 3<&0
exec 0<$FILE
while read -r LINE
do
fnUseResolution $LINE app1
fnUseResolution $LINE app2
fnUseResolution $LINE app3
done
exec 0<&3
IFS=$BAKIFS
That works fine for me, it stops and lets me enter either 1
or 2
.
See the following transcript:
pax$ cat qq.sh
echo "Did you see a display?"
select YN in "yes" "no" ; do
case $YN in
yes)
echo YES
break
;;
no)
echo NO
break
;;
esac
done
pax$ ./qq.sh
Did you see a display?
1) yes
2) no
#? 1
YES
pax$ ./qq.sh
Did you see a display?
1) yes
2) no
#? 2
NO
pax$ _
Are you sure that the standard input is connected to your terminal at that point?
Since you've confirmed my suspicions that your standard input isn't connected to the terminal, there is a somewhat tricky way to fix it by fiddling with file handles.
Consider the following code which does what you indicated in your comment (calling a function accepting standard input from within a loop that has redirected standard input):
fn() {
echo "Did you see a display?"
select YN in "yes" "no" ; do
case $YN in
yes)
echo YES $YN $REPLY
break
;;
no)
echo NO $YN $REPLY
break
;;
esac
done
}
echo 'A
B
C
D' | while read ; do
echo $REPLY
fn
echo ====
done
If you run this code, you'll see:
A
Did you see a display?
1) yes
2) no
#? #? #? #?
=====
This indicates that the input was intermixed among the two readers, while read
and select
.
The trick to solving this is to disassociate the two readers by changing the code as follows:
(echo 'A
B
C
D' | while read ; do
echo $REPLY
fn <&4
done) 4<&0
The output for this is:
A
Did you see a display?
1) yes
2) no
#? 1 <- my input.
YES yes 1
=====
B
Did you see a display?
1) yes
2) no
#? 2 <- my input.
NO no 2
=====
C
Did you see a display?
1) yes
2) no
#? 1 <- my input.
YES yes 1
=====
D
Did you see a display?
1) yes
2) no
#? 2 <- my input.
NO no 2
=====
What this does (in its own hideous fashion) is start a subshell to run your whole thing, with the trick being that the subshell first connects the current standard input (terminal, probably) to file handle 4 for later use.
Inside the subshell, you run your normal while read
loop which changes the standard input (file handler 0) to read from the echo
statement.
But here's the trick: when you call your function, you tell it to take its standard input from file handle 4 rather than the current file handle 0. Since this is connected to the saved, original, standard input, it won't get anything from the while read
standard input (the output of echo
).
This may take some time to wrap your head around (and I recall having to pick my brains off the floor the first time I saw this, since my head exploded). But, once you understand how it works, you'll see its elegance :-)
The output for this is:
A
Did you see a display?
1) yes
2) no
#? 1
YES yes 1
=====
B
Did you see a display?
1) yes
2) no
#? 2
NO no 2
=====
C
Did you see a display?
1) yes
2) no
#? 1
YES yes 1
=====
D
Did you see a display?
1) yes
2) no
#? 2
NO no 2
=====
And, based on your code, probably a good starting point is to surround the whole lot to preserve standard input, something like:
(FILE=res.log
BAKIFS=$IFS
IFS=$(echo -en "\n\b")
exec 3<&0
exec 0<$FILE
while read -r LINE
do
fnUseResolution $LINE app1 <&4
fnUseResolution $LINE app2 <&4
fnUseResolution $LINE app3 <&4
done
exec 0<&3
IFS=$BAKIFS) 4<&0
I'm assuming here that your function is fnUseResolution
.
However, in looking at that code, it may be simpler to use a different file handle for the read
rather than the select
since you have more control over that than I thought.
Try out:
FILE=res.log
BAKIFS=$IFS
IFS=$(echo -en "\n\b")
#exec 3<&0
exec 4<$FILE
while read -u 4 -r LINE
do
fnUseResolution $LINE app1
fnUseResolution $LINE app2
fnUseResolution $LINE app3
done
#exec 0<&3
IFS=$BAKIFS
The -u 4
option to read tels it to use file handle 4 rather than 0 and I've changed the exec
to match that. I haven't tested it but it should work okay.
By doing it that way, you never change standard input so it should be okay within the function. Additionally, there's no reason to save/restore it in file handle 3 in that case, hence I've commented those exec
calls out.
This should work; Bash return the user response to variable $REPLY. This shall work, if you type in "yes" or "no".
echo; echo "Did you see a display?"
select YN in "yes" "no" ; do
case $REPLY in
yes)
echo "$2 at $X x $Y @$REF Hz">>/root/ran.log
break
;;
no)
echo "$2 at $X x $Y @$REF Hz">>/root/didntrun.log
break
;;
*)
echo "blah! blah! $REPLY"
break
esac
done
精彩评论