rcmdnk's blog

Windows PowerShellクックブック

Windows 10のCreators Updateから標準コマンドシェルがPowerShell になりましたが、やはり主にWindowsでしか使わないPowerShellだと慣れてない人も多いはずです。

PowerShellスクリプトの編集などをCygwin等のBash環境などから行って その中ですぐにスクリプトを実行できると割と便利です。

というわけで、CygwinのBashの中からPowerShellなスクリプトを指定した場合に 直接実行できる様にする設定について。

Sponsored Links

Bash: commad_not_found_handle

Bash 4からcommand_not_found_handleという、 指定されたコマンドが無かった場合に実行される関数が実装されていています。

この中でコマンドとしてファイルなどを指定した際、その拡張子を見て 実際に実行するコマンドを指定したりすることが出来ます。

詳しくは上のポストにありますが、 上のポストでは.md.ccなど、通常のファイルを直接実行しようとすると vimで開くように設定してあります。

これに手を加えてこんな感じにします。

.bashrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
if [ "${BASH_VERSINFO[0]}" -ge 4 ];then
  _suffix_vim=(md markdown txt text tex cc c C cxx h hh java py rb sh)
  alias_function() {
    eval "${1}() $(declare -f ${2} | sed 1d)"
  }
  if ! type -a orig_command_not_found_handle >& /dev/null;then
    if type -a command_not_found_handle >& /dev/null;then
      alias_function orig_command_not_found_handle command_not_found_handle
    else
      orig_command_not_found_handle () {
        echo "bash: $1: command not found"
        return 127
      }
    fi
  fi
  command_not_found_handle() {
    cmd="$1"
    args="[email protected]"
    if [ -f "$cmd" ];then
      if echo " ${_suffix_vim[*]} "|grep -q "${cmd##*.}";then
        if type -a vim >& /dev/null;then
          vim "${args[@]}"
          return $?
        fi
      elif [ "${cmd##*.}" = "ps1" ];then
        if type -a powershell >& /dev/null;then
          powershell -F "${args[@]}"
          return $?
        fi
      fi
    fi
    orig_command_not_found_handle "${args[@]}"
  }
  shopt -s autocd # cd to the directory, if it is given as a command.
fi

vimの設定に加えてps1という拡張子の場合にpowershellコマンドへ突っ込む、というだけです。

これで、

example.ps1
1
Write-Output "PowerShell example!"

とかいうスクリプトを作ったとすると、 これがあるフォルダで

$ example.ps1
PowereShell example!

と実行できます。実際には

$ powershell -F example.ps1

が実行されてるわけです。

上の設定をしなければ

-bash: example.ps1: command not found

的なコマンドが見つからないというエラー(エラーコード127)が出ます。

Cygwinではデフォルトで

/cygdrive/c/WINDOWS/System32/WindowsPowerShell/v1.0

というフォルダ(もしくは現状のバージョン)へのPATHが通っていて この中にpowershellコマンドがあるので デフォルトで使えるはずです。

一応コマンドのチェックは入れています。

現在ではWindows以外の環境でもPowerShellを使えるため この設定をしておけばWindows以外でもBashから直接PowerShellなスクリプトを 呼ぶことが出来ます。

ので、この設定をしておけば他の環境でも もしPowerShellがインストールしてあれば使えます。

上に書いたBashでZshのsuffix aliasやauto cdを実現するの 中で、最初Bash 3の場合についても書いてましたが、 ちょっと勘違いしていて最後に実行したコマンドを取ってくるのが簡単には出来ないので Bash 4の場合だけにしてあります。

やろうと思えばhistoryを使って取ってきたりすることは可能だとは思いますが。

powershellに関してちょっと補足として、 -Fを入れなくても

$ powershell ./example.ps1

とすればスクリプトを実行できますが、

$ powershell example.ps1
example.ps1 : The term 'example.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ example.ps1
+ ~~~~~
    + CategoryInfo          : ObjectNotFound: (example.ps1:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

の様にカレントディレクトリにあるファイルを./省略して書くと ファイルではなくPowerShellの中のパス内にあるcmdlet等を探しに行って 無い、というエラーを吐いてきます。

ということでスクリプトを実行する時は基本的にはきちんと-Fを使ってファイルを与えるべきです。

シェル@Cygwinの特別仕様?

ただし、ちょっと注意が必要なのはこの方法を設定してあっても

$ ./example.ps1
./example.ps1: line 1: Write-Output: command not found

と、コマンド入力時に実行ファイルの様にしてしまうとこんな感じのエラーが出ます。

Cygwinの場合、何故かCygwin内での実行権限に関係なく ファイルを指定するとその内容をシェルスクリプトとして実行してしまうようです。

試しにこんなことをやってみると

$ echo 'echo $SHELL; echo $$' > example.txt
$ chmod 644 example.txt
$ ./example.txt
/bin/bash
123456
$ echo $$
9876

の様に、サブシェルを立ち上げてその中でファイルの内容を実行するという 状態になっています。

このシェルは必ず/bin/bashになるらしく、 zshに切り替えた環境で同じことをやっても/bin/bashを使う様です。

通常、他のUnixだと実行権限が無ければ

$ ./example.txt
bash: ./example.txt: Permission denied
$

みたいなエラーコード126のエラーで終わるはずです。

これがあるので、もしPowerShellが現在居るディレクトリとは別の場所にあると 上の方法ではどうしようもありません。 (どうしても/が入ってくると実行ファイルとして受け取られPermission deniedが出てしまいます。)

ちなみに、 MobaXtermでやってみても同じように実行権限が無くてもシェルスクリプトとして実行されました。

一方、powereshellへのパスは通っているのですが、実行しようとすると、

$ example.ps1
. : File C:\Users\user\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 cannot be loaded because running
scripts is disabled on this system. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlin
k/?LinkID=135170.
At line:1 char:3
+ . 'C:\Users\user\Documents\WindowsPowerShell\Microsoft.PowerShell_p ...
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess
./example.ps1 : File C:\cygwin64\home\user\example.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ ./example.ps1
+ ~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess
$

みたいなエラーが出ました。 何やら使おうとしてるスクリプトがMobaXtermの環境だと使えないということらしいです。

シェバングを設定して使う

これまでのファイルはShebangが書いてないものですが、 Shebangが書いてあると、

$ echo '#!/usr/bin/env powershell'  > example.txt
$ echo 'echo $SHELL; echo $$' >> example.txt
$ chmod 644 example.txt
$ ./example.txt
/bin/bash
12309

の様にShebangに従ってスクリプトを実行する様です。 (他のシステムではShebangがあろうとなかろうと実行権限が無ければエラーになります。)

なので、上の設定に加えて単にPowerShellのスクリプトの先頭に

#!/usr/bin/env powershell

と書いておけば上の様に/が入ってもきちんとShebangに従って実行してくれるので 上手く行きます。 これなら他のディレクトリにあっても大丈夫です。

というよりも、そもそも彼園とディレクトリのファイルを ./無しで呼ぶ、というのも気持ち悪い話でなので この方法の方が素直です。

ただし、Windows以外の環境だと、実行権限が付与されてないと実行されないので、 その場合は上のcommand_not_found_handleを使った方法が便利だったりします。

もしくはPowerShellのスクリプトも実行権限がついてないと実行しないようにしたい、 というのであれば逆にこの設定は邪魔になるのでやめた方が良いかも、という場合も。

Zsh: alias -s (suffix alias)

Zshの場合は簡単で

alias -s ps1='powershell -F'

と設定しておくと

$ example.ps1

$ powershell -F example.ps1

を呼ぶことになります。

まとめ

PowerShellスクリプトをCygwinの中とかから呼べたら便利なので より簡単に呼べる様に考えてみましたが、 一番シンプルで分かりやすいのはスクリプトにShebang書いておくことかな、という感じがしました。

ShebangはPowerShellスクリプト自体には単なるコメントで何も意味をなさないので 特に弊害も無いので自分で書くPowerShellスクリプトにはShebangを書いておくかな、と。

Sponsored Links
Sponsored Links

« GitHubのAPIのリクエスト制限を避けるためにclient idなどを取得する GNU Projectパッケージの最新バージョンを取得するワンライナーコマンド »