按任意鍵停止循環

按任意鍵停止循環

下面的事情該怎麼辦呢?

  • 我有一個循環腳本。
  • 我想當我按下一個鍵時停止它,否則它會在 5 秒後繼續。

答案1

需要記住的是,shell 或通常在終端機中運行的任何應用程式不會與鍵盤和螢幕互動。

它們從標準輸入中獲取輸入作為位元組流。當該標準輸入來自常規檔案時,位元組就來自那裡,當它是管道時,資料通常是由另一個進程發送的,當它是可以到達連接到電腦的實體設備的某個設備檔案時。例如,當它是 tty 字元設備時,它是通常由終端通過某些串行線路發送的資料。終端機是某種形式的設備,它將鍵盤事件轉換為位元組序列。

這就是終端應用程式的全部功能所在。輸入機制已為它們抽象化出來,因此它們可以在腳本中互動或自動使用。

在這裡,如果您要發出此類提示並期待鑰匙在新聞發布會上,您可能希望您的應用程式(您的腳本)只是一個互動式應用程式。要么期望 stdin 成為終端,要么從終端獲取輸入,無論 stdin 在哪個上打開。

現在,如上所示,所有應用程式看到的都是位元組流,這就是終端機(或終端模擬器)和 tty 設備線路規則的作用,將按下的按鍵轉換為位元組序列。舉幾個例子:

  • 當您按下該a鍵時,ASCII 終端發送一個 0x61 位元組,
  • 當您按下該£鍵時,UTF-8 終端會傳送兩個位元組 0xc2 和 0xa3。
  • 當您按下該Enter鍵時,ASCII 終端會傳送一個 0x0d 位元組,在基於 ASCII 的系統(如 Linux)上,tty 線路規則通常會將其轉換為 0x0a
  • 當您Ctrl單獨按下時,終端不會發送任何內容,但是如果您使用 按下它C,終端會發送一個 0x03 字節,tty 線路紀律攔截該字節以向前台任務發送 SIGINT 信號
  • 當您按 時Left,終端通常會發送一系列位元組(因終端而異,應用程式可以查詢 terminfo 資料庫來翻譯它),其中第一個字節是 0x1b。例如,根據其所處的模式xterm,在基於 ASCII 的系統上,將發送 0x1b 0x4f 0x44 或 0x1b 0x5b 0x44(<ESC>[A<ESC>OA)。

所以在這裡,我要問的問題是:

  1. 如果 stdin 不是終端,您是否仍要提示用戶
  2. 如果 1 的答案是肯定的,那麼您想在終端上提示用戶,還是透過 stdin/stdout 提示用戶?
  3. 如果 1 的答案是否定的,您是否仍要在每次迭代之間等待 5 秒?
  4. 如果2的答案是透過終端,如果腳本無法偵測到控制終端或回退到非終端模式,它是否應該中止?
  5. 您是否只想考慮發出提示後按下的按鍵。 IOW,如果使用者在發出提示之前不小心鍵入了某個鍵。
  6. 您願意花多長時間來確保只讀取單次按鍵發出的位元組?

在這裡,我假設您希望您的腳本僅成為終端交互式應用程序,並且僅通過控制終端進行交互,而僅保留 stdin/stdout 。

#! /bin/sh -

# ":" being a special builtin, POSIX requires it to exit if a
# redirection fails, which makes this a way to easily check if a
# controlling terminal is present and readable:
:</dev/tty

# if using bash however not in POSIX conformance mode, you'll need to
# change it to something like:
exec 3< /dev/tty 3<&- || exit

read_key_with_timeout() (
  timeout=$1 prompt=$2
  saved_tty_settings=$(stty -g) || exit

  # if we're killed, restore the tty settings, the convoluted part about
  # killing the subshell process is to work around a problem in shells
  # like bash that ignore a SIGINT if the current command being run handles
  # it.
  for sig in INT TERM QUIT; do
    trap '
      stty "$saved_tty_settings"
      trap - '"$sig"'
      pid=$(exec sh -c '\''echo "$PPID"'\'')
      kill -s '"$sig"' "$pid"

      # fall back if kill failed above
      exit 2' "$sig"
  done

  # drain the tty's buffer
  stty -icanon min 0 time 0; cat > /dev/null

  printf '%s\n' "$prompt"

  # use the tty line discipline features to say the next read()
  # should wait at most the given number of deciseconds (limited to 255)
  stty time "$((timeout * 10))" -echo

  # do one read and count the bytes returned
  count=$(dd 2> /dev/null count=1 | wc -c)

  # If the user pressed a key like the £ or Home ones described above
  # it's likely all the corresponding bytes will have been read by dd
  # above, but not guaranteed, so we may want to drain the tty buffer
  # again to make sure we don't leave part of the sequence sent by a
  # key press to be read by the next thing that reads from the tty device
  # thereafter. Here allowing the terminal to send bytes as slow as 10
  # per second. Doing so however, we may end up reading the bytes sent
  # upon subsequent key presses though.
  stty time 1; cat > /dev/null

  stty "$saved_tty_settings"

  # return whether at least one byte was read:
  [ "$(($count))" -gt 0 ]

) <> /dev/tty >&0 2>&0

until
  echo "Hello World"
  sleep 1
  echo "Done greeting the world"
  read_key_with_timeout 5 "Press any key to stop"
do
  continue
done

答案2

while true; do
    echo 'Looping, press Ctrl+C to exit'
    sleep 5
done

沒有必要讓它變得更複雜。

要求如下bash

while true; do
    echo 'Press any key to exit, or wait 5 seconds'
    if read -r -N 1 -t 5; then
        break
    fi
done

如果read失敗(由於超時),循環將繼續。

相關內容