himorogiの日記

主にプログラミングに関することなど。少々ハード(電子工作)についても。

【macOS】PowerShell から WebAPI を呼ぶ

Web Access

PowerShellmacOS にやってきて、.Net 周りの機能はいろいろ入ってなかったので、てっきり WebClient (というか DownloadFile/DownloadString)は使えないと思っていたのだけど、確認したら既に使えるようになってたし、Invoke-RestMethod や Invoke-WebRequest も使えた。

Yahoo! Open Local Platform

以前 GoogleMap の StreetView をダウンロードする script を公開したけど、今は無償利用でも GoogleMap では決済情報の登録が必須になったようなので、今回は Yahoo! の地図・地域情報を利用した。

YOLP(地図):YOLP(地図) - Yahoo!デベロッパーネットワークdeveloper.yahoo.co.jp

事前準備

Yahoo! の WebAPI は決済情報の登録は不要だけど、WebAPI の呼びしに Client ID が必要になるので予め YahooID を作成し、それに紐付いたアプリケーション毎の Client ID を取得する必要がある。

ご利用ガイド - Yahoo!デベロッパーネットワークdeveloper.yahoo.co.jp

早速やってみる

Yahoo!ID を作成し、Client ID を取得したら、実際にやってみよう。
今回は、気象情報API で特定の場所の降水強度を実測値・予測値として受け取る。

YOLP(地図):気象情報API - Yahoo!デベロッパーネットワークdeveloper.yahoo.co.jp

気象情報API では、場所の指定に経度・緯度を渡す必要がある。
そこで、住所文字列からジオコーダAPI で経度・緯度を取得しておく。

YOLP(地図):Yahoo!ジオコーダAPI - Yahoo!デベロッパーネットワークdeveloper.yahoo.co.jp

Yahoo!ジオコーダAPI を呼ぶ

Client ID は 変数 $CID に格納しておく。
PowerShell から WebAPI を呼び出す方法はいくつかあるけど、ここでは WebClient の DownloadString を使う。
Yahoo! WebAPI の戻り値のデータ・フォーマットは XML が既定値だけど JSONP も選択できる。
PowerShell では XML を簡単に Custom Object へ展開できるので、ここはデフォルトで。

リクエストURL+パラメータ文字列
https://map.yahooapis.jp/geocode/V1/geoCoder?appid=<Client ID>&query=<住所文字列>

手順は以下の通り。

PS /Users/hoge/devPwsh> $CID="<取得した Clinent ID>"
PS /Users/hoge/devPwsh> $yolp = "https://map.yahooapis.jp"        
PS /Users/hoge/devPwsh> $wc = New-Object System.Net.WebClient
PS /Users/hoge/devPwsh> $x = [xml]$($wc.DownloadString( "$yolp/geocode/V1/geoCoder?appid=$CID&query=" + "東京都港区虎ノ門1丁目23"))

というわけで、展開してみる。

PS /Users/hoge/devPwsh> $x

xml                            YDF #comment
---                            --- --------
version="1.0" encoding="utf-8" YDF  xxxcache nohit 0.084, 0.001, 0.001 

PS /Users/hoge/devPwsh> $x.YDF        

xmlns                 : http://olp.yahooapis.jp/ydf/1.0
totalResultsReturned  : 5
totalResultsAvailable : 5
firstResultPosition   : 1
ResultInfo            : ResultInfo
Feature               : {東京都港区虎ノ門1丁目23, 東京都港区虎ノ門1丁目23-3, 東京都港区虎ノ門1丁目23-1, 東京都港区虎ノ門1丁目23-4…}

PS /Users/hoge/devPwsh>

Yahoo!ジオコーダAPI は、該当する住所がデータベースになかったり、また複数の住所がヒット(ここでは枝番の存在)したときに、それらを含めて最大10件の住所情報を返す。(REST request のパラメータ、results(=表示件数)のデフォルトが10件なので。これは最大100件まで指定可能)
今回、最初にヒットした住所がそまま使えるので…

PS /Users/hoge/devPwsh> $x.YDF.Feature[0]

Id          : 13103.18.1.23
Gid         : 
Name        : 東京都港区虎ノ門1丁目23
Geometry    : Geometry
Category    : 
Description : 
Style       : 
Property    : Property

PS /Users/hoge/devPwsh> $x.YDF.feature[0].Geometry                 

Type  Coordinates              BoundingBox
----  -----------              -----------
point 139.75022366,35.66905771 139.74074600,35.66094700 139.75178800,35.67094600

PS /Users/hoge/devPwsh> $x.YDF.feature[0].Geometry.Coordinates
139.75022366,35.66905771
PS /Users/hoge/devPwsh> 

これで、東京都港区虎ノ門1丁目23 の経度・緯度が取得できた。

Yahoo!気象情報API を呼ぶ

PowerShell 3.0 から、Invoke-RestMethod と Invoke-WebRequet が使えるようになったので、今度は Invoke-RestMethod を使ってみる。

リクエストURL+パラメータ文字列
https://map.yahooapis.jpweather/V1/place?appid=<Client ID>&coordinates=<経度>,<緯度>

手順は以下の通り。
Yahoo! の WebAPI を利用するなら Invoke-RestMethod が一番手間がかからない。

PS /Users/hoge/devPwsh> $yolp = "https://map.yahooapis.jp" 
PS /Users/hoge/devPwsh> $y = Invoke-RestMethod -Uri $("$yolp/weather/V1/place?appid=$CID&coordinates=" + "139.75001544,35.66663019")
PS /Users/hoge/devPwsh> $y

xml                            YDF
---                            ---
version="1.0" encoding="UTF-8" YDF

PS /Users/hoge/devPwsh> $y.YDF

xmlns                 : http://olp.yahooapis.jp/ydf/1.0
firstResultPosition   : 1
totalResultsAvailable : 1
totalResultsReturned  : 1
ResultInfo            : ResultInfo
Feature               : Feature


PS /Users/hoge/devPwsh> $y.YDF.Feature

Id                              Name                                                                  Geometry Property
--                              ----                                                                  -------- --------
202102071115_139.75002_35.66663 地点(139.75002,35.66663)の2021年02月07日 11時15分から60分間の天気情報 Geometry Property

PS /Users/hoge/devPwsh>

正常に取得できてる模様なので、早速展開してみる。

PS /Users/hoge/devPwsh> $y.YDF.feature.Property

WeatherAreaCode WeatherList
--------------- -----------
4410            WeatherList

PS /Users/hoge/devPwsh> $y.YDF.feature.Property.WeatherList

Weather
-------
{Weather, Weather, Weather, Weather…}

PS /Users/hoge/devPwsh> $y.YDF.feature.Property.WeatherList.Weather

Type        Date         Rainfall
----        ----         --------
observation 202102071115 0.00
forecast    202102071125 0.00
forecast    202102071135 0.00
forecast    202102071145 0.00
forecast    202102071155 0.00
forecast    202102071205 0.00
forecast    202102071215 0.00

PS /Users/hoge/devPwsh>

降水強度(気象レーダーからの換算値)が獲得できた。

【macOS】PowerShell から SQLite を使うための備忘録【.import】

CSV の import なら SQLite の DotCommand でもできるけど…

昨日書いたのは、要するに CSV のデータを PowerShell で読み込んで INSERT Query 生成して SQLite に読み込ませる手順。
CSV import なら SQLite 自身、.import コマンドで対応できる…が…

とにかく一度やってみよう。

まずCSV を準備(昨日の使いまわし)

Proverb.csv

"ID","name","phrase"
"1","厩戸皇子","以和爲貴"
"2","釈迦","天上天下唯我独尊"
"3","孔子","有朋自遠方来不亦楽乎"
"4","杜甫","国破山河在"

CSV にもいろいろなフォーマットがあるが、普通はこういう形。
ところが、SQLite では separator の default は '|' なので、昨日 INSERT した結果を SELECT したら…

PS /Users/hoge/handleProverb> ./selectProverb.ps1
1|厩戸皇子|以和爲貴
2|釈迦|天上天下唯我独尊
3|孔子|有朋自遠方来不亦楽乎
4|杜甫|国破山河在
PS /Users/hoge/handleProverb>

こうなる。

.import の実例

.import でそのまま Proverb.csv を読むと、カラムの判別ができないので、.separetor , で変更しないといけない。
*1
面倒なんで、CREATE TABLE から .import , SELECT を一括して処理したのが以下の通り。

importProverb.ps1

@"
CREATE TABLE Proverb ( id, name, phrase );
.separator ,
.import ./Proverb.csv Proverb
SELECT * FROM Proverb;
"@ | sqlite3 ./myFirst.db $_

実行結果は…

PS /Users/hoge/handleProverb> ./importProverb.ps1
ID,name,phrase
1,厩戸皇子,以和爲貴
2,釈迦,天上天下唯我独尊
3,孔子,有朋自遠方来不亦楽乎
4,杜甫,国破山河在
PS /Users/hoge/handleProverb>

はい、読み込めてます…が、最初のレコード、header が入ってる。
つまり、.import で読み込む CSV には header 入れちゃ拙い。 .import のパラメータには header 無視するとかいう指定はない(はず)。
まぁ、予め CSV の一行削除するとか、DELETE 文で header 削除するとかやりようはあるけど、要するに手間が一つ余計にかかる(とはいっても、上記の例なら .import の直後に DELET 文一行追加するだけなので大した手間ではないけど、これを忘れてうっかり header 残したままにすることが、実は一番の問題だと思う)。

そういうわけで私はあんまり .import 使わないのだけど、大量のレコードを読み込む必要があって PowerShell の遅さに我慢できないときは、予め CSV の header 削除するか、DELETE文追加するくらいの手間と header 消し忘れるリスクは許容できる…かもしれない。

【追記】

せっかくなので、CSV の header 行削除板を…

importProverbHeaderless.ps1

get-content ./Proverb.csv | Select-Object -skip 1 | set-content ./ProverbHeaderless.csv
@"
CREATE TABLE Proverb ( id, name, phrase );
.separator ,
.import ./ProverbHeaderless.csv Proverb
SELECT * FROM Proverb;
"@ | sqlite3 ./myFirst.db $_

実行結果も…

PS /Users/hoge/handleProverb> ./importProverbHeaderless.ps1 
1,厩戸皇子,以和爲貴
2,釈迦,天上天下唯我独尊                                                                                                    
3,孔子,有朋自遠方来不亦楽乎
4,杜甫,国破山河在
PS /Users/hoge/handleProverb>

*1:【2021/01/13 追記】 .separator による default 値の変更は session 中のみ有効なので、毎回指定する必要がある(はず)。

【macOS】PowerShell から SQLite を使うための備忘録

前振り

現役時代 Windows 環境では PowerShell 活用し、業務では結構大量のデータを扱ってたために MS Access より SQLite を重宝してた。
定年リタイアで mac 専科に復帰したため、script は専ら Javascript でやろうと思ってたが、JXA は過疎ってるし node.js は逆に Version 管理面倒くさい…
とか思ってたら PowerShell core の登場で mac でも PowerShell が使えるように。

…が、リタイアしちゃったので、あんまり PowerShell 使う必要性に迫られない。
一応、pwsh を VSCode の terminal に常時立ち上げて shell として使ってるが、script 書くことは稀。

流石にこれではいかんと SQLite のハンドリングする PowerShell の script 書いてたら、どちらも暫く使ってないので結構忘れてた。
しかも今や老眼で、昔ならリファレンスやハンドブックを気軽に検索できたことが目が滑って全然頭に入らない。

というわけで、もっと老化(老眼や記憶力の低下)が進んだときに備え :-p) 基本の基本、あえて書くほどでもない極々当たり前の tips を、紙のメモだと見辛いのでここに記す。

sample データを準備する(csv

writeProverb2CSV.ps1

@"
1,厩戸皇子,以和爲貴
2,釈迦,天上天下唯我独尊
3,孔子,有朋自遠方来不亦楽乎
4,杜甫,国破山河在
"@ | ConvertFrom-CSV -header "ID","name","phrase" | Export-CSV Proverb.csv -NTI

いきなり script 出たけど、CSV ファイル構築するのに、Excel から引っ張ると妙な自動変換されることがあるし、text editor で編集するのは、" や ’ で item 毎に毎回文字列括るのが面倒。だからここでは here document で列挙し、それを一旦 ConvertForm-CSV に渡し、Export-CSV で書き出した。

Export-CSV の -NTI パラメータは PowerShell ver6 以降では要らないみたい…

以下は here document 使わない場合。

ConvertFrom-CSV(
    "1,厩戸皇子,以和爲貴",
    "2,釈迦,天上天下唯我独尊",
    "3,孔子,有朋自遠方来不亦楽乎",
    "4,杜甫,国破山河在"
) -header "ID","name","phrase" | Export-CSV Proverb.csv -NTI

その結果の Proverb.csv

"ID","name","phrase"
"1","厩戸皇子","以和爲貴"
"2","釈迦","天上天下唯我独尊"
"3","孔子","有朋自遠方来不亦楽乎"
"4","杜甫","国破山河在"

まぁ、実際にデータを準備するときは Google SpreadSheet などから直接 CSV 引っ張ってくると思う…

SQLite データベースファイルを作る

macOS だと、Windows 7 まで(Window 10 はあんまり知らない)のときみたいに encoding など default 設定弄る必要ないので、いきなり始められるのが嬉しい。
createProverb.ps1

sqlite3 ./myFirst.db "CREATE TABLE Proverb ( id, name, phrase );"
CSV から SQLite データベースファイルを初期化する

こういう query を渡したいけど面倒なので…

INSERT INTO Proverb ( id, name, phrase ) values( 1, '厩戸皇子', '以和爲貴' );
INSERT INTO Proverb ( id, name, phrase ) values( 2, '釈迦', '天上天下唯我独尊' );
INSERT INTO Proverb ( id, name, phrase ) values( 3, '孔子', '有朋自遠方来不亦楽乎' );
INSERT INTO Proverb ( id, name, phrase ) values( 4, '杜甫', '国破山河在' );

CSV から読み出して script で query 文字列生成して SQLite に渡す。

initProverbFromCSV.ps1

Import-CSV ./Proverb.csv | ForEach-Object{
    "INSERT INTO Proverb ( id, name, phrase ) values( $($_.ID), '$($_.name)', '$($_.phrase)' );"
} | sqlite3 ./myFirst.db $_

$($_.ID) と $() で括ってるのは文字列中に埋め込んでるから、正常に parse できるようにするため。

テーブルに正しく反映されたか確認

selectProverb.ps1

sqlite3 ./myFirst.db "SELECT * FROM Proverb;"

結果

PS /Users/hoge/handleProverb> ./selectProverb.ps1
1|厩戸皇子|以和爲貴
2|釈迦|天上天下唯我独尊
3|孔子|有朋自遠方来不亦楽乎
4|杜甫|国破山河在
PS /Users/hoge/handleProverb>
おまけ

敢えて書くほどのことでもないが…

deletProverb.ps1

sqlite3 ./myFirst.db "DELETE FROM Proverb;"

Twitter のアカウントロック喰らったっぽい(続

twitter については、正直、最近ちょっと時間費やしすぎてると思ってた。
定年で実務からリタイアして2年過ぎ、毎日 tweet しているわりには新しいネタも出てこないから、このままフェードアウトしようかと思う。
それにわけのわからないアカウントロックが横行するサービスでは、情報発信以前にライフログとして使えないしなぁ。

そういうわけで、こっちでまた program なネタを書いてゆこう。

Twitter のアカウントロック喰らったっぽい

先ほど突然 twitter (WebもAppも)が「電話番号を確認してください」というページになり、電話で認証コードを受け取って入力しろ、というメッセージが表示された。
難聴なので、SMS で認証コード受け取ってから入力するんだけど、どうもアカウントロックっぽくて同じ「電話番号を確認してください」というページに戻ってしまう。
だけど、どこにもアカウントロックのメッセージはないし、登録したメールアドレスにもなんの通知もない。
知り合いに自分のユーザーネームと twitter アカウント検索してもらったら、どうも出てこないみたいなので、アカウントロックなのだと思うけど、そんなに危ない tweet した覚えないんだがなぁ?

macOS で Powershell - using osascript 編

Windows 版の Powershell では System.Windows.Forms から Userform を簡単に作れるけど、macOS + Powershell.netcore でもせめて Dialog くらい出したいので検討したが、一番簡単で手っ取り早いのは osascript つまり AppleScript から display dialog などを使う方法ではないだろうか。

PS /Users/hoge> 'display dialog "Hello World"' | osascript
button returned:OK
PS /Users/hoge>

display dialog の戻り値は基本的に button returned:(button 名) だけど、cancel したときは以下のように。

PS /Users/hoge> 'display dialog "Hello World"' | osascript                       
0:28: execution error: ユーザによってキャンセルされました。 (-128)
PS /Users/hoge>

なお、title や icon を指定することもできる。icon は 'note'、'caution'、'stop' の三種類が指定できる。

PS /Users/hoge> 'display dialog "Hello World" with title "dialog window"' | osascript
button returned:OK
PS /Users/hoge> display dialog "Note" with title "Using ICON" with icon note' | osascript
button returned:OK
PS /Users/hoge>

また、button は任意のもの(リスト)を設定することもできる。

PS /Users/hoge> 'display dialog "What’s color?" buttons("R","G","B")' | osascript
button returned:G
PS /Users/hoge>

複数 button 選択するには choose も使える。

PS /Users/hoge> 'choose from list {"R","G","B"} default items "R" with title "color select"' | osascript
B
PS /Users/hoge>

display dialog とよく似たものに display alert もあるけど、使い方はほぼ同じなのと、display daialog でだけで間に合うので省略。
試してみたいときは AppleScript の情報をたどって欲しい。

また、通知センターに notification を表示する、という方法もある。

PS /Users/hoge> 'display notification "hello"' | osascript   
PS /Users/hoge>

これだけでは物足りないので、JXA(Javascript Automation:Javascript で記述する osascript)でも書いてみた。

PS /Users/hoge> @"
>> var Notice = Application.currentApplication()
>> Notice.includeStandardAdditions = true
>> Notice.displayNotification('hello world' )
>> "@ | osascript -l JavaScript
PS /Users/hoge>

macOS で Powershell - rehabilitation 編 -3 : profile

Powershell では bash_profile のように、起動時に諸々の設定を施すための設定ファイルが使える。

macOS 版…というか Powershell netcore も Windows 版と同様に profile の設定ができる。

設定ファイルの path は $profile 変数にある

PS /Users/hoge> $profile
/Users/hoge/.config/powershell/Microsoft.PowerShell_profile.ps1
PS /Users/hoge>

実は $profile が格納する設定ファイルパスは、他にもある。
$profile を get-member してみると…

PS /Users/hoge> $profile | gm


   TypeName: System.String

Name                   MemberType            Definition
----                   ----------            ----------
Clone                  Method                System.Object Clone(), System.Object ICloneable....
CompareTo              Method                int CompareTo(System.Object value), int CompareT...
CopyTo                 Method                void CopyTo(int sourceIndex, char[] destination,...
…長いので途中省略…
TrimEnd                Method                string TrimEnd(), string TrimEnd(char trimChar),...
TrimStart              Method                string TrimStart(), string TrimStart(char trimCh...
AllUsersAllHosts       NoteProperty          string AllUsersAllHosts=/usr/local/microsoft/pow...
AllUsersCurrentHost    NoteProperty          string AllUsersCurrentHost=/usr/local/microsoft/...
CurrentUserAllHosts    NoteProperty          string CurrentUserAllHosts=/Users/hoge/.conf...
CurrentUserCurrentHost NoteProperty          string CurrentUserCurrentHost=/Users/hoge/.c...
Chars                  ParameterizedProperty char Chars(int index) {get;}
Length                 Property              int Length {get;}


PS /Users/hoge>

MemberType = NoteProperty が profile 設定ファイルの path を指しており、名前の通りユーザーやホストにより保存場所を切り分けている。
というわけで、MemberType = NoteProperty に絞って format-list してみた。

PS /Users/hoge> $profile | gm | ? MemberType -eq NoteProperty | format-list


TypeName   : System.String
Name       : AllUsersAllHosts
MemberType : NoteProperty
Definition : string AllUsersAllHosts=/usr/local/microsoft/powershell/6/profile.ps1

TypeName   : System.String
Name       : AllUsersCurrentHost
MemberType : NoteProperty
Definition : string AllUsersCurrentHost=/usr/local/microsoft/powershell/6/Microsoft.PowerShell_p
             rofile.ps1

TypeName   : System.String
Name       : CurrentUserAllHosts
MemberType : NoteProperty
Definition : string CurrentUserAllHosts=/Users/hoge/.config/powershell/profile.ps1

TypeName   : System.String
Name       : CurrentUserCurrentHost
MemberType : NoteProperty
Definition : string CurrentUserCurrentHost=/Users/hoge/.config/powershell/Microsoft.PowerShe
             ll_profile.ps1



PS /Users/hoge> $profile.CurrentUserCurrentHost
/Users/hoge/.config/powershell/Microsoft.PowerShell_profile.ps1
PS /Users/hoge> 

なお、profile を一度も設定していなければ $profile が返す path は実在してない。

S /Users/hoge> test-path -path $profile
test-path : Access to the path '/Users/hoge/.config/powershell/Microsoft.PowerShell_profile.ps1' is denied.
At line:1 char:1
+ test-path -path $profile
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : PermissionDenied: (/Users/hoge...ell_profile.ps1:String) [Test-Path], UnauthorizedAccessException
+ FullyQualifiedErrorId : ItemExistsUnauthorizedAccessError,Microsoft.PowerShell.Commands.TestPathCommand
 
False
PS /Users/hoge>