ドキュメントに書いてあるコマンドが動かない時(Windows)

ミドルウェアやフレームワーク、パッケージを導入する際にドキュメントを読みながら導入を進めていくのですが、 Windows の場合はコマンドが動かないことがあります。なぜかというとコマンドは UNIX 系(Linux とか Mac とか)で動かすことを前提に書かれているからです。

ですがいくつかの挙動の違いを理解すれば、コマンドを動くように修正することも可能です。動かないコマンドの例とその対処法を紹介します。

始めに確認すること

Windows のコマンドを打つところをこの記事ではターミナルと呼ぶことにします。まず普段お使いのターミナルを立ち上げて見てください。立ち上がったターミナルには何と書かれているでしょうか。

例1
例2
例3

Windows は人によって好みのターミナルが異なるという問題があります。まずは何を立ち上げているかを把握してもらうことが重要です。

Windows でのターミナルは主に2つに分けられます1WSL や mingw とかはここでは割愛させてもらいます

  • コマンドプロンプト
  • PowerShell 系2Windows PowerShell と PowerShell (いわゆる Core 版)では少し挙動が違いますが、この記事では影響しないので PowerShell で統一します。

ファイルパスを引用符で囲む

Windows だけではないですが、ファイルパスは引用符で囲んだ方が安全です。ファイルパスにスペースが含まれるとコマンドの区切り文字として認識されるためです。

例えば C:\s pace\php\php.exe という場所に実行したいファイルがある場合、引用符なしだと

c:\>C:\s pace\php\php.exe -v
'C:\s' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
PS C:\> C:\s pace\php\php.exe -v
C:\s : 用語 'C:\s' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください。
発生場所 行:1 文字:1
+ C:\s pace\php\php.exe
+ ~~~~
    + CategoryInfo          : ObjectNotFound: (C:\s:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

のようなエラーになります。C:\s pace\php\php.exe -v がスペースによって

  • C:\s
  • pace\php\php.exe
  • -v

の3つに分解され、 C:\s というプログラムがないか探しに行ってしまうためです。このような場合は引用符を付けます。

c:\>"C:\s pace\php\php.exe" -v
PHP 8.0.3 (cli) (built: Mar  2 2021 23:33:56) ( NTS Visual C++ 2019 x64 )
Copyright (c) The PHP Group
Zend Engine v4.0.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.3, Copyright (c), by Zend Technologies
PS C:\> & 'C:\s pace\php\php.exe' -v
PHP 8.0.3 (cli) (built: Mar  2 2021 23:33:56) ( NTS Visual C++ 2019 x64 )
Copyright (c) The PHP Group
Zend Engine v4.0.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.3, Copyright (c), by Zend Technologies

PowerShell の場合、単純に引用符で囲むとコマンドではなく単純な文字列として取り扱われるため、 & とスペースを先頭につける必要があります3但し引数にファイルパスを渡すケースでは不要です。& は Invoke-Expression のエイリアスです。

& の存在は忘れがちですが、スペースありのパスの場合は Tab キーを使ってフォルダ名を入力補完させると自動で引用符と & を付けてくれるので確実です。

特殊文字を引用符で囲む

Spring Boot How-to Guides より

java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar

上記のコマンドを PowerShell で打った場合、以下のようなエラーになります。※コマンドプロンプトでは正常に動作します。

Error: Unable to access jarfile .profiles.active=production

PowerShell では – が付くとパラメーター名として処理しようとする実装があり、推測ですが . はパラメーター名の名前として無効な文字なのでそこで引数が分割されるようです。この場合、以下のように引用符を付けて文字列として解釈させます。

java -jar '-Dspring.profiles.active=production' demo-0.0.1-SNAPSHOT.jar

または --% を使うことで、それ以降を式として解釈するのをやめるよう指示できます(但し変数も使えなくなるので注意)。

java --% -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar

コマンドプロンプトで同様の問題が発生したら ‘ ではなく ” (ダブルクォート)で囲みます。

引用符を変える

PowerShell は文字列の引用符が以下の2種類があります(クォーテーションだと長いのでクォートで統一します)。

  • ‘ シングルクォート
  • ” ダブルクォート

‘ の場合は単純に文字列として処理されます。” の場合は変数が中に入っていると展開されます。

PS C:\> echo '$PWD'
$PWD
PS C:\> echo "$PWD"
C:\

対して、コマンドプロンプトは文字列の引用符が ” しかないです。

具体的には以下のようなコマンドを実行する際に問題が発生します。(AWS CLIを使ってEC2インスタンス一覧を取得する より)

C:\> aws ec2 describe-instances --output=table --query 'Reservations[].Instances[].{InstanceId: InstanceId, PrivateIp: join(`, `, NetworkInterfaces[].PrivateIpAddress), GlobalIP: join(`, `, NetworkInterfaces[].Association.PublicIp), Platform:Platform, State: State.Name, SecurityGroupId: join(`, `, SecurityGroups[].GroupId) ,Name: Tags[?Key==`Name`].Value|[0]}'
'[0]}'' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

‘ は文字列用の引用符ではないため | がパイプとして認識されて、それに続く [0]}' がコマンドだと認識されたようです。以下のようにシングルクォートをダブルクォートにすることで対処可能です。

C:\> aws ec2 describe-instances --output=table --query "Reservations[].Instances[].{InstanceId: InstanceId, PrivateIp: join(`, `, NetworkInterfaces[].PrivateIpAddress), GlobalIP: join(`, `, NetworkInterfaces[].Association.PublicIp), Platform:Platform, State: State.Name, SecurityGroupId: join(`, `, SecurityGroups[].GroupId) ,Name: Tags[?Key==`Name`].Value|[0]}"

逆に PowerShell で ” ダブルクォートを使うと、 ` バッククォートがエスケープ文字として認識されるため、以下のようにうまく動きません。

PS C:\> aws ec2 describe-instances --output=table --query "Reservations[].Instances[].{InstanceId: InstanceId, PrivateIp: join(`, `, NetworkInterfaces[].PrivateIpAddress), GlobalIP: join(`, `, NetworkInterfaces[].Association.PublicIp), Platform:Platform, State: State.Name, SecurityGroupId: join(`, `, SecurityGroups[].GroupId) ,Name: Tags[?Key==`Name`].Value|[0]}"

Bad value for --query Reservations[].Instances[].{InstanceId: InstanceId, PrivateIp: join(, , NetworkInterfaces[].PrivateIpAddress), GlobalIP: join(, , NetworkInterfaces[].Association.PublicIp), Platform:Platform, State: State.Name, SecurityGroupId: join(, , SecurityGroups[].GroupId) ,Name: Tags[?Key==Name].Value|[0]}: invalid token: Parse error at column 68, token "," (COMMA), for expression:
"Reservations[].Instances[].{InstanceId: InstanceId, PrivateIp: join(, , NetworkInterfaces[].PrivateIpAddress), GlobalIP: join(, , NetworkInterfaces[].Association.PublicIp), Platform:Platform, State: State.Name, SecurityGroupId: join(, , SecurityGroups[].GroupId) ,Name: Tags[?Key==Name].Value|[0]}"

余談ですが、PowerShell でのコマンドの展開はバッククォートではなく $() を使用します

PS C:\> echo "$(Get-Location)"
C:\

$PWD

例は Docher Hub の Apache のページより。

PS C:\> docker run -dit --name my-apache-app -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4
docker: invalid reference format.
See 'docker run --help'.

$PWD は作業ディレクトリ(カレントディレクトリ)のパスが入っている変数です。今ターミナルで開いているフォルダのパスです。

コマンドプロンプトではこの $PWD が利用できません。なので、代わりに %CD% を使用します。

C:\> docker run -it --name my-apache-app -p 8080:80 -v "%CD%":/usr/local/apache2/htdocs/ httpd:2.4

PowerShell では $PWD が利用できますが、 "$PWD":/usr/local/apache2/htdocs/ という書き方では "$PWD":/usr/local/apache2/htdocs/ の2つに分解されてしまいます。ようするに、以下を実行したような動きになります。

PS C:\> docker run -dit --name my-apache-app -p 8080:80 -v C:\ :/usr/local/apache2/htdocs/ httpd:2.4

PowerShell のドキュメントには以下のように記載されています。

Argument mode is designed for parsing arguments and parameters for commands in a shell environment. All input is treated as an expandable string unless it uses one of the following syntaxes:
* Quotation marks (' and ") begin string values

about_Parsing – PowerShell | Microsoft Docs

PowerShell では式(引数)が ” で開始される場合は「展開可能な文字列」として取り扱われないようです。そのため文字列連結がされなくなり、別々の引数として扱われます。

PS C:\> echo "$PWD"a
C:\
a
PS C:\> echo a"$PWD"
aC:\

そのため、引用符は引数全体を囲むようにします。また、 $PWD${PWD} に置き換えます。{} をつけないと PWD: というようにコロンまでが変数名として解釈されるためです。

PS C:\> docker run -it --name my-apache-app -p 8080:80 -v "${PWD}:/usr/local/apache2/htdocs/" httpd:2.4

改行

Unix の場合、コマンドを途中で改行する場合は \ (バックスラッシュ。円マーク。)を使えば可能です。\ はエスケープ用の文字なので、行末の改行文字をエスケープするイメージです。(例は Helidon MP Quick Start より)

mvn -U archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-mp \
    -DarchetypeVersion=2.3.0 \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-mp \
    -Dpackage=io.helidon.examples.quickstart.mp

エスケープ文字は Powershell では ` (バッククォート。キーボードの @ のところにいるやつ)を使います。さらに前述の通り . は引用符で囲んで文字列とする必要があります。

PS C:\> mvn -U archetype:generate -DinteractiveMode=false `
    -DarchetypeGroupId='io.helidon.archetypes' `
    -DarchetypeArtifactId=helidon-quickstart-mp `
    -DarchetypeVersion='2.3.0' `
    -DgroupId='io.helidon.examples' `
    -DartifactId=helidon-quickstart-mp `
    -Dpackage='io.helidon.examples.quickstart.mp'

コマンドプロンプトでは ^ ハットを使います。

C:\> mvn -U archetype:generate -DinteractiveMode=false ^
    -DarchetypeGroupId=io.helidon.archetypes ^
    -DarchetypeArtifactId=helidon-quickstart-mp ^
    -DarchetypeVersion=2.3.0 ^
    -DgroupId=io.helidon.examples ^
    -DartifactId=helidon-quickstart-mp ^
    -Dpackage=io.helidon.examples.quickstart.mp

.cmd ファイル

Unix と Windows どちらも対応しようとする場合、スクリプトファイルは2つ用意する必要があります。下記例では拡張子がないファイルと、 .cmd 拡張子のファイルが用意されています。

拡張子がないファイルは Unix 系用の shell スクリプト、 .cmd は Windows 用のコマンドが記載されています。

このような場合、ドキュメントには

./mvnw

を実行するよう書かれています。これを実行すると Unix 系の OS では拡張子なしのファイルが、PowerShell では mvnw.cmd が実行されます。ただ、コマンドプロンプトの場合は以下のようなエラーになってしまいます。

C:\ > ./mvnw 
'.' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

このエラーが出た場合は、以下のように書き換えてあげる必要があります。

C:\ > mvnw.cmd

まとめ

コマンドが動かないときは以下を見直してみましょう。

  • PowerShell
    • 文字列は ‘ シングルクォート
    • エスケープと改行は ` バッククォート
    • – から始まる引数は引用符で囲む
    • Argument mode」の「expandable string」として判定される条件に注意
      • スペースなしで記述した時に、同一文字列として結合されるか、別の引数になるか
  • コマンドプロンプト
    • 文字列は ” ダブルクォート
    • 引用符のエスケープは “” ダブルクォート2つ
    • 改行は ^ ハット
    • 現在のディレクトリは %CD%

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください