30 votes

Bash : Comment mettre fin à une boucle infinie en appuyant sur n'importe quelle touche ?

Je dois écrire une boucle infinie qui s'arrête lorsqu'une touche est pressée.

Malheureusement, celui-ci ne boucle que lorsqu'une touche est pressée.

Des idées, s'il vous plaît ?

#!/bin/bash

count=0
while : ; do

    # dummy action
    echo -n "$a "
    let "a+=1"

    # detect any key  press
    read -n 1 keypress
    echo $keypress

done
echo "Thanks for using this script."
exit 0

37voto

Sam Hocevar Points 7554

Vous devez mettre l'entrée standard en mode non bloquant. Voici un exemple qui fonctionne :

#!/bin/bash

if [ -t 0 ]; then
  SAVED_STTY="`stty --save`"
  stty -echo -icanon -icrnl time 0 min 0
fi

count=0
keypress=''
while [ "x$keypress" = "x" ]; do
  let count+=1
  echo -ne $count'\r'
  keypress="`cat -v`"
done

if [ -t 0 ]; then stty "$SAVED_STTY"; fi

echo "You pressed '$keypress' after $count loop iterations"
echo "Thanks for using this script."
exit 0

Edit 2014/12/09 : Ajouter le -icrnl pour stty pour attraper correctement la touche Retour, utilisez cat -v au lieu de read afin d'attraper l'Espace.

Il est possible que cat lit plus d'un caractère si les données lui sont fournies assez rapidement ; si ce n'est pas le comportement souhaité, remplacez cat -v con dd bs=1 count=1 status=none | cat -v .

Editer le 05/09/2019 : Utilisez stty --save pour restaurer les paramètres TTY.

10voto

Paul Points 2497

read a un paramètre de nombre de caractères -n et un paramètre de délai d'attente -t qui pourrait être utilisé.

De manuel bash :

-n nchars read retourne après avoir lu nchars caractères plutôt que d'attendre une ligne d'entrée complète, mais honore un délimiteur si moins de nchars caractères sont lus avant le délimiteur.

-t timeout

Cause lire pour mettre fin à la lecture et retourner un échec si une ligne complète d'entrée (ou un nombre spécifié de caractères) n'est pas lue dans les secondes du délai d'attente. Le délai d'attente peut être un nombre décimal avec une partie fractionnaire suivant le point décimal. Cette option n'est effective que si read lit des données provenant d'un terminal, d'un tube ou d'un autre fichier spécial ; elle n'a aucun effet sur la lecture de fichiers ordinaires. Si read expire, read enregistre toute entrée partielle lue dans le nom de variable spécifié. Si le délai d'attente est de 0, read revient immédiatement, sans essayer de lire aucune donnée. L'état de sortie est 0 si l'entrée est disponible sur le descripteur de fichier spécifié, non-zéro sinon. L'état de sortie est supérieur à 128 si le délai d'attente est dépassé.

Cependant, le buildin de lecture utilise le terminal qui a ses propres paramètres. Ainsi, comme d'autres réponses l'ont souligné, nous devons définir les drapeaux pour le terminal en utilisant la commande stty .

#!/bin/bash
old_tty=$(stty --save)

# Minimum required changes to terminal.  Add -echo to avoid output to screen.
stty -icanon min 0;

while true ; do
    if read -t 0; then # Input ready
        read -n 1 char
        echo -e "\nRead: ${char}\n"
        break
    else # No input
        echo -n '.'
        sleep 1
    fi       
done

stty $old_tty

3voto

mouviciel Points 36624

D'habitude, cela ne me dérange pas de rompre une boucle infinie en bash avec un simple CTRL-C. C'est la manière traditionnelle de mettre fin à une tail -f par exemple.

1voto

F. Hauri Points 5893

Pure bash : entrée utilisateur non surveillée sur la boucle

Je l'ai fait sans avoir à jouer avec la stty :

loop=true
while $loop; do
    trapKey=
    if IFS= read -d '' -rsn 1 -t .002 str; then
        while IFS= read -d '' -rsn 1 -t .002 chr; do
            str+="$chr"
        done
        case $str in
            $'\E[A') trapKey=UP    ;;
            $'\E[B') trapKey=DOWN  ;;
            $'\E[C') trapKey=RIGHT ;;
            $'\E[D') trapKey=LEFT  ;;
            q | $'\E') loop=false  ;;
        esac
    fi
    if [ "$trapKey" ] ;then
        printf "\nDoing something with '%s'.\n" $trapKey
    fi
    echo -n .
done

Cela permettra

  • boucle avec une très faible empreinte (2 millisecondes maximum)
  • réagir aux touches cursor left , cursor right , cursor up y cursor down
  • sortir de la boucle avec la touche Escape o q .

0voto

pd3 Points 606

Voici une autre solution. Elle fonctionne pour n'importe quelle touche enfoncée, y compris espace, entrée, flèches, etc.

La solution originale testée en bash :

IFS=''
if [ -t 0 ]; then stty -echo -icanon raw time 0 min 0; fi
while [ -z "$key" ]; do
    read key
done
if [ -t 0 ]; then stty sane; fi

Une solution améliorée testée dans bash et dash :

if [ -t 0 ]; then
   old_tty=$(stty --save)
   stty raw -echo min 0
fi
while
   IFS= read -r REPLY
   [ -z "$REPLY" ]
do :; done
if [ -t 0 ]; then stty "$old_tty"; fi

En bash, vous pouvez même laisser de côté REPLY pour la variable read car il s'agit de la variable par défaut.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X