Écraser la sortie précédente dans Bash au lieu de l'ajouter

22

Pour une minuterie bash, j'utilise ce code:

#!/bin/bash
sek=60
echo "60 Seconds Wait!"
echo -n "One Moment please "
while [ $sek -ge 1 ]
do
   echo -n "$sek "  
sleep 1
   sek=$[$sek-1]
done
echo
echo "ready!"

Cela me donne quelque chose comme ça

One Moment please: 60 59 58 57 56 55 ...

Existe-t-il une possibilité de remplacer la dernière valeur de seconde par la plus récente afin que la sortie ne génère pas une grande traînée mais le compte à rebours des secondes comme un temps réel à une position? (J'espère que vous comprenez ce que je veux dire :))

NDA
la source
Il pourrait y avoir un moyen de le faire avec la watchcommande, même si je ne sais pas exactement comment le faire.
AJMansfield

Réponses:

14
#!/bin/bash
sek=60
echo "60 Seconds Wait!"
echo -n "One Moment please "
while [ $sek -ge 1 ]
do
   echo -n "$sek" 

sleep 1
   sek=$[$sek-1]
   echo -en "\b\b"
done
echo
echo "ready!"
aneeshep
la source
2
Incroyable, merci beaucoup. Juste un avis pour les autres lecteurs. Pour adapter le code ci-dessus, vous devez utiliser echo -en "\ b \ b \ b" en raison de l'espace.
NES
1
+1 Sympa, mais ... en dessous de 10 ça commence à manger le message ...
lepe
1
Oui, mieux vaut utiliser \r. Voir ma réponse.
Mikel
3
Du manuel de bash: The old format $[expression] is deprecated and will be removed in upcoming versions of bash.. Utilisez plutôt POSIX $((expression))ou ((-command. Par exemple sek=$(( sek - 1 ))ou (( sek = sek - 1 ))ou (( sek-- )).
geirha
17

Fondamentalement, la même chose que la réponse de aneeshep, mais utilise Return ( \r) plutôt que Backspace ( \b) car nous ne savons pas si la longueur sera toujours la même, par exemple quand $sek < 10.

En outre, votre premier echodoit utiliser $sek, pas du code dur 60.

Enfin, notez l'espace après le ....

#!/bin/bash
sek=60
echo "$sek Seconds Wait!"
while [ $sek -ge 1 ]
do
   echo -ne "One Moment please $sek ... \r"
   sleep 1
   sek=$[$sek-1]
done
echo
echo "ready!"
Mikel
la source
7

Avec bash, vous pouvez utiliser la variable spéciale SECONDS.

#BASH
SECONDS=0;
while sleep .5 && ((SECONDS <= 60)); do 
    printf '\r%s: %2d' "One moment please" "$((60-SECONDS))"
done
printf '\n'
geirha
la source
+1 Bonne réponse, en plus je n'ai jamais connu SECONDES.
Mikel
Cela fonctionne, mais c'est un peu étrange de voir 'sleep .5' comme une condition de boucle while testée. Cependant, j'aime particulièrement l'utilisation de $ SECONDS, car elle se rapporte au temps réel, (c'est-à-dire pas un temps écoulé fixe entre actions chronophages, comme avec sleep 1) ... SECONDS Cette variable se développe en nombre de secondes depuis le démarrage du shell. (donc je ne le mettrais probablement pas à 0 .. il suffit de le tester pendant 60 secondes de plus à partir du début du script) .. +1
Peter.O
Je commente davantage, uniquement parce que je suis personnellement intéressé par un moyen d'obtenir un coutntdown précis ... J'ai testé $ SECONDS et il semble que s'il soit réglé sur '0' ou non, cela dépend toujours de la fraction de seconde actuelle du système .. ie. Le mettre à «0» peut entraîner un temps de 0,99 ... (il en va de même s'il est laissé tel quel) .... Donc, sa meilleure chance moyenne est d'être à seulement 0,5 seconde. .. Juste un commentaire (c'est à ça que servent les commentaires :)
Peter.O
@ fred.bear Eh bien, vous ne serez jamais vraiment précis; un autre processus pourrait commencer à monopoliser le processeur et / ou les E / S pendant le compte à rebours et ruiner la précision que vous aviez auparavant. Ce que vous pouvez faire, c'est de le faire attendre au moins X fois, et de donner un compte à rebours approximatif si vous le souhaitez. Quand un programme dit "ça va prendre une minute", pensez-vous que cela prendra exactement 1 minute, à la milliseconde près? ou pour prendre 1 minute, donner ou prendre quelques secondes?
geirha
@geirha ... Oui, cette variation n'aura pas d'importance dans 99% des cas, et c'est génial que vous m'ayez fait connaître $ SECONDS ... Je trouve juste ses limites ... J'ai joué avec un variation de votre script qui rapporte le temps de finition sur la base d'un sommeil de 0,01 s (plus, comme vous l'avez mentionné, tout décalage du système externe) mais ne s'imprime que lorsqu'un second clique dessus, mais j'ai découvert que la première 'seconde' imprimait trop tôt (dans un cas, juste après la première .01 sec) .. Tout cela fait partie de ma courbe d'apprentissage bash ... J'aime votre réponse, c'est pourquoi je l'ai regardée plus en profondeur.
Peter.O
3

En plus des approches \rou \b, il est possible d'utiliser le \033[2K caractère de contrôle , qui indique au terminal d'effacer toute la ligne. L'avantage de ceci par rapport au \bfait que vous n'avez pas à faire correspondre le nombre de \bavec le nombre de caractères que vous souhaitez supprimer, et par rapport à ce \rqu'il n'y aura pas de caractères qui ressortent à l'écran si la nouvelle ligne est plus courte que l'ancienne une.

Vous trouverez ci-dessous l'exemple de la façon dont il peut être appliqué à cette question, et voici un exemple de l'application associée pour créer une sortie similaire aux messages de démarrage. Dans cet exemple particulier, le minuteur disparaîtra une fois la 0e seconde atteinte et la ligne du minuteur sera remplacée par "Prêt!" phrase.

#!/bin/bash
sek=60
echo "60 Seconds"

while ((sek--)); do
    printf "One moment please: %d" "$sek"
    sleep 1
    printf "\r%b" "\033[2K"
done
echo "Ready!"

Une autre alternative serait d'employer la dialogcommande pour créer des boîtes de dialogue simples en ligne de commande. La boîte de dialogue restera à l'écran pendant la durée du minuteur et se mettra à jour avec la boucle, et au moment où elle est terminée - le minuteur sera remplacé par le message "Prêt! Appuyez pour quitter" de manière transparente:

#!/bin/bash
sek=60
echo "60 Seconds"

while ((sek--)); do
    echo "$sek" | dialog --progressbox "Please wait" 10 25
    sleep 1
done
dialog --msgbox "Ready! Press <OK> to finish" 10 25
Sergiy Kolodyazhnyy
la source
dialogn'existe pas sur mac: /
Ricky Levi
1

Voici ce que j'ai trouvé après avoir lu ici et un peu plus, la doublure:

SEC=101;for i in `seq $SEC -1 1`;do printf "\rNext in: %`expr length $SEC`ds" "$i";sleep 1;done;echo

Plus lisible:

#!/bin/bash
SEC=101

for i in `seq $SEC -1 1`;do
        printf "\rNext in: %`expr length $SEC`ds" "$i";
        sleep 1;
done
echo

Où SEC peut être réglé sur n'importe quel entier positif et le printf se chargera du remplissage approprié. Testé sous Ubuntu et cygwin.

Tod
la source
1

Peut y parvenir en plaçant le retour chariot \r.

En une seule ligne de code, il est possible avec echo -ne

for i in {60..1}; do echo -ne "One moment please: $i\r" && sleep 1; done

ou avec printf

for i in {60..1}; do printf "One moment please: $i\r" && sleep 1; done
Akif
la source
-2

Compte à rebours:

MIN=1 && for i in $(seq $(($MIN*60)) -1 1); do echo -n "$i, "; sleep 1; done; echo -e "nnMessage"

et le chronomètre «normal» est:

START=$( date +%s ); while true; do CURRENT=$( date +%s ) ; echo $(( CURRENT-START )) ; sleep 1 ; echo -n  ; done

contrôle + c pour arrêter

Habituel
la source
Cela ne répond pas à la question sur l'écrasement du texte
Jeremy Kerr