---
slug: "python-keyboard-menu-in-terminal-with-escape-sequence"
title: "Python で矢印キーで操作するターミナルメニューを作る (エスケープシーケンスを駆使する)"
description: "curses を使わずに、矢印キーで操作できるターミナルメニューを作る場合の情報。\n不明点が多かったので、他のサイトを参考にしながら書きました。感謝。"
url: "https://www.ytyng.com/blog/python-keyboard-menu-in-terminal-with-escape-sequence"
publish_date: "2022-07-24T08:46:08Z"
created: "2022-07-24T08:46:08Z"
updated: "2026-02-27T01:06:32.825Z"
categories: []
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20250605/69126ee3310f492a99ff079148615b95.png.webp?width=768"
has_video: true
has_music: true
video_urls: ["https://media.ytyng.net/ytyng-blog/239/featured-video-1.mp4", "https://media.ytyng.net/ytyng-blog/239/featured-video-2.mp4", "https://media.ytyng.net/ytyng-blog/239/featured-video-3.mp4"]
music_urls: ["https://media.ytyng.net/ytyng-blog/239/featured-music-239-1.mp3", "https://media.ytyng.net/ytyng-blog/239/featured-music-239-2.mp3"]
lang: "ja"
---

# Python で矢印キーで操作するターミナルメニューを作る (エスケープシーケンスを駆使する)

<p>curses を使わずに、矢印キーで操作できるターミナルメニューを作る場合の情報。</p>
<p>不明点が多かったので、他のサイトを参考にしながら書きました。感謝。</p>
<p>エスケープシーケンスの勉強になりました。</p>
<p></p>
<h2>CSI</h2>
<p><code>\033[</code> というエスケープシーケンスが多く出てきますが、これは「 <strong>CSI&nbsp;(Control Sequence Introducer)</strong>」とよばれるシーケンスで、カーソルの移動や色の変更など、広い用途に使われます。</p>
<p><code><span>\033[</span></code>&nbsp;は8進数表記の場合です。16進表記だと&nbsp;<code>\x1b[</code> となり、両者は同じ文字です。(ちなみに10進だと27 なので <code>chr(27)</code>&nbsp;+ <code>[</code> としても同じです )</p>
<p><code>ESC + [</code> とも表現します。</p>
<p></p>
<p>CSIについては、英語版 Wikipedia に詳しく記載されています。日本語版は無いようです。</p>
<p><a href="https://en.wikipedia.org/wiki/ANSI_escape_code" target="_blank">https://en.wikipedia.org/wiki/ANSI_escape_code</a></p>
<p></p>
<h2>描画</h2>
<p>描画した複数行を更新する必要がある。</p>
<p>複数行を描画したあと、<code>\033[nA</code> (nは数字。行数) をプリントしてカーソルを上に移動し、再び print する。</p>
<p></p>
<h4>参考サイト</h4>
<p><a href="https://hacknote.jp/archives/51679/" target="_blank">pythonで複数行の標準出力を上書きしながら出力する方法</a><br /><br /></p>
<h2>キー操作</h2>
<p>キー入力を受け付ける必要がある。<code>input()</code> だと Enter キーの入力をしなければいけないのでだめ。</p>
<p><code>readchar</code> ライブラリを使うか、<code>tty</code> を使って読み取る必要がある。</p>
<p>readchar も中で tty を使っている。</p>
<p>ただし、PyCharm の中のターミナルでは動作しなかった。iTerm では問題なく動作した。</p>
<p></p>
<h4>参考サイト</h4>
<p><a href="https://emotionexplorer.blog.fc2.com/blog-entry-126.html" target="_blank">pythonでコンソールから1文字入力 - Emotion Explorer</a></p>
<p><a href="https://qiita.com/tortuepin/items/e6c72f48115f20744ace" target="_blank">pythonでキー入力を検出する(tty) - Qiita</a></p>
<h2>色</h2>
<p>テキストの色を変えると見やすい。エスケープシーケンスで色を変える。</p>
<h4>参考サイト</h4>
<p><a href="https://www.nomuramath.com/kv8wr0mp/" target="_blank">[python]print文で色をつけてみよう &ndash; 野村数学研究所</a></p>
<h2>コード</h2>
<pre>#!/usr/bin/env python3<br /><br />import readchar<br /><br />menu = [<br />    'Arcturus',<br />    'Betelgeuse',<br />    'Capella',<br />    'Deneb',<br />    'Eltanin',<br />    'Fomalhaut',<br />    'Gacrux',<br />]<br /><br /><br />class Color:<br />    BLACK = '\033[30m' # (文字)黒<br />    RED = '\033[31m' # (文字)赤<br />    GREEN = '\033[32m' # (文字)緑<br />    YELLOW = '\033[33m' # (文字)黄<br />    BLUE = '\033[34m' # (文字)青<br />    MAGENTA = '\033[35m' # (文字)マゼンタ<br />    CYAN = '\033[36m' # (文字)シアン<br />    WHITE = '\033[37m' # (文字)白<br />    COLOR_DEFAULT = '\033[39m' # 文字色をデフォルトに戻す<br />    BOLD = '\033[1m' # 太字<br />    UNDERLINE = '\033[4m' # 下線<br />    INVISIBLE = '\033[08m' # 不可視<br />    REVERCE = '\033[07m' # 文字色と背景色を反転<br />    BG_BLACK = '\033[40m' # (背景)黒<br />    BG_RED = '\033[41m' # (背景)赤<br />    BG_GREEN = '\033[42m' # (背景)緑<br />    BG_YELLOW = '\033[43m' # (背景)黄<br />    BG_BLUE = '\033[44m' # (背景)青<br />    BG_MAGENTA = '\033[45m' # (背景)マゼンタ<br />    BG_CYAN = '\033[46m' # (背景)シアン<br />    BG_WHITE = '\033[47m' # (背景)白<br />    BG_DEFAULT = '\033[49m' # 背景色をデフォルトに戻す<br />    RESET = '\033[0m' # 全てリセット<br /><br /><br />def tint_color(text):<br />   return f'{Color.BG_CYAN}{Color.BLACK}{text}{Color.RESET}'<br /><br /><br />def help_color(text):<br />    return f'{Color.GREEN}{text}{Color.RESET}'<br /><br /><br />def strong_color(text):<br />    return f'{Color.YELLOW}{text}{Color.RESET}'<br /><br /><br />def main():<br />    current = 0<br />    c = ''<br />    while True:<br />        for i, item in enumerate(menu):<br />        if current == i:<br />            print(tint_color(f'&gt; {item}'))<br />        else:<br />            print(f' {item}')<br />        print(help_color(f'c={repr(c)}, current={current}'))<br />        c = readchar.readkey()<br />        if c == 'q':<br />            # q で終了<br />            break<br />        elif c == '\r':<br />            # エンターで決定<br />            print(f'selected:{strong_color(menu[current])}')<br />            break<br />        if c == 'j' or c == '\x1b[B':<br />            # j か &darr;　でカーソル移動<br />            if current &lt; len(menu) - 1:<br />                current += 1<br />        if c == 'k' or c == '\x1b[A':<br />            # k か &uarr;でカーソル移動<br />            if current &gt; 0:<br />                current -= 1<br />        print(f'\033[{len(menu) + 1}A', end='')<br /><br /><br />if __name__ == '__main__':<br />    main()</pre>
<p></p>
