風柳メモ

ソフトウェア・プログラミング関連の覚書が中心。

Git for WindowsのGit BASHでウィンドウタイトルだけが文字化けする現象(winptyが原因)と対処方法

[2020.10.27追記] Git for Windows 2.29.1-64-bit(mintty 3.4.0 / winpty 0.4.3 の組み合わせ)では、exec winpty bash した後も Git Bash のウィンドウタイトルが文字化けしなくなった模様。以下は古いバージョンについての記述であり、新バージョンでは環境変数 PS1 を書き換えるとかえって文字化けしてしまうので、注意。



Git BASHで、たまたま日本語を含むフォルダ下で作業していたら、ウィンドウのタイトルだけが文字化けしていることに気がついた(ウィンドウ内については正常に表示されている)。
Windows 10 Pro バージョン 1909 (OS ビルド 18363.959)、Git for Windows 2.28.0-64-bit、mintty 3.2.0 (x86_64-pc-msys) [Windows 18363]

f:id:furyu-tei:20200812030339p:plain
Git BASHでウィンドウタイトルだけが文字化けしている様子

この現象の原因と、対処方法についての覚え書きを残しておく。




原因

どうやら、~/.bash_profile にて

exec winpty bash

を記述していたためらしい。

試しにこの記述を外すと、ウィンドウタイトルは化けなくなる。
ただし当然ながら、対話型コマンドがそのままではうまく動作しなくなる。

f:id:furyu-tei:20200812040929p:plain
exec winpy bash の記述を外した場合


Python なんかの Windows ネイティブな対話型コマンドを呼び出すのに、いちいち

$ winpty python

としたり、あるいは ~/.bashrc で対話型コマンド毎に

alias python="winpty python.exe"

のように alias 定義するのが面倒だったので bash 自体を winpty で呼び出していたのだけれど、これが原因でウィンドウタイトルが化けてしまうのは想定外……。

対処方法

A. 必要なコマンド毎に個別に winpty を付けて呼び出す alias を定義しておく

bash そのものを winpty 経由で呼び出しているのが原因なのでこれを止め、個別のコマンド毎に winpty 付き呼び出しを行うようにすれば問題はなくなる。
その場合、標準では /etc/profile.d/aliases.sh にて

    for name in node ipython php php5 psql python2.7
    do
        case "$(type -p "$name".exe 2>/dev/null)" in
        ''|/usr/bin/*) continue;;
        esac
        alias $name="winpty $name.exe"
    done

のように、いくつかのコマンドについて対応されているのを参考にして ~/.bashrc に必要なコマンドについて同様の記述を追記するというのが一つの方法。

B. 環境変数 PS1 を書き換える

A. のようにすると、対話型コマンドの種類が増えるごとにメンテナンスしなければならないため、面倒なのが難点。
そこで、bash を winpty で呼び出したときにウィンドウタイトルのみが化けている点に着目し、プロンプト定義用の環境変数(PS1)を書き換えてやれば良いのではないか? と考えた。
状況的に、bash を winpty 経由にした際には、mintty はウィンドウタイトル文字列としては Shift-JIS(CP932) で渡されることを期待しているのではないかと推測、$PS1 中に含まれる $PWD の文字コードを変換するように定義してみると、

export PS1=`echo "$PS1" | sed "s~\\$PWD~\\\`echo \\$PWD | iconv -f utf-8 -t cp932 2>/dev/null\\\`~"`

これでうまくいった。
ウィンドウタイトルに関してだけはなんとかなったということで、winpty bash したときに出る潜在的な問題は他にもあるかも知れない。

f:id:furyu-tei:20200812042525p:plain
ウィンドウタイトルも文字化けせず、対話型コマンドも正常に起動できている

覚え書きを兼ねて、現状の ~/.bash_profile および ~/.bashrc を残しておく。

~/.bash_profile

#【覚書】
# ※参考:[bashの.profileや.bashrc等を実行する動作仕様 - sgryjp.log](https://blog.sgry.jp/entry/2019/11/09/232927)
#===============================================================================
# ・ ~/.bash_profile はログインシェルで一度だけ実行される
#   このため、主に
#
#   - ログイン時に一度だけ実行したい処理
#   - 対話操作では必要のない(対話操作以外のコマンド実行に必要な)設定(環境変数 LANG 定義等)
#   - ログインシェル/サブシェルに関わらず基本変更不要な設定(環境変数 PATH 定義等)
#
#   を記述するのに向く
#
# ・ ~/.bashrc はサブシェルでは自動的に呼ばれる(ログインシェルでは自動では呼ばれない)
#   このため、~/.bash_profile からも呼び出されるよう設定の上で (^1)
#
#   - alias や関数等、暗黙的に引き継がれない設定
#   - 対話操作のみで必要となる設定(環境変数 PS1、EDITOR 等)
#
#   を記述するのに向く
#
#   (^1) ~/.bashrc があるのに ~/.bash_profile, ~/.bash_login or ~/.profile が無い環境は推奨されず、
#        例えば Git for Windows においては、その場合、/etc/profile.d/bash_profile.sh により
#        ~/.bashrc を呼び出す記述が入った ~/.bash_profile が生成される
#===============================================================================

# ■ ~/.bashrc 実行
#   ※ログインシェルでは ~/.bashrc は自動的には実行されないため、この設定が必要
#test -f ~/.profile && . ~/.profile
test -f ~/.bashrc && . ~/.bashrc

# ■ 各種定義
export PYTHONUTF8=1
export PYTHONSTARTUP=~/.pythonstartup

# ■ bash を winpty 経由で起動
#   ※ Windows ネイティブな対話型コマンド(Python 等の、interactive mode が mintty に対応していないもの)に対応
exec winpty bash
#   ※ exec によりシェルプロセス自体を置換しているため、これ以降に書いた内容は反映されないことに注意

~/.bashrc

if [[ $TERM_PROGRAM = "mintty" ]]; then
    # ■ mintty 専用設定
    case `tty` in
        /dev/cons*) # winpty
            # winpty 経由だと $PWD が日本語を含む場合ウィンドウタイトルが文字化けする(mintty 3.2.0)問題に対処
            # → mintty 3.4.0 / winpty 0.4.3 の組み合わせでは発生しなくなったため、必要がなくなった
            # export PS1=`echo "$PS1" | sed "s~\\$PWD~\\\`echo \\$PWD | iconv -f utf-8 -t cp932 2>/dev/null\\\`~"`
            ;;
        /dev/pty*) # (default)
            ;;
    esac
fi


# ■ 暗黙的にサブシェルに引き継がれない設定(alias・関数定義)
# ※サブシェルでは、
#   - profile系スクリプト(/etc/profile、/etc/profile.d/*、~/.bash_profile等)は実行されない
#   - ログインシェルで定義された alias や関数は引き継がれない
#   ことに注意
shopt -q login_shell || {
    # サブシェル専用設定
    # ※必要に応じて /etc/profile や /etc/profile.d/* (aliases.sh等)で定義されている alias 等をコピーしておく
    alias ls='ls -F --color=auto --show-control-chars'
    alias ll='ls -l'
}

# ~/.bash_profile 経由/サブシェル共用設定
alias ls='ls -aF --color=auto --show-control-chars'
alias tree='tree -N'
alias python2='py -2'
alias python3='py -3'
alias python='python3'
alias pip2='python2 -m pip'
alias pip3='python3 -m pip'
alias pip='pip3'


# ■ 非対話ログインで不要かつ対話ログインで使いたい設定(環境変数定義)
export EDITOR=vim

その他の覚え書き等

ユーザー独自のファイル(bashスクリプト)をログインシェル/サブシェルどちらでも呼び出す(Git BASH 独自仕様?)

/etc/profile.d/git-prompt.sh
の最後の方に、以下のような記述があることに気づいた。

# Evaluate all user-specific Bash completion scripts (if any)
if test -z "$WINELOADERNOEXEC"
then
        for c in "$HOME"/bash_completion.d/*.bash
        do
                # Handle absence of any scripts (or the folder) gracefully
                test ! -f "$c" ||
                . "$c"
        done
fi
  • /etc/profile.d 下のファイルは通常はログインシェルからしか呼ばれないものだが、git-prompt.sh に限り、サブシェルからも呼び出される(/etc/bash.bashrc 経由)
  • ~/bash_completion.d/ というディレクトリを作成し、この下に拡張子 .bash の付いた任意の bash スクリプトを置いておくと、/etc/profile.d/git-prompt.sh 経由で呼び出される(結果的にはログインシェル/サブシェルの区別なく呼ばれることになる)

参考

winpty・mintty 関連

mseeeen.msen.jp

bash 関連ファイル(~/.bash_profile・~/.bashrc 等)の動作仕様について

blog.sgry.jp
なんとなくで設定しがちだった bash の ~/.bash_profile や ~/.bashrc といったファイルの動作仕様が分かりやすくまとまっていて非常に参考になった。