Windowsでのターミナル環境

提供:Turgenev's Wiki

WindowsはLinuxに比べるとライトユーザー向けのOSで、端末エミュレータで文字を打ってコマンドを実行するというCUIのスタイルで使いやすいようにデザインされているとは言い難い。それでも、ある程度工夫すれば不満点を改善することはできる。

C:\Users\USERNAME\.pathにPATHを通す

ユーザーフォルダ(C:\Users\USERNAME)の直下などわかりやすいところに.path(もちろん名前は好きに決めてよい)というフォルダを作り、これをユーザー環境変数のPATH(の先頭がいいかな?)に追加しておく(他ユーザーから見えないフォルダなのでシステム環境変数に追加するのは不適切)。するとPATHをいちいち書き換えなくてもこの中に入れた実行ファイルやバッチファイルはファイル名だけで呼び出せる状態になる。

これは、インストール不要(圧縮ファイル解凍のみ)、あるいはPATHを自動で通さないようなソフトウェアを使いやすくするのに有用である。実行ファイル単体で動作するならファイルを直接入れてもいいし、依存ファイルがあるなら本体の位置はそのままでそれを呼び出すプログラムを入れればいい(バッチファイルでもいいが、欠点がある。詳しくは後述)。

筆者はrclone(ファイル同期ソフト)やmpv(メディアプレイヤー)などをこの.path経由で起動できるようにしている。またLinuxでも同様の設定をしている。

他のコマンドを呼び出すプログラム

前項の目的を達するため、「自身に渡されたコマンドライン引数を(場合により多少の処理をした上で)ほぼそのまま使って他のコマンドを実行する」ようなプログラムを作成した。

https://github.com/ge9/win-console-delegator

このプログラムは、「自分自身のファイル名の最後の文字と最後から3番目の文字をtに変更したファイル(主にexe→txtを想定しているが、com→totなどでもよい)を読み込み、その内容と自分に与えられたコマンドライン引数をつなげた文字列をコマンドとして実行する」という挙動をする。すなわち、実行ファイルは(コンパイルし直さず)そのままコピーした上で、ペアとなる.txtファイルに適切な内容を書くことで、以下のようなプログラムが実現できる。

他のプログラムをそのまま実行

たとえばテキストファイルに以下のように書いておけば、mpvが起動される。末尾の半角スペースも入れること。

"C:\Users\username\Softwares\mpv\mpv.exe"

Windows版のmpvは、設定ファイル(mpv.conf)やプラグインなどを自身が存在するディレクトリから読み込むが、それらの動作を維持したままで、.pathにはmpv.exeという呼び出し専用のファイルだけを配置することができる。

指定フォルダにあるプログラムを実行

myprogram somecommand arg1 arg2 ...と呼び出されたときに、C:\path\to\somecommand arg1 arg2 ...をかわりに実行したい、ということがある。例えば複数の実行ファイルがまとまって提供されるような場合である。このときは以下のようなmycommand.txtをペアにすればよい。この場合はパスを直接引数につなげるため、末尾に半角スペースを入れてはいけない。

C:\path\to\

ところで、C:\path\to\の部分に空白文字が含まれている場合、このままではパスが正しく認識されない。通常はパス全体をダブルクォーテーションで囲うことでこれを回避するが、今回は与えられた引数のどこにダブルクォーテーションを挿入するかが自明ではない(myprogram someprogram"と入力させるという方法もあるが美しくない)ため、半角スペースを含まない別名(8.3形式)を使うのがよいだろう(短い名前についてはWindowsのパス長さ制限に関しても参照)。例えばProgram Filesなら普通は「PROGRA~1」になっているはずである。以下はGit Bashでのtxtファイルの例である。

C:\PROGRA~1\Git\usr\bin\

これにより、PATHを変更せずとも、例えばgb grepなどとするだけでGit Bashのgrepを呼び出すことができるようになる。gitやnpmやbusyboxやmagick(ImageMagick)のようにサブコマンドを指定して使うプログラムは多くあるが、これと同じような使用感になる。プレフィックスを設けることでそれぞれの名前の集合を別々に管理して衝突を防ぐ、という意味では、多くのプログラム言語で採用されている名前空間(namespace)に近い発想かもしれない。(コマンドにおけるこのような手法に特に名前は付いていないと思う。)

pathなどの環境変数を変更して実行

上記と若干似ているが、PATHをはじめとした環境変数を変えることでプログラムがうまく実行されるようにしたい、という場合もある。この場合は、テキストファイルを以下のようにすればよい(Git Bashのフォルダをパスに追加し、MYVARvalue1をセットする例)。末尾の半角スペースはあってもなくてもいい。

cmd /c path C:\Program Files\Git\usr\bin;%PATH% & set MYVAR=value1 &

こちらは、先ほどと違って、内部でGit Bashのコマンドを使用する(Git Bashとは無関係な)プログラムを実行する際などに有用である。

また、これに類似のケースとして、環境変数の設定などを行うバッチファイルが既に用意されていてそれを使いたいという場合もある。例えば以下のようなテキストファイルを用いれば、実質的にVisual Studioの開発者コマンドプロンプト(Developer Command Prompt for VS 2022)の内部で与えられたコマンドを実行してくれるプログラムが作れる。

cmd /c "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" &

Linuxでも、例えば特定のvenv仮想環境の中で与えられたコマンドを実行するシェルスクリプトを全く同じ発想で作ることができる。

実装について

  • 言語はC++を使った。これはWindows APIのGetCommandLineから生のコマンドライン文字列が得られるからである。予め分割されたコマンドラインでは情報量が減ってしまう。WinMainが使えればlpCmdLineで楽にできたが、今回はコンソールアプリケーションで実装したかったので、c++ - How to get the raw command line arguments - Stack Overflowのようにして自身のファイルパスだけ取り除いた。
  • コンソールイベント(Ctrl+Cなど)の転送に関しては、CreateProcessにCREATE_NEW_PROCESS_GROUPを指定した上で、SetConsoleCtrlHandlerによって全てのコンソールイベントを転送する方式とした。CREATE_NEW_PROCESS_GROUPを指定しない場合よりもこのほうが安定して動いている気がする。
  • cmdの対話シェルには、実行するアプリケーションがコンソールアプリケーションではなくGUIアプリケーションである場合は実行終了を待たずに即座に制御を返すという仕様がある。これを模倣するため、自作プログラムにはGUIアプリケーションにしたバージョンも同梱している。

batとの比較

上記の例は、いずれもバッチファイルを使って似たようなことが実現できる。例えばGit Bashならそれぞれ

@C:\path\to\directory\%*

@cmd /c "path C:\path\to\directory;%PATH% & %*"

のようにする(@は、echo offを一時的に行うために必要である)。

しかし、cmd.exeには、バッチファイルの実行中にCtrl+Cを送信すると「バッチ ジョブを終了しますか (Y/N)?」というプロンプトを表示するという厄介な仕様がある。また、%で挟まれた環境変数(%PATH%など)が文字列として含まれていた場合に展開されてしまう。そのため、先ほどの自作プログラムを使用するほうがスマートである。

Cygwin, MSYS/MinGW, Git Bashの違い

これらはいずれもWindows上でUnixライクなコマンドライン環境を整備してくれるツールだが、違いもある。

個人的な印象としては

  • Cygwinは起動に若干タイムラグがあるが、最も信頼できる動作をするので、例えばシェルスクリプトを実行させるときなどはまずCygwinで試した方がいい。
  • MSYS系は、C++でWindows向けに何かをコンパイルしたい時に使う。
  • Git Bashは、最も手軽だが、Cygwinに比べると意外とWindowsとの互換性というか、動作が怪しいことがある。

参考サイト

アプリ実行エイリアスの扱い

wt.exeやmspaint.exeなどストアアプリ系の実行ファイルは「アプリ実行エイリアス」というやや特殊な仕組みで管理されており(例えばエクスプローラーで見るとこれらのファイルサイズは0バイトである)、Cygwin系と相性が悪い。Git BashではPermission Deniedでこれらのファイルの実行ができないという問題があった。最新リリースでは修正されている。

Permission Error on all App Execution Aliases in git-bash · Issue #2675 · git-for-windows/git

Cygwinでは、実行は問題ないがwhich wtとすると見つからないと言われる(Git Bashでは大丈夫そう)(2023/08時点)。

Cygwinのforkに関するエラーを直す

setupを再実行してバイナリがアップデートされた後に、以下のようなエラーが頻発するようになることがある。毎回出るわけではなく、確率的に生じるので厄介である。

child -1 – forked process 9272 died unexpectedly, retry 0, exit code 0xC0000142, errno 11
40 [main] bash 3348 fork: child -1 - forked process 4248 died unexpectedly, retry 0, exit code -1073741819, errno 11

解決方法はいくつかある(単に再起動などで治る場合もある?)らしいが、自分の場合は以下のようにrebaseallというのをやることで治った。

Cygwin error: "-bash: fork: retry: Resource temporarily unavailable" - Stack Overflow(元記事はhttps://cygwin.fandom.com/wiki/Rebaseallらしい)

ここにはrebaseall -vと書いてあるが、手元ではそのようなオプションがなさそうだったので単純にrebaseallとしたら、それでうまく行ったようである。

  • まとめると、手順は以下。

    • Cygwin関連のプロセスをすべて終了させる。
    • /usr/binにあるdash.exe管理者権限で起動させる(おそらく軽量で依存関係が少ないから?)
    • ./rebaseallを実行
  • 他には以下のようにWindowsのセキュリティ設定をいじる方法もあるようだが、ちょっと不安。

    https://oni-gili.org/archives/290

    https://umateku.com/archives/462

Cygwinでcmdに"を出力させる

change in handling quotes in cygwin package from 3.1.4-1 to 3.1.5-1にある通り、Cygwinでcmd(を含むWindowsネイティブのプログラム?)にダブルクォーテーション単体(あるいはダブルクォーテーションを途中に含む任意の文字列など)を渡すのは難しい(難しくなった?)。環境変数に"を入れておいて渡すという手はあるが、衝突する可能性も考えられる。一番確実なのは、与えられた引数に含まれる指定された文字列を全て"に置き換えて実行するようなプログラムを別途書くことだろう(気が向いたらやるかも)。

Git Bash/Cygwinなどでbatやvbsを拡張子無しで呼ぶ

Windows上では、実行ファイルの拡張子(の主流)である.exe以外にもバッチファイル(.bat)やVBScript(.vbs)などが環境変数PATHEXTに登録され、拡張子無しで呼び出すことができる。しかし、Git Bash/Cygwinでは、exeのみが省略でき、他は明示的に付けないと呼び出せない。これはGit Bashで.batや.lnkを使うとき拡張子を省く - Qiitaのようにbash側で設定すると解決できる(が、あまり綺麗な方法とはいえない気がする)(なおCygwinでは未確認)。また、よく使う操作をコマンド一発で呼び出したい時は、batではなくシェルスクリプトや前述のようにexeファイルを使う手もある。

conhost.exeで文字選択中に出力がブロックされる

Windows コンソールホスト(conhost.exe)というのは、Windowsで従来ずっと使われてきたターミナルのことであり、この上でコマンドプロンプト(cmd.exe)やPowerShellやその他のコンソールアプリケーションが動く。conhost.exeに代わるものとして最近出てきたのがWindows Terminal(wt.exe)である。

この2つは以下の画像の通り、概ね見た目で区別できる。(Windows11以降でWindows Terminalをデフォルトにする機能が追加されるようであるが、そうしていない場合は、)cmdやPowerShell、あるいはpythonやnode.jsなどの対話コンソールを普通に開いた際にはこの上側のような感じのウインドウが表示されるはずである。

conhost.jpg
conhost.exeの見た目
wt.jpg
Windows Terminalの見た目

しかし、あまり知られていないようだが、このconhost.exeには「文字を選択した状態(ウインドウタイトルに「選択」と出る)だと一切の出力がブロックされる」という仕様があるようである(文字選択を容易にするため?参考: https://github.com/microsoft/terminal/issues/34)。例えばping -t localhostを実行して文字を選択してみるといいだろう。方向キーや文字を入力するなどして選択状態を解除すると出力は再開する。

この仕様により、ruby on rails - how can I stop my server from freezing when powershell is in 'select' mode? - Stack Overflow などにあるように、サーバーアプリケーションの実行が出力の際に意図せずブロックされるといった問題が起こる(筆者はerror_private_pageのサーバーを使っている際にこの問題に気付いた)。また、詳しい条件はわからないが、筆者の経験では、単にそのコンソールウインドウを長時間使わず放置するだけでも同じ現象が発生することがあり、文字選択をしないように気を付けるだけでは不十分なようである。

解決策としては、conhost.exe以外のターミナルを使えばよい。比較的新しいWindowsであればWindows Terminal、それが無理ならCygwinやGit Bashのターミナル(mintty.exeだっけ?)などでもよい(ただGit BashはCygwinと比べてWindowsアプリケーションとの互換性が微妙な気もする。例えばcmd.exeを開いて方向キーを押すと不自然な動作をする)。また、あくまでブロックされるのは出力に際してであるため、> nulなどで出力を全て捨てればブロックはされない(ただしそれでは不便という場合も多いだろう)。

cmd

あまり知られていないが、cmdでもbashなどと同じようにPROMPT変数にエスケープ文字を入れることでプロンプト(C:\Users>みたいなやつ)の色やスタイル(太字など)を変えられる。

コマンドプロンプトのプロンプトに色をつける方法 - Qiita

ちよぶろ。: コマンドプロンプトの色を変える。

ただしVista以降~Windows 10の途中(1607より前?)ではcmd+conhostという組み合わせ(といっても多分cmdを使うとしたら事実上conhostが必要となる)だとエスケープ文字がそのまま表示されてしまい色がつかない。これはansiconで解決できる。

Windows Terminal

プロファイルを自分で追加できるなどの仕様は、Windowsにしてはなかなか偉い感じがする。

フォントは、GUIからではなく設定ファイルを変更する必要はあるが、変えられる。デフォルトのCascadia CodeよりConsolasのほうが英数字が日本語のちょうど半分のサイズになるので読みやすい。

"defaults": {
            "font":{   "face": "Consolas",
            "size": 12
             }
        },
設定例

CSSのように複数のフォントを指定することはまだできないようである。https://github.com/microsoft/terminal/issues/2664 まあこれはしょうがないだろう。

コマンドライン引数をめぐる仕様

作業のため一時的に非公開となっている可能性があります。履歴もご確認ください。