Icon Ressource

[bash] 'pause' Funktion 2020-04-05

Ich fange mal mit einer kleinen Spielerei an. Wer auf Windows unterwegs ist, ist es in der Regel gewohnt Scripts per Doppelklick auszuführen, statt aus einer Shellkonsole oder einem Terminal. Um zu verhindern dass sich das Fenster bei Beenden des Scripts automatisch schließt, hat die CMD das PAUSE Kommando an Bord. Ebenso die PowerShell. Für das Debuggen eines Scripts kann das auch hilfreich sein. Schnell mal ein PAUSE rein geschrieben, um zu sehen wo der Fehler liegt ...
Die Shell auf Linux kennt das in dieser Form nicht. Natürlich ist es keine Hexerei ein ähnliches Verhalten nachzubilden. Angefangen habe ich mit dem Einzeiler ...
Bash:
read -rsn 1 -p 'Press any key to continue . . . ';echo
... der in dem meisten Fällen vermutlich ausreichend ist. Meine Vorstellung von einer vernünftigen pause Funktion erfüllt das aber nicht. Also habe ich es noch etwas erweitert. Das folgende bash-Script besteht aus mehr Kommentaren als eigentlichem Code, um zu erklären was ich dort mache und warum.
Bash:
#!/bin/bash

# The aim of the pause function is to automatically suspend the script and wait
# for user interaction to continue.
# My checklist to accomplish this, alongside the comparison with the built-in
# PAUSE of cmd.exe:
#  1. Discard pending characters to avoid treating them as user input.
#   bash: ✔
#   cmd: ❌
#  2. Prompt the user.
#   bash: ✔
#   cmd: ✔ ⚠ the prompt can be redirected and thus, be invisible for the user
#  3. Optionally disable or customize the prompt.
#   bash: ✔
#   cmd: ❌
#  4. Suspend the script execution.
#   bash: ✔
#   cmd: ✔ ⚠ treats redirected stdin as user interaction, too
#  5. React to arbitrary keyboard input.
#   bash: ✔
#   cmd: ✔ ⚠ treats redirected stdin as keyboard input, too
#  6. Ignore redirected standard streams.
#   bash: ✔
#   cmd: ❌
#  7. Discard remnants caused by extended keys (such as F-keys or cursor keys).
#   bash: ✔
#   cmd: ❌
#  8. Wrap the line after keyboard input to leave the prompt in a separate line.
#   bash: ✔
#   cmd: ✔
#  9. Keep previous settings, at least restore them.
#   bash: ✔
#   cmd: ✔
# 10. Preserve the return value of the previous command; we only suspend the
#     script execution at a certain point without changing any further behavior.
#   bash: ✔
#   cmd: ✔

# Usage: pause [[-q] | [custom prompt]]
function pause() {
  # pause shouldn't change the recent return code but it would, hence capture it
  declare r=$?
  # also save the current terminal line settings (we work with tty explicitely
  # because stdin and stdout could be redirected)
  declare s=$(stty -gF "/dev/tty")
  # set default terminal characteristics
  stty sane -F "/dev/tty"
  # disable sending the terminal stop signal via Ctrl+Z to avoid freezing
  stty susp undef -F "/dev/tty"
  # make c a variable with local scope before we use it along with command read
  declare c
  # discard pending chars from tty (if any)
  while read -rst 0 c; do read -rsn 1 c; done < "/dev/tty"
  # option -q will disable the prompt and line wrapping
  if [ "${1}" = "-q" ]; then
    # wait for input, read 1 char in raw mode without printing it
    read -rsn 1 c < "/dev/tty"
  else
    # prompt the user
    echo -ne "${1:-Press any key to continue . . . }" > "/dev/tty"
    # wait for input, read 1 char in raw mode without printing it
    read -rsn 1 c < "/dev/tty"
    # wrap the line
    echo > "/dev/tty"
  fi
  # discard remnants (if any) as caused by cursor keys or other extended keys
  while read -rst 0 c; do read -rsn 1 c; done < "/dev/tty"
  # restore previous terminal line settings
  stty $s -F "/dev/tty"
  # return the cached value
  return $r
}

# A few tests:
echo -e "have a global variable c with value \e[91mfoo\e[0m"
c='foo'
echo -e "set the return code to \e[91m5\e[0m before calling pause"
(exit 5)
pause
echo -e "after pause returned, the return code should still be 5:"\
" \e[91m${?}\e[0m\nand c still foo: \e[91m${c}\e[0m"

echo
echo "redirecting file content to stdin shall not affect the behavior of the"\
" pause call:"
(pause) < "$0"

echo
echo "redirecting the stdout to /dev/null shall not affect the visibility of"\
" the pause prompt:"
(pause) > "/dev/null"

echo
echo -e "\e[94mpress Ctrl+z\e[0m which should provide the key input for"\
" the pause call (instead of making the terminal unresponsive)"
pause

echo
echo -e "\e[94mpress a cursor key\e[0m which should only provide the key"\
" input for one pause call"
pause
echo "if the script is suspended and you see the prompt below, then everything"\
" worked as expected"
pause

echo
echo "suppress the pause prompt"
pause -q

echo
echo "specify your own prompt"
pause "Press any key to \e[92mquit the script\e[0m . . . "
Getestet habe ich auf Ubuntu im WSL. Ich gehe kaum davon aus, dass das Script in einer anderen Shell als bash läuft. Auf anderen Distros als Ubuntu könnte es aber funktionieren. Ihr könnt ja mal Feedback geben ;)

BTW: Wer den Code unter Windows hier rauskopiert, sollte peinlich darauf achten, dass er ihn mit UNIX Zeilenumbrüchen speichert. Sonst funktioniert nix.

Steffen
1586098096586.png
  • Like
Reaktionen: Mat
Autor
german
Downloads
1.029
Aufrufe
2.665
Erstellt am
Letzte Bearbeitung
Bewertung
0,00 Stern(e) 0 Bewertung(en)

Weitere Ressourcen von german

Zurück
Oben Unten