Execute a command after some time if no input from user
Try with read
command to get runtime the input from the user. Since it has timeout option.
From man:
-t timeout Cause read to time out and return failure if a complete line of input is not read within timeout seconds. timeout may be a decimal number with a fractional portion following the decimal point. This option is only effective if read is reading input from a terminal, pipe, or other special file; it has no effect when reading from regular files. If timeout is 0, read returns success if input is available on the specified file descriptor, failure otherwise. The exit status is greater than 128 if the timeout is exceeded.
Your attempt with sleep
would not work as the sleep
call and the subsequent test on $flag_cancel
happens in a background job. Any change to the variable flag_cancel
in the main part of the code would not affect the value of the variable in the backgrounded subshell and the code would unconditionally suspend the system after 10 seconds.
Instead, you can use the fact that both read
and select
times out after $TMOUT
seconds in bash
.
Here's a variation on the theme of your first piece of code:
suspend_maybe ()
{
local PS3='Please confirm/cancel system suspension: '
local TMOUT=10
local do_suspend=true
select confirmation in confirm cancel; do
case $REPLY in
1)
# default case
break ;;
2)
do_suspend=false
break ;;
*)
echo 'Sorry, try again' >&2
esac
done
if "$do_suspend"; then
echo 'Suspending...'
systemctl suspend
else
echo 'Will not suspend'
fi
}
Changes made:
- The function is now called
suspend_maybe
since there is already a built-insuspend
utility inbash
. - The
select
loop usesPS3
for its prompt. - The
select
loop times out after$TMOUT
seconds. - We use the digits in the
case
statement. That way we don't have to type all the strings in twice. The value of$REPLY
will be whatever the user types in. - We only need the
select
loop to tell us whether the user wants to cancel the suspension of the system. We treat suspension as the default action. - Once we're out of the
select
loop, we suspend the system unless the user chose to cancel that action.
The same thing but with an input loop using read
as a drop-in replacement for select
:
suspend_maybe ()
{
local PS3='Confirm system suspension [y]/n: '
local TMOUT=10
local do_suspend=true
while true; do
if ! read -p "$PS3"; then
# timeout
break
fi
case $REPLY in
[yY]*)
# default case
break ;;
[nN]*)
do_suspend=false
break ;;
*)
echo 'Sorry, try again' >&2
esac
done
if "$do_suspend"; then
echo 'Suspending...'
systemctl suspend
else
echo 'Will not suspend'
fi
}
Here, the user may enter any string starting with n
or N
to cancel the suspension. Letting the read
time out, entering a word starting with y
or Y
, or pressing Ctrl+D, would suspend the system.
With the loop above, it is easier to catch the timeout case, in case you want to do anything special when the suspension is happening due to a user not responding to the prompt. The break
in the if
-statement would be triggered whenever the read
call fails, which it does upon timing out (or when the input stream is closed).
In general, an alternative you have is timeout
from GNU coreutils. Not the best choice here, because we are setting a timeout for a builtin command and an external utility can only do it by spawning a whole shell:
if ! timeout 10 bash -c '
select conf in yes no
do
case $conf in
(yes) exit 1;;
(no) exit 0;;
esac
done'
then
echo 'Suspending'
fi
Likely more interesting if you are going to ask for input from the user through some external tool, possibly graphical (several of them implement their own timeout mechanism, though).
Note that timeout
needs the --foreground
option when not invoked directly from a shell prompt (for instance, if you invoke it in subshell: ( timeout ... )
) and it needs to read from the terminal.