「Windowsでエクスプローラーからフォルダを各種ターミナルで開く」の版間の差分
Notion-MW |
Notion-MW |
||
(同じ利用者による、間の15版が非表示) | |||
34行目: | 34行目: | ||
* Git BashではWindowsの実行ファイルにスラッシュ付きオプションを渡すときはスラッシュを2つにする必要がある。<code>cmd //c</code>や<code>bcdedit //v</code>など。 | * Git BashではWindowsの実行ファイルにスラッシュ付きオプションを渡すときはスラッシュを2つにする必要がある。<code>cmd //c</code>や<code>bcdedit //v</code>など。 | ||
* printf関数の%qオプションを使うと、文字列を<strong>エスケープされた状態にして</strong>返してくれる。これはshからの呼び出し先の別のshで再びフォルダ名をcdなどの引数として利用したいときに使える。 | * printf関数の%qオプションを使うと、文字列を<strong>エスケープされた状態にして</strong>返してくれる。これはshからの呼び出し先の別のshで再びフォルダ名をcdなどの引数として利用したいときに使える。 | ||
** | ** bash(POSIX互換ではない)の組み込みコマンドのprintfと、独立実行ファイル(GNU Coreutilsの一部)である/bin/printfがある。基本的な趣旨としては同一のコマンドだが、組み込みコマンドのほうは入力に<code>"</code>が含まれていない限り結果にも<code>"</code>が使われることはないようで、このおかげで意図しない動作を回避できることがある。今回扱う例に関しては前者が後者の上位互換であるということになる。後者を使っているものはそれでも動いたものである。 | ||
** そもそも%qは組み込みコマンドのほうにしかないという話もある。環境によって違いそう。 | ** そもそも%qは組み込みコマンドのほうにしかないという話もある。環境によって違いそう。 | ||
* <code>\\?\</code>が付くような長いパスをカレントディレクトリにしていても、Git Bash/Cygwinに付属の(というかMSYS/Cygwin向けにコンパイルされた?)exeなら実行できる。 | * <code>\\?\</code>が付くような長いパスをカレントディレクトリにしていても、Git Bash/Cygwinに付属の(というかMSYS/Cygwin向けにコンパイルされた?)exeなら実行できる。 | ||
* Git Bashの(ba)shは<code>--login</code> | * Git Bashの(ba)shは<code>--login</code>をつけて起動してもカレントディレクトリが維持されるが、<s>Cygwinの(ba)shは</s><s><code>--login</code></s><s>をつけると強制的にホームディレクトリに移動する。</s> | ||
** 2024/4頃、Cygwinの挙動が変化し、カレントディレクトリが維持されるようになっていることに気付く。変わっていた方のバージョンは3.5.3、変わっていない方は3.3.6であった。 | |||
** ところで、[https://superuser.com/questions/345964/start-bash-shell-cygwin-with-correct-path-without-changing-directory Start bash shell (cygwin) with correct path without changing directory - Super User]の通り、<code>CHERE_INVOKING</code>という変数を1に設定しておけばカレントディレクトリは維持される。 | |||
* Git Bashにおいては環境変数を正しく設定するため<code>--login</code>の時点で環境変数<code>MSYSTEM</code>を<code>MINGW64</code>にしておく必要がある。[[Windowsでのターミナル環境|Windowsでのターミナル環境]] 参照。 | * Git Bashにおいては環境変数を正しく設定するため<code>--login</code>の時点で環境変数<code>MSYSTEM</code>を<code>MINGW64</code>にしておく必要がある。[[Windowsでのターミナル環境|Windowsでのターミナル環境]] 参照。 | ||
46行目: | 48行目: | ||
** 以後、「powershell(.exe)」と「pwsh(.exe)」で呼び分ける。ただし手元ではpowershellの5.1とpwshの7.2~7.3くらいでしか試していないので、一部バージョンではまた違う可能性もある。 | ** 以後、「powershell(.exe)」と「pwsh(.exe)」で呼び分ける。ただし手元ではpowershellの5.1とpwshの7.2~7.3くらいでしか試していないので、一部バージョンではまた違う可能性もある。 | ||
** pwshは必ずしもWindowsに標準では入っていないので、以下の例でもあまり依存しないように気を付ける。 | ** pwshは必ずしもWindowsに標準では入っていないので、以下の例でもあまり依存しないように気を付ける。 | ||
** 基本、シングルクォートで囲えばほとんどの特殊文字が無効になり、シングルクォート自体を書くときは2つ並べる。ただし注意点として、驚くべきことにpowershell/pwshでは「<strong>全角のシングルクォート</strong>(U+2018-U+201B、<code>‘</code> <code>’</code> <code>‚</code> <code>‛</code>の4つ)」も通常のシングルクォートと同じ効力があるので、同じようにエスケープする必要がある(参照: [https://learn.microsoft.com/ja-jp/powershell/scripting/lang-spec/chapter-02?view=powershell-7.2 https://learn.microsoft.com/ja-jp/powershell/scripting/lang-spec/chapter-02?view=powershell-7.2])。掲載したスクリプトでは省略しているがやることは同じ。 | |||
* powershell.exe | * powershell.exe | ||
** -Commandがなくても付けたのと同じ扱いになる? | ** -Commandがなくても付けたのと同じ扱いになる? | ||
*** 逆に、- | *** 逆に、-Commandを付けなければいけないという点を除いてはpwshがpowershellの上位互換という感覚である(新しいのでそれはそう)。 | ||
** <code>`</code>や<code>[]</code>を含むフォルダの扱いにバグがあり、cmdなどでこれらのフォルダをカレントディレクトリとした状態で5系のPowerShellを起動すると<code>C:\</code>や<code>C:\Windows\System32\WindowsPowerShell\v1.0></code>をカレントディレクトリとして起動される(たとえば<code>C:\somefolder][</code>なら前者、<code>C:\somefolder[]</code>なら後者)。 | ** <code>`</code>や<code>[]</code>を含むフォルダの扱いにバグがあり、cmdなどでこれらのフォルダをカレントディレクトリとした状態で5系のPowerShellを起動すると<code>C:\</code>や<code>C:\Windows\System32\WindowsPowerShell\v1.0></code>をカレントディレクトリとして起動される(たとえば<code>C:\somefolder][</code>なら前者、<code>C:\somefolder[]</code>なら後者)。 | ||
** -Fileを使用してスクリプトを実行するとき、ダブルクォーテーションで囲われていない <code>-</code> が引数に含まれているとエラーになるバグがある。 | |||
* powershell.exe・pwsh.exe共通 | * powershell.exe・pwsh.exe共通 | ||
** 連続した<code>`</code>があるフォルダに関する挙動にバグがある。コンソールプログラムを実行しても新規ウインドウで開かれる、そこをカレントディレクトリとしたときにファイルをmvできないなど。 | ** 連続した<code>`</code>があるフォルダに関する挙動にバグがある。コンソールプログラムを実行しても新規ウインドウで開かれる、そこをカレントディレクトリとしたときにファイルをmvできないなど。 | ||
** <code>`</code>や<code>[]</code>を含むフォルダに関しても、絶対パス指定したりきちんとエスケープしたりすれば操作自体はできる(場合もある)。 | ** <code>`</code>や<code>[]</code>を含むフォルダに関しても、絶対パス指定したりきちんとエスケープしたりすれば操作自体はできる(場合もある)。 | ||
*** エスケープはいずれも直前に<code>`</code>を付ける。 | |||
*** これらを特殊文字として扱いたくないときは<code>-LiteralPath</code>を付けると良い場合もある。 | *** これらを特殊文字として扱いたくないときは<code>-LiteralPath</code>を付けると良い場合もある。 | ||
** UNCパスをカレントディレクトリにすることができるが、<code>Microsoft.PowerShell.Core\FileSystem::\\</code>とかいう謎のプレフィックスが付く。 | ** UNCパスをカレントディレクトリにすることができるが、<code>Microsoft.PowerShell.Core\FileSystem::\\</code>とかいう謎のプレフィックスが付く。 | ||
70行目: | 75行目: | ||
これを防ぐ方法はいくつかある。ちなみに、<code>%%</code>でのエスケープというのはバッチファイルの中だけの話で、今回の場面では以下より簡単なエスケープ方法は多分ないと思う。 | これを防ぐ方法はいくつかある。ちなみに、<code>%%</code>でのエスケープというのはバッチファイルの中だけの話で、今回の場面では以下より簡単なエスケープ方法は多分ないと思う。 | ||
< | <ol style="list-style-type: decimal;"> | ||
<li><p>既に定義されているかもしれない環境変数をバックアップし、一時的にその中身を<code>%</code>に変えて使用することでリテラル文字としての<code>%</code>を表現し、後で元に戻す。</p> | <li><p>既に定義されているかもしれない環境変数をバックアップし、一時的にその中身を<code>%</code>に変えて使用することでリテラル文字としての<code>%</code>を表現し、後で元に戻す。</p> | ||
<p>すなわち、<code>MY_PERCENT</code>のような変数の中身をバックアップし、<code>MY_PERCENT</code>の中身を<code>%</code>に変え、未知の文字列中の<code>%</code>をすべて<code>%MY_PERCENT%</code>にすることで<strong>この部分が置換によって</strong><strong><code>%</code></strong><strong>に変わるのを利用して</strong><code>%</code>をそのまま渡すということである(環境変数の置換は左から順に行われるため、たとえば<code>%MY_PERCENT%PATH%MY_PERCENT%</code>の<code>%PATH%</code>が置換されることはない)。受け渡しが終わったら<code>MY_PERCENT</code>の中身を復元する。</p> | <p>すなわち、<code>MY_PERCENT</code>のような変数の中身をバックアップし、<code>MY_PERCENT</code>の中身を<code>%</code>に変え、未知の文字列中の<code>%</code>をすべて<code>%MY_PERCENT%</code>にすることで<strong>この部分が置換によって</strong><strong><code>%</code></strong><strong>に変わるのを利用して</strong><code>%</code>をそのまま渡すということである(環境変数の置換は左から順に行われるため、たとえば<code>%MY_PERCENT%PATH%MY_PERCENT%</code>の<code>%PATH%</code>が置換されることはない)。受け渡しが終わったら<code>MY_PERCENT</code>の中身を復元する。</p> | ||
93行目: | 98行目: | ||
</li> | </li> | ||
<li>ただ、<code>==%</code>を<code>%</code>に置換するという操作はPowerShellやshにとっては容易でもcmdにとっては不可能に近いので、最終的な呼び出し先がcmdであるときには採用しづらい。</li></ul> | <li>ただ、<code>==%</code>を<code>%</code>に置換するという操作はPowerShellやshにとっては容易でもcmdにとっては不可能に近いので、最終的な呼び出し先がcmdであるときには採用しづらい。</li></ul> | ||
</li></ | </li></ol> | ||
また、今回の記事ではcmdやvbsのRunを経由してコマンドを実行しているものが多くあるが、<strong>このスクリプトの内容自体もエスケープが必要</strong>である。つまり、例えば<code>cmd /c</code>の内側に<code>$mypath.Replace('%','%MY_PERCENT%')</code>というPowershellコードが含まれるなら、<code>','</code>や<code>MY_PERCENT</code>という環境変数が定義されていた時にその部分が置換されてしまう。これを回避するのはそこまで難しくなく、この例であれば<code>$mypath.Replace('%'.Trim('='),'%'.Trim('=')+'MY_PERCENT%')</code>のように<code>=</code>を含む無意味なコードを挿入すればよい。また、<code>%</code>をいったん<code>==%</code>にしている部分に関しては、<code>==%</code>ではなく<code>==%==</code>にすることで問題を避けられるだろう。無駄に複雑になるのでスクリプト例ではそのような措置はしていない。 | また、今回の記事ではcmdやvbsのRunを経由してコマンドを実行しているものが多くあるが、<strong>このスクリプトの内容自体もエスケープが必要</strong>である。つまり、例えば<code>cmd /c</code>の内側に<code>$mypath.Replace('%','%MY_PERCENT%')</code>というPowershellコードが含まれるなら、<code>','</code>や<code>MY_PERCENT</code>という環境変数が定義されていた時にその部分が置換されてしまう。これを回避するのはそこまで難しくなく、この例であれば<code>$mypath.Replace('%'.Trim('='),'%'.Trim('=')+'MY_PERCENT%')</code>のように<code>=</code>を含む無意味なコードを挿入すればよい。また、<code>%</code>をいったん<code>==%</code>にしている部分に関しては、<code>==%</code>ではなく<code>==%==</code>にすることで問題を避けられるだろう。無駄に複雑になるのでスクリプト例ではそのような措置はしていない。 | ||
135行目: | 140行目: | ||
* 特殊文字に関しては、最初は空白のないフォルダ、次に空白のあるフォルダや連続空白のあるフォルダ、次にシングルクォートや<code>%PATH%</code>を含むフォルダなどとだんだん難しくしていくとよい。 | * 特殊文字に関しては、最初は空白のないフォルダ、次に空白のあるフォルダや連続空白のあるフォルダ、次にシングルクォートや<code>%PATH%</code>を含むフォルダなどとだんだん難しくしていくとよい。 | ||
* それとは別に、Unicode対応、<code>C:\</code>(ドライブ直下)対応、259文字のフォルダ対応(後述)などをチェックする | * それとは別に、Unicode対応、<code>C:\</code>(ドライブ直下)対応、259文字のフォルダ対応(後述)などをチェックする | ||
* 難しい例は、例えば<code>z 𠮷𠮷%PATH% ' '' ` `` $PATH & && %%PATH%%[] ] [ ^ ^^ ' ; ;; 𩸽𩸽!PATH!#$%&'()=~{`+}_,.][;@^-</code> | * 難しい例は、例えば<code>z 𠮷𠮷%PATH% ' '' ` `` $PATH & && %%PATH%%[] ] [ ^ ^^ ' ; ;; 𩸽𩸽!PATH!#$%&'()=~{`+}_,.][;@^- - ‘ ’ ‚ ‛</code>のような感じ(適当)。こんなファイル名を現実に見ることはないが、エクスプローラー上で普通に入力できる内容である。 | ||
** | ** 「𠮷」はBMP外かつJIS外の漢字としては最も変換で出しやすいのでテストに重宝する。(𩸽はBMP外だがJIS外ではない(第4水準)) | ||
==== その他 ==== | ==== その他 ==== | ||
157行目: | 162行目: | ||
cmdやPowerShellやCygwinのbashなどのシェルを使えば、スクリプトを起動して複雑なコマンドを呼び出せたり、パイプ実行が可能になったりと色々便利だが、それだけのためには大仰すぎて、特殊文字の扱いなどがかえって仇になることもある。 | cmdやPowerShellやCygwinのbashなどのシェルを使えば、スクリプトを起動して複雑なコマンドを呼び出せたり、パイプ実行が可能になったりと色々便利だが、それだけのためには大仰すぎて、特殊文字の扱いなどがかえって仇になることもある。 | ||
そこで、シェル関連の操作に汎用的に使えるコマンドをいくつか作成して公開した。[https://github.com/ge9/win-console-delegator https://github.com/ge9/win-console-delegator] | そこで、シェル関連の操作に汎用的に使えるコマンドをいくつか作成して公開した。[https://github.com/ge9/win-console-tools https://github.com/ge9/win-console-tools] | ||
* 以前はC++で書いていた([https://github.com/ge9/win-console-delegator https://github.com/ge9/win-console-delegator])が、C#のほうが文字列の扱いなどが簡明。 | |||
ただし、これはあくまで筆者が独自に作ったものであり、Windows標準のもので何とかしようとするのが面白いところでもあるので、それほど積極的には使わない。今後出てくる例はすべて、これらを一切使わなくても(余計な環境変数が設定されてしまうかもしれないという点をのぞけば)同等の機能が実現できる。 | ただし、これはあくまで筆者が独自に作ったものであり、Windows標準のもので何とかしようとするのが面白いところでもあるので、それほど積極的には使わない。今後出てくる例はすべて、これらを一切使わなくても(余計な環境変数が設定されてしまうかもしれないという点をのぞけば)同等の機能が実現できる。 | ||
188行目: | 195行目: | ||
<p>startrunと同じだが、コンパイル時のマニフェスト設定でこのプログラム自体の実行に管理者権限を要求するようにしたので、adminrunと同じように使える(管理者権限で実行する対象が渡されたコマンドではなくuacrun自体になるという違いはある)</p></li> | <p>startrunと同じだが、コンパイル時のマニフェスト設定でこのプログラム自体の実行に管理者権限を要求するようにしたので、adminrunと同じように使える(管理者権限で実行する対象が渡されたコマンドではなくuacrun自体になるという違いはある)</p></li> | ||
<li><p>pecho</p> | <li><p>pecho</p> | ||
<p> | <p>与えられたコマンドライン引数をそのままコンソールに出力する。→WindowsでUnicode文字(特にU+10000以上)を正しく表示(&ファイルに書き込み)するには、「出力先がコンソールならWriteConsoleW、そうでなければWriteFileを使用する」といった非常に面倒な実装が必要であり(参照: [https://twitter.com/mattn_jp/status/542581083242364928 https://twitter.com/mattn_jp/status/542581083242364928] など)、C++やC#では扱いづらい。そこでrustを用いて実装した。→[https://github.com/ge9/pure-echo-win pure-echo-win]</p></li> | ||
<li><p>printcd</p> | <li><p>printcd</p> | ||
<p>カレントディレクトリをコンソールに出力する。(ちなみにpwdの由来はprint working directoryなのでそれに倣った)</p></li></ul> | <p>カレントディレクトリをコンソールに出力する。(ちなみにpwdの由来はprint working directoryなのでそれに倣った)</p></li></ul> | ||
<span id="注意:-当初は後述のnoworkingdirectoryを知らなかったので1の方法に頼っていたが実際には2の方法のほうが簡明なのでそちらを使うことを勧める1は残しておくが読む必要はあまりない"></span> | |||
== 注意: 当初は後述のNoWorkingDirectoryを知らなかったので1.の方法に頼っていたが、実際には2.の方法のほうが簡明なので、そちらを使うことを勧める。1.は残しておくが、読む必要はあまりない。 == | |||
<span id="1標準入力から受け取る場合"></span> | <span id="1標準入力から受け取る場合"></span> | ||
302行目: | 312行目: | ||
たとえば以下のようなvbsを用意する。 | たとえば以下のようなvbsを用意する。 | ||
<syntaxhighlight style="margin-bottom:0.2em;" lang="vb.net">Dim objShell | |||
<syntaxhighlight lang=" | |||
Set objShell = CreateObject("WScript.Shell") | Set objShell = CreateObject("WScript.Shell") | ||
objShell.CurrentDirectory = WScript.Arguments(1) | objShell.CurrentDirectory = WScript.Arguments(1) | ||
490行目: | 498行目: | ||
<li><p>vbsの例</p> | <li><p>vbsの例</p> | ||
<p>次の節で解説する「(自分が起動された)カレントディレクトリでの起動」にも対応している(引数がない場合)。</p> | <p>次の節で解説する「(自分が起動された)カレントディレクトリでの起動」にも対応している(引数がない場合)。</p> | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="vb.net">Dim objShell | ||
Dim curDir | |||
Set ws = CreateObject("Wscript.Shell") | |||
Dim dir | |||
If WScript.Arguments.Count = 0 Then | |||
dir = ws.CurrentDirectory | |||
Else | |||
dir = Wscript.Arguments(0) | |||
End If | |||
dir = Replace(dir,"%","==%") | |||
ws.run "powershell -Command ""& {Start-Process -Verb Runas -Filepath wt -Argumentlist powershell, -noexit, -command, Set-Location, -LiteralPath, ('\""'''+($args[1].Trim('\""') -replace '''', '''''' -replace ';', '\;' )+'''.Replace(''==%'',''%'')\""')}"" --% ""\"""&dir&"\""""", vbHide | |||
Set objShell = Nothing</syntaxhighlight></li></ul> | |||
</li> | |||
< | |||
<li><p>cmd</p> | <li><p>cmd</p> | ||
<p><code>powershell -Command "& {$bak=$env:MY_PERCENT;$mypath=$args[1].Trim('\"'); $env:MY_PERCENT = '%%';if ($mypath.Length -gt 258) { $mypath = cmd /c \"for %%A in (`\"$($mypath.Replace('%%', '%%MY_PERCENT%%'))`\") do @echo %%~sA\" };$env:MY_PERCENT = $bak;Start-Process -Verb Runas -Filepath wt -Argumentlist '-d', ('\"'+($mypath -replace '\\$','\\\\' -replace ';', '\;')+'\"'), cmd}" --%% "\"%V\""</code></p> | <p><code>powershell -Command "& {$bak=$env:MY_PERCENT;$mypath=$args[1].Trim('\"'); $env:MY_PERCENT = '%%';if ($mypath.Length -gt 258) { $mypath = cmd /c \"for %%A in (`\"$($mypath.Replace('%%', '%%MY_PERCENT%%'))`\") do @echo %%~sA\" };$env:MY_PERCENT = $bak;Start-Process -Verb Runas -Filepath wt -Argumentlist '-d', ('\"'+($mypath -replace '\\$','\\\\' -replace ';', '\;')+'\"'), cmd}" --%% "\"%V\""</code></p> | ||
515行目: | 521行目: | ||
<p><code>"C:\Program Files\Git\usr\bin\sh.exe" "C:\path\to\gb-wt-admin.sh" "%V"</code></p> | <p><code>"C:\Program Files\Git\usr\bin\sh.exe" "C:\path\to\gb-wt-admin.sh" "%V"</code></p> | ||
<p>で、<code>gb-wt-admin.sh</code>の中身は以下。</p> | <p>で、<code>gb-wt-admin.sh</code>の中身は以下。</p> | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="bash">#!/bin/sh | ||
IFS= | |||
LANG=en_US.UTF-8 | |||
echo $*|/bin/sed 's/\\/\\\\/'|/bin/cygpath -f -|printf %q $(/bin/cat) |/bin/sed "s/'/''/g;s/%/==%/g;s/;/\\\\;/g"|/bin/xargs -d '\n' -I {} powershell -Command Start-Process -Filepath wt -ArgumentList "'"'"C:\Program Files\Git\usr\bin\env.exe"'"'" , '"MSYSTEM=MINGW64"' , "'"'"C:\Program Files\Git\usr\bin\sh.exe"'"'" , "'--login'", "'-c'", "'"'"IFS=\;cd $(echo {}| /bin/sed s/==%/%/g) \; exec bash"'"'"</syntaxhighlight> | |||
<p>Cygwinも同様である。</p></li></ul> | |||
<span id="vs-codeのメニューおまけ"></span> | <span id="vs-codeのメニューおまけ"></span> | ||
==== VS Codeのメニュー(おまけ) ==== | ==== VS Codeのメニュー(おまけ) ==== |