Perl入学式 オンライン 2021 第2回お疲れ様でした
Perl入学式 オンライン 2021 第2回
受講された方、サポーターの方、お疲れ様でした。 講師の人です。
今回は条件分岐の if 文に繰り返しの for 文をやりました。そして、それを合わせて利用する fizzbuzz をやりました。
また、複数の値を格納する配列を学びました。
今回のスライドの内容です。
講義中に解いた練習問題の回答例です。
講義スライドや動画を見て、不明点がある場合には Discord で質問してみてください。第 0 回の環境構築や第 1 回、前年度の内容などについてでも大丈夫です。
サポーター陣一同お待ちしております
fizzbuzz を Excel で
今回、というか自分が講義を受け持つときは毎回、fizzbuzz と自分の出会いのエピソードを語っていました。
それは、Excel でやってみようとしてできなかった、というものです。
今回もそれを説明しつつ、そろそろこの過去からの宿題を終わらせるべき・・・と思ったのでした。
Excel で剰余
Excel で剰余を求めるときは =mod(数値, 割る数)
を利用します。
Excel で if 文
if 文もあるのですが、fizz, buzz, fizzbuzz と条件が複数あるので、 ifs を利用します。
=IFS(論理式1, 値が真の場合1, 論理式2, 値が真の場合2 ... )
というものです。
また、いずれの条件にも合致しないときには対象の数字を表示するのですが、それを実現するために =IFERROR(値, エラーの時の値)
で囲みます。
Excel で AND
これはそのまま AND(論理式1, 論理式2)
を使います。
完成
できたのがこの関数です。
=IFERROR( IFS( AND( MOD($A2,3)=0,( MOD($A2,5)=0) ), "fizzbuzz", MOD($A2,3)=0, "fizz", MOD($A2,5)=0, "buzz"), $A2 )
長く未提出だった宿題を終えることができました。
でもまぁ、あの時「え、オレってこの簡単そうに見える fizzbuzz も解けないの!?」という衝撃あればこそ、 Perl をしっかり学ぶことができたので塞翁が馬ってやつですね。
ほんと fizzbuzz に感謝です。
あと、過去できなかったことや分からなかったことをブログに書いておくと、過去からの宿題という形でネタができるので、ブログ書いておくのおすすめです。
オンライン第 2 回
7 月以降を予定しています。次回は以下を予定してます。
- 配列を操作する関数
- ハッシュ
- ハッシュを操作する関数
- サブルーチン
開催日は現在 Discord でアンケート中です。
KOKUYO のワーキングチェアにヘッドレストを追加した
全国で 4 人くらいにしか刺さらない記事
4 人というのは適当ですが、まぁ、そんなに多くはないだろうけど・・・
リモートワークの頻度が増えたので、昨年末より部屋を快適な環境にすべく色々と買いました。
その中でもワーキングチェアの話になります。
CRS-G2800E6 コクヨ/KOKUYO 肘付ハイバックチェア 301175
購入したのはこの KOKUYO 製の中古のワーキングチェアです。
モデレートタイプという、ヘッドレストのないタイプ。
背もたれ倒してフラットにするすことはないよなー(フラグ 1 )、後ろに服をかけるところあると便利かもねー(フラグ 2 )ってことでこの椅子にしました。
あと、当時セール中だったというのも大きい。
https://www.officebusters.com/items/301175?rname=replywww.officebusters.com
商品代金合計:26,600円 送料:3,420円
概ね満足したのですが、ただ一つ・・・背もたれをガッツリと倒したい!(フラグ 1 回収)
KOKUYO さんに聞いてみる
いや、背もたれを倒すことないよなーって思ってたのは本当です。
会社でも倒すことないですし。
なのですが、実際に倒してみると、とても楽なんですよね。
・・・頭の支えがあれば。
ヘッドレストがないので、フルフラットに近い状態にすると頭が浮くわけです。
そこで、メーカーのコクヨにヘッドレストのみを注文可能か聞いてみました。
ベゼルチェアのヘッドレストですが、 ファンクショナルタイプには、後付けで取り付けが可能です。 モデレートタイプにも取り付けは可能ですが、 取り付け高さが低くなり頭の位置が合わないため、 ヘッドレストの取り付けは推奨できません。 (中略) ベゼル用ヘッドレストの品番をご案内します。 品番:CRB-G2800E6
うちが使っているのはモデレートタイプ。推奨されないのか・・・ううーん。
なお、注文は代理店で承るとのこと。
送料無料高級本革ヘッドレスト 6色対応
で、注文すべきかしないべきかを悩んでいたところ、このハンガー部分を活かせないか?と思い至りました(フラグ 2 回収)。
メジャーでサイズを測り、このソファ用のヘッドレストであればうまくハマりそう・・・ということで注文しました。
当時はセール中で 3300 円でした。
これが見事にハマり、まるで正規のオプション品のような使い心地(個人の感想です)
背もたれを倒しても首が支えられて最高!素晴らしい!これで午後ローを横になりながら見ることができる!となったのでした。
これも結構色々検索して迷ったりしたので、ここに書き残しておきます。
Perl を使って画像にマスクをかける
Perl を使って画像にマスクをかける
Perl で画像を扱うライブラリといえば代表的なのが以下の 3 つですが、どれも使ったことないです。
Imager
GD
PerlMagick(ImageMagick をPerlから使うライブラリ)
ということで、今回は Imager でやってみます。
そして、わからないー!ってなっている時に見つけた Perl.org のチュートリアルです。
このチュートリアルなんですが、記事中の画像が連結されてたりするので、こちらでバラしてついでに png にしてます。
画像保存して名前つけてプログラムを試すことができます。
コードはこちらです。
#!/usr/bin/env perl use strict; use warnings; use Imager; my $tile = Imager->new( xsize => 450, ysize => 450, channels => 4, ); my $mask = Imager->new( file => 'mask.png' ); my $craftsman = Imager->new( file => 'craftman.png' ); $tile->compose( src => $craftsman, mask => $mask ); $tile->write( file => 'tile.png' );
で、合成した画像がこちらです。
craftman.png
と mask.png
を合成し、mask.png
の黒枠のところは隠して白い部分だけ craftman.png
の画像を出す、隠した部分は透過させる・・・という処理です。
こういうのをマスクをかける、というそうです。
言葉は知ってましたが、実際にやってみるのは初めてでした。
わからないなりになんとか
画像処理のプログラムですが、業務で利用している PHP の画像合成コードを Perl で書き直してみよう!ってことで着手しました。
PHP の方では GD という画像処理ライブラリを利用しており、それと 1 対 1 で翻訳していくような形で Perl に移植しました。
いったんなんとか動く、ってところまで持っていき Perl っぽく書きなおし、 Imager のメソッドをフル活用し、最終的には Perl のモジュールにすることができました。
いやー、大変だった。
何が大変だったかというと、画像を回転させると画像の縦横が少し増えることを意識できず遠回りしまくったことですね。
完成させられたのは Google 翻訳と運のおかげですわ。
以下のメソッドを使いました。
compose: マスクをかける
rubthrough: 合成(セル画を重ねるようなイメージ?)
scale: サイズ変更、サムネイル作るとか
crop: 画像の一部を抜き出す
rotate: 画像を回転させる。回転させて生じた余白は指定がなければ透過黒
PHP(GD)の画像処理プログラムをPerl(Imager)に移植するってやつで悪戦苦闘してる。大壁にぶつかって進捗全くダメだったけど、なんとか迂回方法見つけて山を超えられたのではないかという実感。寝よう。
— sironekotoro (@sironekotoro) 2021年6月5日
つか、PHPの方はなんであれであの結果が出るのか本当に理解できない。透過PNGとは人生とは
(追記あり)SSD を換装した MacBook Air (13-inch, Mid 2013) で panic(cpu 0 caller 0xffffff8002ac2838)
続きの記事を書きました
panic(cpu 0 caller 0xffffff8002ac2838)
今年(2021 年)の春くらい、5 月頃からこのエラーが出るようになりました。
発生タイミングはスリープ復帰時&おそらく高負荷時。
エラーメッセージは以下。
エラーメッセージ
panic(cpu 0 caller 0xffffff8002ac2838): nvme: "Fatal error occurred. CSTS=0xffffffff US[1]=0x0 US[0]=0x41 VID=0x1987 DID=0x5012
. FW Revision=ECFM22.6\n"@/System/Volumes/Data/SWE/macOS/BuildRoots/2288acc43c/Library/Caches/com.apple.xbs/Sources/IONVMeFamily/IONVMeFamily-557.100.13/Common/IONVMeController.cpp:5499
Backtrace (CPU 0), Frame : Return Address
0xffffffa077583960 : 0xffffff800028e02d
0xffffffa0775839b0 : 0xffffff80003d48e3
0xffffffa0775839f0 : 0xffffff80003c4eda
0xffffffa077583a40 : 0xffffff8000232a2f
0xffffffa077583a60 : 0xffffff800028d84d
0xffffffa077583b80 : 0xffffff800028db43
0xffffffa077583bf0 : 0xffffff8000a9d68a
0xffffffa077583c60 : 0xffffff8002ac2838
0xffffffa077583c80 : 0xffffff8002aa7433
0xffffffa077583de0 : 0xffffff80009f54f5
0xffffffa077583e50 : 0xffffff80009f53f6
0xffffffa077583e80 : 0xffffff80002d44b5
0xffffffa077583ef0 : 0xffffff80002d5424
0xffffffa077583fa0 : 0xffffff800023213e
Kernel Extensions in backtrace:
com.apple.iokit.IONVMeFamily(2.1)[2A44DC48-B629-386C-985E-3BE03CCA48F2]@0xffffff8002aa0000->0xffffff8002ac9fff
dependency: com.apple.driver.AppleEFINVRAM(2.1)[C6EE02AA-79D2-3EF8-83A6-9E52549E16D9]@0xffffff80016fe000->0xffffff8001707fff
dependency: com.apple.driver.AppleMobileFileIntegrity(1.0.5)[2D13AEBE-3C77-3EE0-BC06-BDED7FE19FDE]@0xffffff80018b8000->0xffffff80018cdfff
dependency: com.apple.iokit.IOPCIFamily(2.9)[29933CED-5D05-36A4-BFA1-6F4B4F349283]@0xffffff8002d5f000->0xffffff8002d87fff
dependency: com.apple.iokit.IOReportFamily(47)[3F7604AB-EA65-3904-A1F4-AFEB25D288A7]@0xffffff8002d96000->0xffffff8002d98fff
dependency: com.apple.iokit.IOStorageFamily(2.1)[58EA4506-4E6B-3AC3-A70D-ED35EE2C381D]@0xffffff8002e62000->0xffffff8002e73fff
Process name corresponding to current thread: kernel_task
Boot args: -x
Mac OS version:
20E232
Kernel version:
Darwin Kernel Version 20.4.0: Fri Mar 5 01:14:14 PST 2021; root:xnu-7195.101.1~3/RELEASE_X86_64
Kernel UUID: BB2FFEBA-7D53-301F-A238-9867CA51276F
KernelCache slide: 0x0000000000000000
KernelCache base: 0xffffff8000200000
Kernel slide: 0x0000000000010000
Kernel text base: 0xffffff8000210000
__HIB text base: 0xffffff8000100000
System model name: MacBookAir6,2 (Mac-7DF21CB3ED6977E5)
System shutdown begun: NO
Panic diags file available: YES (0x0)
Hibernation exit count: 0
System uptime in nanoseconds: 565017200055
Last Sleep: absolute base_tsc base_nano
Uptime : 0x000000838da6550d
Sleep : 0x0000000000000000 0x0000000000000000 0x0000000000000000
Wake : 0x0000000000000000 0x00000009032d2f54 0x0000000000000000
last started kext at 379393286647: @filesystems.afpfs 11.3 (addr 0xffffff7f9a183000, size 327680)
last stopped kext at 302135866140: >!ASMBusPCI 1.0.14d1 (addr 0xffffff7f99490000, size 4096)
loaded kexts:
@filesystems.afpfs 11.3
@nke.asp_tcp 8.2
@filesystems.autofs 3.0
>AGPM 122
>X86PlatformShim 1.0.0
>!AGraphicsDevicePolicy 6.2.9
@Dont_Steal_Mac_OS_X 7.0.0
>!ABacklight 180.3
>!A!IFramebufferAzul 16.0.2
>!ALPC 3.1
>!AMCCSControl 1.14
>!UTopCaseDriver 4040.11
|IO!BUSBDFU 8.0.4d18
|SCSITaskUserClient 436.100.4
>!UCardReader 511.101.1
>!AFileSystemDriver 3.0.1
@filesystems.tmpfs 1
@filesystems.hfs.kext 556.100.11
@BootCache 40
@!AFSCompression.!AFSCompressionTypeZlib 1.0.0
@!AFSCompression.!AFSCompressionTypeDataless 1.0.0d1
>!ATopCaseHIDEventDriver 4040.11
@filesystems.apfs 1677.100.114
>AirPort.BrcmNIC 1400.1.1
@private.KextAudit 1.0
>!ASmartBatteryManager 161.0.0
>!ARTC 2.0
>!AACPIButtons 6.1
>!AHPET 1.8
>!ASMBIOS 2.1
>!AACPIEC 6.1
>!AAPIC 1.7
@!ASystemPolicy 2.0.0
@nke.applicationfirewall 311
|IOKitRegistryCompatibility 1
|EndpointSecurity 1
$SecureRemotePassword 1.0
@kext.triggers 1.0
>!AGraphicsControl 6.2.9
@!AGPUWrangler 6.2.9
>!ABacklightExpert 1.1.0
|IONDRVSupport 585.1
|IOAccelerator!F2 442.9
@!AGraphicsDeviceControl 6.2.9
>X86PlatformPlugin 1.0.0
>IOPlatformPlugin!F 6.0.0d8
|IOGraphics!F 585.1
>!ASMBus!C 1.0.18d1
>!AActuatorDriver 4440.3
>usb.IOUSBHostHIDDevice 1.2
|Broadcom!BHost!CUSBTransport 8.0.4d18
|IO!BHost!CUSBTransport 8.0.4d18
|IO!BHost!CTransport 8.0.4d18
>usb.!UHub 1.2
>usb.cdc 5.0.0
>usb.networking 5.0.0
>usb.!UHostCompositeDevice 1.2
>!AThunderboltDPInAdapter 8.1.4
>!AThunderboltDPAdapter!F 8.1.4
>!AThunderboltPCIDownAdapter 4.1.1
>!ABSDKextStarter 3
|IOSurface 290.7
@filesystems.hfs.encodings.kext 1
>!AMultitouchDriver 4440.3
>!AInputDeviceSupport 4400.35
>!AHS!BDriver 4040.11
>IO!BHIDDriver 8.0.4d18
>!AHIDKeyboard 224
>!AHSSPIHIDDriver 61
>!AXsanScheme 3
|IONVMe!F 2.1.0
>!AThunderboltNHI 7.2.8
|IOThunderbolt!F 9.3.2
|IO80211!F 1200.12.2b1
|IOSkywalk!F 1
>mDNSOffloadUserClient 1.0.1b8
>corecapture 1.0.4
>usb.!UHostPacketFilter 1.0
|IOUSB!F 900.4.2
>!AHSSPISupport 61
>!A!ILpssSpi!C 3.0.60
>!A!ILpssI2C 3.0.60
>!A!ILpssDmac 3.0.60
>usb.!UXHCIPCI 1.2
>usb.!UXHCI 1.2
>!A!ILpssGspi 3.0.60
>!AEFINVRAM 2.1
>!AEFIRuntime 2.1
|IOSMBus!F 1.1
|IOHID!F 2.0.0
$!AImage4 3.0.0
|IOTimeSync!F 980.4
|IONetworking!F 3.4
>DiskImages 493.0.0
|IO!B!F 8.0.4d18
|IOReport!F 47
|IO!BPacketLogger 8.0.4d18
$quarantine 4
$sandbox 300.0
@kext.!AMatch 1.0.0d1
|CoreAnalytics!F 1
>!ASSE 1.0
>!AKeyStore 2
>!UTDM 511.101.1
|IOUSBMass!SDriver 184.101.1
|IOSCSIBlockCommandsDevice 436.100.4
|IO!S!F 2.1
|IOSCSIArchitectureModel!F 436.100.4
>!AMobileFileIntegrity 1.0.5
@kext.CoreTrust 1
>!AFDEKeyStore 28.30
>!AEffaceable!S 1.0
>!ACredentialManager 1.0
>KernelRelayHost 1
|IOUSBHost!F 1.2
>!UHostMergeProperties 1.2
>usb.!UCommon 1.0
>!ABusPower!C 1.0
>!ASEPManager 1.0.1
>IOSlaveProcessor 1
>!AACPIPlatform 6.1
>!ASMC 3.1.9
|IOPCI!F 2.9
|IOACPI!F 1.4
>watchdog 1
@kec.pthread 1
@kec.corecrypto 11.1
@kec.Libm 1
エラーの原因
不明です。
ただまぁ、予想はついててエラーメッセージの
panic(cpu 0 caller 0xffffff8002ac2838): nvme: "Fatal error occurred. CSTS=0xffffffff US[1]=0x0 US[0]=0x41 VID=0x1987 DID=0x5012 . FW Revision=ECFM22.6\n"@/System/Volumes/Data/SWE/macOS/BuildRoots/2288acc43c/Library/Caches/com.apple.xbs/Sources/IONVMeFamily/IONVMeFamily-557.100.13/Common/IONVMeController.cpp:5499
ここにある IONVMeFamily
ですね。IO は Input/Output 、NVMe は SSD の規格名です。
ここであれかなーって思うわけです。
タイトルにある通り、この MacBook Air は容量を増やすために SSD を換装しています。
Docker 使っている分にはこまめにイメージを消せば良いのですが、さすがに Parallels で Windows を使うようになってからは容量が厳しくなってきました。
で、快適に 1 年くらい使ってたところでこのエラーな訳です。
エラーメッセージの一部でググると、やっぱこのエラーに遭遇している人が多少いるようです。
様子見
MacOS の電源設定を行う pmset
コマンドやシステム環境設定で色々やりました。
他には
shift + option + command + R
からの OS 再インストール(ただしデータは残ってた)command + option + P + R
による PRAM リセットcommand + D
によるハードウェア診断(異常なしとの診断結果)
とりあえず落ち着いたみたいなのですが、色々試しすぎたため、どの設定変更が功を奏したかわからなくなってしまいました。
なので現在の設定を置いておきます。
$ pmset -g System-wide power settings: Currently in use: standbydelaylow 10800 standby 0 womp 0 halfdim 1 hibernatefile /var/vm/sleepimage powernap 0 gpuswitch 2 networkoversleep 0 disksleep 0 standbydelayhigh 0 sleep 0 (sleep prevented by mds, mds_stores, UserEventAgent, UserEventAgent, UserEventAgent, runningboardd, runningboardd, AddressBookSourceSync) autopoweroffdelay 259200 hibernatemode 3 autopoweroff 1 ttyskeepawake 1 displaysleep 180 highstandbythreshold 50 acwake 0 lidwake 1
$ uptime 11:45 up 2 days, 21:35, 2 users, load averages: 2.22 2.58 2.47
まだ 1TB くらい空きがあるのが笑えるわー。貧乏性だ。
うめき
私物 MacBook Air がこの状態に / Mac の起動プロセスが完了しない場合 - Apple サポート https://t.co/cRYlhwtd4S
— sironekotoro (@sironekotoro) 2021年6月2日
昨日から昼にかけてもうダメやろ、これはもう MacBook 買い替えするしかない、秘蔵の積み立てを下ろす時がきたんや・・・ってなってたけど、なんとか復活したような。
— sironekotoro (@sironekotoro) 2021年6月2日
前は10分と持たず再起動してたのが、3時間は持ってる。でもまた再発するかもなのでバックアップ中 pic.twitter.com/pAtkVBm19H
追記(2021/06/07 02:11:45)
症状の再発を確認。
エラーメッセージは同一。
SSDを元に戻して( 2TB -> 256GB )清貧な暮らしをするか、買い替えするかだなぁ。
追記(2021/06/21)
諦めて元の SSD に戻して利用中です。もちろん症状は出ていません。
次の macOS が出て、余力があればもう一度試してみたいですね。
追記(2022/02/14)
Sublime Text 4 で環境変数を読み込む
Sublime Text 4 にあげました
もうアップデートすることもないかな・・・って思っていた Sublime Text ですが、起動したらアップデートを促されました。
アップデートしたところ、メジャーバージョンが上がって 4 になりました。
幸い、いまのところ(3時間くらい)困ったところは出てないです。
Sublime Text 3 の頃に設定した Perl の開発環境もそのまま動いています。
エラーメッセージの出方がちょっと違うかなーとか、それくらい。
Sublime Text 4 との出会い
2013 年に受講した Perl 入学式でおすすめされたことがきっかけです。
初心者に環境を与えると、それを親と思い込む性質がある・・・かどうかはともかく、うちは Sublime Text をメインに、 VS Code をサブに、サーバー上では vim って感じで使い分けています。
Sulime Text 4 で環境変数が読み取れない?
多分、Sublime Text 4 に限らないことだと思います。
~/.zshrc
に export GOOGLE_CLIENT_ID=hogehoge
こんな感じで環境変数を設定しました。
$ source ~/.zshrc
で設定を反映させて、ターミナルから確認します。
$ echo $GOOGLE_CLIENT_ID hogehoge
よしよし。
で、sublime text 上の Perl で確認します。
Sublime Text には Build System というものがあり、これは VS Code でいうところの Code Runner みたいなやつです。
書いたコードをすぐに実行して、エディタの別領域に結果を出せるという。
こんな感じ。
#!/usr/bin/env perl use strict; use warnings; print $ENV{GOOGLE_CLIENT_ID}
・・・エラー!?
いやいやいやいや、ターミナルでは表示できてまんがな。
Sulime Text 4 で環境変数が読み取る
で、結論から言うと、この時の Sublime Text 4 は Mac の Dock から起動したものでした。
これをターミナルから起動してあげると・・・あ、うちは .zshrc
にこういうエイリアス書いてます。
alias subl='/Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl'
$ subl ~/Desktop/test.pl
はい、このようにターミナルからの起動であれば環境変数を読み込むことができました。
Sublime Text の Build System は既存のシェルとは独立したもののようです。
というか、前も plenv で選択した Perl のバージョンが利用されないって色々やったなそういえば(今になって思い出した)
めでたしめでたし。
Perl で Google Sheet API を使(って|わなくても) csv でダウンロードする
休日の方が仕事がらみのプログラムが捗る
ってことないですかね?
うちはそうです。
つまり逆は・・・この話やめますか
こんな業務
以下のフローで処理している業務があります。
他部署から限定公開の Google Sheet の URL がこちらに伝えられてくる
Google Sheet を csv ファイルでダウンロードする
- ファイル -> ダウンロード -> カンマ区切りの値
ダウンロードした csv ファイルを特定のファイル名にリネームする
別なプログラムを起動して csv ファイルを読み込ませる
生成された諸々のファイルをサーバーにあげてなんたらかんたら
めんどさの水位を超えた
まず、この業務の時にいちいち Google Sheet みなきゃいけないのがめんどいです。
ダウンロードするためだけにブラウザで開くのめんどい。
コマンド一発でファイルをダウンロードできたらいいのに・・・という思いを持ってきたのですが、書きだした通りたいした手間でもない(15秒もかからない)のでずっと「めんどい」と思いながら放置してきたわけです。
しかし、めんどい値が水位を超えて溢れる日がやってきました。先週です。
というわけで、いつものように Perl で片付けます。
Perl で Google Sheet API を使って csv でダウンロードする
まず、限定公開の Google Sheet なので、プログラムからアクセスするには OAuth による認可とったうえで API でアクセスします。
当初は Google Drive API で持ってきたら楽なのでは?と考えたのですが、シート単位でのダウンロードはできない模様。
できるという情報ある方、お待ちしてます・・・
ほとんど過去のコードのコピペです。
Google Sheet API で Google Sheet のデータを取得して、csv に書き出すってやつです。
#!/usr/bin/env perl use strict; use warnings; use Encode; use HTTP::Tiny; use JSON; use Text::CSV_XS; use URI; my $ACCESS_TOKEN = access_token( CLIENT_ID => '', CLIENT_SECRET => '', REFRESH_TOKEN => '', ); my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN ); my $rows = values_get( bearer => $bearer, SPREADSHEET_ID => '', GOOGLE_SHEET_API => 'https://sheets.googleapis.com/v4/spreadsheets/', SHEET_NAME => 'シート1', ); my $csv = Text::CSV_XS->new( { eol => "\012"} ); open my $FH, '>:utf8', './test.csv'; while ( my $row = shift @${rows} ) { $csv->print( $FH, $row ); } close $FH; sub values_get { my %hash = @_; my $ht = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } ); my $SHEET_URI = $hash{GOOGLE_SHEET_API} . $hash{SPREADSHEET_ID} . '/values/' . "$hash{SHEET_NAME}!A1:BV"; my $URI = URI->new($SHEET_URI); my $res = $ht->get($URI); my $data = decode_json( $res->{content} ); return $data->{values}; } sub access_token { my %hash = @_; my $URI = URI->new('https://oauth2.googleapis.com/token'); my $ht = HTTP::Tiny->new(); my $response = $ht->request( 'POST', $URI, { content => encode_json( { client_id => $hash{CLIENT_ID}, client_secret => $hash{CLIENT_SECRET}, grant_type => 'refresh_token', refresh_token => $hash{REFRESH_TOKEN}, } ) } ); my $json = decode_json( $response->{content} ); return $json->{access_token}; }
しかし、出来上がった csv ファイルは Google Sheet の API からダウンロードした csv ファイルと内容が異なっています。
どうやら、こんな感じ
Web からダウンロード: セルに日本語が入っており一定の長さ以上だったり
,
カンマやスペースが入っている時は"
ダブルクオーテーションで囲んでいる(?)、そうでない時は"
ダブルクオーテーションで囲んでいない今回作ったのでダウンロード:機械的にフィールドの値を
"
ダブルクオーテーションで囲んでいる
うーん。
いや、csv ファイルがちょっと異なっていようが、今回 Perl でおとしてきた csv ファイルが、手順上の「その上で別なプログラムを起動して csv ファイルを読み込ませる」でも読めればいいわけです。
・・・だめでした。無念。
このまま、Google Sheet 上での csv ダウンロードの仕様に沿った csv つくる・・・?
俺が、俺こそが Google になって Google Sheet を csv にパースする?
それってもっとすんごく面倒そうだなー、となります。
Perl で Google Sheet API を使わなくても csv でダウンロードする
色々とぐぐっている中で、こういう記事がありました。
あら、curl で csv にしてダウンロードできるの・・・?
この記事では公開されている Google Sheet ですが、非公開でも OAuth で通ったやつをつけてあげれば良いのでは?と考えました。
#!/usr/bin/env perl use strict; use warnings; use HTTP::Tiny; use URI; use Encode; use JSON; my $ACCESS_TOKEN = access_token( CLIENT_ID => '', CLIENT_SECRET => '', REFRESH_TOKEN => '', ); my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN ); my $http = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } ); my $url = 'https://docs.google.com/spreadsheets/d/xxxxxx/export?gid=0123456789&format=csv'; my $file = './test.csv'; my %options = (); my $response = $http->mirror($url, $file, \%options); if ( $response->{success} ) { print "$file is up to date\n"; } sub access_token { my %hash = @_; my $URI = URI->new('https://oauth2.googleapis.com/token'); my $ht = HTTP::Tiny->new(); my $response = $ht->request( 'POST', $URI, { content => encode_json( { client_id => $hash{CLIENT_ID}, client_secret => $hash{CLIENT_SECRET}, grant_type => 'refresh_token', refresh_token => $hash{REFRESH_TOKEN}, } ) } ); my $json = decode_json( $response->{content} ); return $json->{access_token}; }
あっさりダウンロードできました。csv ファイルも Google シートからダウンロードしたものと diff をとっても違いなし。
これでまた一つ、仕事が楽になりました。
Perl で Google Sheet API をつかってセルの値を削除する
あれー?
Google Sheet API を使って Google Sheet に金融機関コードや支店ごとの情報を反映させる、ってなコードを書いてました。
以下が完成するとこうなるって画像です。
テストのシートへの反映を確認し、いざ本番のシートに反映!
・・・で、やってみたところ確かに反映されるのですが、一部の値が重複していることがわかりました。
確認したところ、こういうことでした。
支店の統廃合の結果、全金融機関の支店数は日々減少している
更新に使ったGoogle Sheet API の
values:batchUpdate
は、引数で渡された範囲のセルの値を更新する対象範囲外のセルは残る
つまり、反映前に 1000 件のリストがあり、更新時のリストが 800 件しかなければ、更新されるのは 800 件のみとなります。
そして残りの 200 件はそのまま残るわけですね。
なるほど。
シートの値だけを消す
ということで、情報を反映させる前に一旦シートの中身を綺麗にすることで対応しました。
今回は値のみを削除し、書式は残します。
これの Perl で書いてみた版です。
あと自分のブログのこの辺り参考 にしました。半年も前だと完全に忘れてますね。
#!/usr/bin/env perl use strict; use warnings; use HTTP::Tiny; use JSON; use URI; use utf8; my $ACCESS_TOKEN = access_token( CLIENT_ID =>'', CLIENT_SECRET => '', REFRESH_TOKEN => '', ); my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN ); sheet_clear( bearer => $bearer, SHEET_ID => '123456789', # 更新したいシートのID(gid= の後の数字) SPREADSHEET_ID => '', # スプレッドシートのID GOOGLE_SHEET_API => 'https://sheets.googleapis.com/v4/spreadsheets/', ); sub sheet_clear { my %hash = @_; my $ht = HTTP::Tiny->new( default_headers => { Authorization => $hash{bearer}, } ); my $SHEET_URI = $hash{GOOGLE_SHEET_API} . $hash{SPREADSHEET_ID} . ':batchUpdate'; my $URI = URI->new($SHEET_URI); my $response = $ht->request( 'POST', $URI, { content => encode_json( { requests => [ { updateCells => { range => { sheetId => $hash{SHEET_ID} }, fields => "userEnteredValue", } } ] } ) } ); } sub access_token { my %hash = @_; my $URI = URI->new('https://oauth2.googleapis.com/token'); my $ht = HTTP::Tiny->new(); my $response = $ht->request( 'POST', $URI, { content => encode_json( { client_id => $hash{CLIENT_ID}, client_secret => $hash{CLIENT_SECRET}, grant_type => 'refresh_token', refresh_token => $hash{REFRESH_TOKEN}, } ) } ); my $json = decode_json( $response->{content} ); return $json->{access_token}; }
困って解決したらブログに残しておこう
困った時にすぐググる人っていますよね?
自分のことです。
自分が解決したり、困ったり、一度やったことを書いておくと、ググる様が検索して見つけてきてくれます。
ありがたい。
今回も過去の自分の記事に救われました。書いててよかったわー
この記事もきっと未来の自分と、もしかしたら Google 経由で来た誰かの助けになるでしょう。