はじめに
PowerShell の小ネタ集その 2 です。かなり雑多な内容になっています。
1. 自動変数を除き、自分で設定した変数だけを一括削除する
2. 自分のグローバル IP を取る
3. テンポラリでコマンド実行して終わったらクリーンアップする
4. コマンドラインからリファレンスページを開く
5. コマンドラインから AWS の What’s New Feed を検索する
1. 自動変数を除き、自分で設定した変数だけを一括削除する
PowerShell はセッション開始時に以下のように自動変数を設定します。以下は macos で profile を特に設定せずに Get-Variable した例で、pwsh は 7 系です。
Name Value
---- -----
? True
^
$
args {}
ConfirmPreference High
DebugPreference SilentlyContinue
EnabledExperimentalFeatures {}
Error {}
ErrorActionPreference Continue
ErrorView ConciseView
ExecutionContext System.Management.Automation.EngineIntrinsics
false False
FormatEnumerationLimit 4
HOME /Users/user1
Host System.Management.Automation.Internal.Host.InternalHost
InformationPreference SilentlyContinue
input System.Collections.ArrayList+ArrayListEnumeratorSimple
IsCoreCLR True
IsLinux False
IsMacOS True
IsWindows False
MaximumHistoryCount 4096
MyInvocation System.Management.Automation.InvocationInfo
NestedPromptLevel 0
null
OutputEncoding System.Text.UTF8Encoding
PID 17621
PROFILE /Users/user1/.config/powershell/Microsoft.PowerShell_profile.ps1
ProgressPreference Continue
PSBoundParameters {}
PSCommandPath
PSCulture en-US
PSDefaultParameterValues {}
PSEdition Core
PSEmailServer
PSHOME /usr/local/microsoft/powershell/7
PSNativeCommandArgumentPassing Standard
PSScriptRoot
PSSessionApplicationName wsman
PSSessionConfigurationName http://schemas.microsoft.com/powershell/Microsoft.PowerShell
PSSessionOption System.Management.Automation.Remoting.PSSessionOption
PSStyle System.Management.Automation.PSStyle
PSUICulture en-US
スクリプトを書くに当たっていろいろ試行錯誤していると、その過程で一時的な変数を設定したり、別名で変数を作り直したり、そんな感じでいつのまにかゴミが溜まってしまうことはよくあります。
そうなると、自動変数を除いた自作変数だけを瞬間的に一括削除したいなぁと考えはじめます。しかし PowerShell にそんな機能はありません。今回はそれを私なりに実現した方法です。
profile を作成する
これは profile による PowerShell セッション開始時のスクリプト読み込みで実現できます。まずは profile.ps1 を作成します。
if (-not (Test-Path -LiteralPath $profile.CurrentUserAllHosts)) {
New-Item -Path $profile.CurrentUserAllHosts -ItemType File
}
profile を編集する
profile.ps1 に以下を記述します。
# Capture the variable state at terminal startup
New-Variable -Name '__defaultVars' -Value $(
(
(Get-Variable).Name.ForEach{
if ($_ -in '?', '^', '$') {
'`' + $_
}
else {
$_
}
} + '__defaultVars'
) | Sort-Object
) -Option Constant
このように書くと、PowerShell セッション開始時の profile 読み込みで概ね以下のような処理が動きます。
- セッション開始時に自動設定された変数の名前一覧をエスケープ処理しながら変数
__defaultVarsに代入 - 変数
__defaultVarsに__defaultVars(自分自身の変数名) を追加 -OptionパラメータにConstantを渡して定数化し、勝手に削除されないようにする
これで、セッション開始時の変数名一覧がキャプチャされました。あとはこれを使う関数を profile に追記し、読み込ませればよいです。
- セッション開始以降に設定された変数を一覧表示する関数
# List variables set after the terminal is ready
function global:gva {
Get-Variable -Name * -Exclude $__defaultVars -Scope Script
}
- セッション開始以降に設定された変数を削除する関数
# Delete variables set after the terminal is ready
function global:rva {
Remove-Variable -Name * -Exclude $__defaultVars -Scope Script
}
これで「自動変数を除き、自分で設定した変数だけを一括削除したい」が達成できました。-ErrorAction Ignore でエラーを握りつぶすようなこともないため、比較的クリーンな手法で実現できたといえます。
ただ注意点として、VSCode などツールの設定によってはそちら側で独自に設定した変数などがあり、これが削除時に予期せぬ挙動をしたりするので、削除対象から除外するなど対応が必要になるかもしれません。
PowerShell だと bash における exec $SHELL -l のようなことを直接は実現できないので、こういうちょっとしたクリーンアップ用コマンドも割と使うことがあります。
(以下コマンドで、現在のセッションの中で新しいセッションを起動することはできるのですが、PowerShell セッションが自分自身を再起動するような方法はない気がします。知っている方がいれば教えてほしいです)
Invoke-Command -ScriptBlock { & pwsh } -NoNewScope
ニッチすぎて誰得なネタですが、供養のために紹介しました。
2. 自分のグローバル IP を取る
上記に比べ、簡単かつ実用的なネタです。checkip.amazonaws.com のようなサイトに curl 的なコマンドを実行する関数を profile に登録しておけば、調べる手間が省けます (gip と打ちさえすればよい)
function gip {
param (
[Alias('c')]
[switch]$cidr
)
$ret = (Invoke-RestMethod -Uri 'checkip.amazonaws.com' -Method Get).Trim()
if ($cidr) {
$ret = '{0}/32' -f $ret
}
return $ret
}
3. テンポラリでコマンド実行して終わったらクリーンアップする
特定のコマンドを TMPDIR 配下などで実行して、終わったらその痕跡をすぐ削除したいようなケースが稀によくあります。PowerShell で関数化して、引数でスクリプトブロックを渡せるようにしておけば、テンポラリでの作業を少し楽にできます。
#Requires -Version 6.0
function usetemp {
param (
[Alias('c')]
[ScriptBlock]$command
)
$tmpDir = if ($IsWindows) {
$env:TMP
}
else {
$env:TMPDIR
}
# Create temporary directory
$tmpSubDir = $(New-Item -Path $tmpDir -Name $([System.Guid]::NewGuid().Guid) -ItemType Directory).FullName
try {
# Change temporary directory
Push-Location -LiteralPath $tmpSubDir
Write-Host $('Working in ''{0}''' -f $tmpSubDir) -ForegroundColor Cyan
# Execute command
$result = Invoke-Command -ScriptBlock $command
return $result
}
catch {
throw $_.Exception
}
finally {
# Cleaning
Pop-Location
Remove-Item -LiteralPath $tmpSubDir -Recurse -Force
}
}
使い方
usetemp -command { New-Item -Path "test.txt" -ItemType File | Get-ChildItem }
大事な点
- 一時フォルダ名が衝突しないように GUID を使う
- finally ブロックで後始末
4. コマンドラインからリファレンスページを開く
なにかのコマンドリファレンスを見たいが、ブラウザ操作でリファレンスを検索して該当のコマンドまでたどり着くのが面倒という場合に、目的のページにコマンドラインから直行する方法です。例として AWS Tools for PowerShell のリファレンスを対象とします (ちなみに AWS Tools for PowerShell のインストール方法はコチラ)
まず、引数でコマンド名を渡すときのコマンド一覧が TAB 補完で選択できるようになってほしいので、その設定をします。
# Get-EC2Instance などコマンドレット一覧が補完されてほしい awspwshref -command Get-EC2Instance
この補完の設定は、System.Management.Automation.IValidateSetValuesGenerator インターフェースを実装することで実現できます。GetValidValues メソッドでの戻り値が補完で表示される値になります。
#Requires -Version 6.0
class AWSPowerShellCommand : System.Management.Automation.IValidateSetValuesGenerator {
[string[]]GetValidValues() {
return (Get-AWSCmdletName).CmdletName
}
}
あとは以下のようにして、リファレンスの URL に引数で得たコマンド名を埋め込んでブラウザオープンする関数を書けばよいです。補完は param() 内のパラメータに対して [ValidateSet([])] を付与することで設定できます (oscmd はこちらで紹介した自作関数です)
function awspwshref {
param(
[ValidateSet([AWSPowerShellCommand])]
[Alias('c')]
[string]$command
)
$uri = 'https://docs.aws.amazon.com/powershell/latest/reference/items/{0}.html' -f $command
$commands = @{
'windows' = { cmd.exe /c "start """" ""$uri""" }
'macos' = { open $uri }
'linux' = { echo "no browser for gui!" }
}
oscmd @commands
return
}
これで、あるコマンドのリファレンスページをブラウザで開く一連の作業が一撃化できました。
5. コマンドラインから AWS の What’s New Feed を検索する
これもニッチすぎるネタなのですが、AWS の What’s New Feed でリリース情報を調べたいとします。弊社ではこの RSS フィードを垂れ流す slack チャンネルがあり活用していたのですが、なかなか能動的に見に行かなくなってしまいました。これを簡易的な方法でオンザフライに調べたいなぁという時に、コマンドラインで検索ワードを入力してそのままブラウザを開きます。
function awsfeed {
param (
[Alias('w')]
[string[]]$word
)
$words = $word -join '%2B'
$uri = (
'{0}?{1}' -f 'https://aws.amazon.com/new/', (
@(
'whats-new-content-all.sort-by=item.additionalFields.postDateTime'
'whats-new-content-all.sort-order=desc'
'awsf.whats-new-categories=*all'
'whats-new-content-all.q={0}'
'whats-new-content-all.q_operator=AND#What.27s_New_Feed'
) -join '&'
)
) -f $words
$commands = @{
'windows' = { cmd.exe /c "start """" ""$uri""" }
'macos' = { open $uri }
'linux' = { echo "no browser for gui!" }
}
oscmd @commands
return
}
まず引数で渡した複数の文字列を、’+’ を URL エンコードした値である ‘%2B’ で連結します。
$words = $word -join '%2B'
次に URL とクエリパラメータを組み立てます。
$uri = (
'{0}?{1}' -f 'https://aws.amazon.com/new/', (
@(
'whats-new-content-all.sort-by=item.additionalFields.postDateTime'
'whats-new-content-all.sort-order=desc'
'awsf.whats-new-categories=*all'
'whats-new-content-all.q={0}'
'whats-new-content-all.q_operator=AND#What.27s_New_Feed'
) -join '&'
)
) -f $words
クエリパラメータは以下の構造になっています。
| パラメータ | 意味 |
|---|---|
| whats-new-content-all.sort-by=item.additionalFields.postDateTime | postDateTime でソート指定 |
| whats-new-content-all.sort-order=desc | 降順でソート指定 |
| awsf.whats-new-categories=*all | 全カテゴリを指定 |
| whats-new-content-all.q={0} | $words に格納されている検索文字列を指定 |
| whats-new-content-all.q_operator=AND | 検索条件としてANDオペレータを指定 |
また、以下でアンカーを指定して画面上 What’s New Feed の位置にジャンプします。
#What.27s_New_Feed
あとはクエリパラメータを & で連結し、ベース URL とクエリパラメータの塊を ? で連結して URL を作り、OS ごとのコマンドでブラウザを開きます (oscmd はこちらで紹介した自作関数です)
$commands = @{
'windows' = { cmd.exe /c "start """" ""$uri""" }
'macos' = { open $uri }
'linux' = { echo "no browser for gui!" }
}
oscmd @commands
おわりに
コンテキストとしては PowerShell を活用した業務効率化かなと思いながらこの記事を書いていました。
毎日のようにターミナルを開くので、いろんな操作をコマンドで実行できるようにしておくと捗ります。楽がしたいモチベーションで調査をする中で技術力も向上できるので、こういう細かいことに拘るのはいいことだと思いました。