Perl で気象庁の非公式 API を叩いて天気情報を取得する
長いこと無料で天気情報APIを提供してくれていた Livedoor Weather さんがサービスを終えて以来、お手軽に利用できる天気情報APIがなかったのですが、最近こんなツイートを見かけました。
まじかよ気象庁公式の天気予報APIができてるぞ、感激して大声で泣いちゃったhttps://t.co/2HQumqjel8
— Torishima (@izutorishima) 2021年2月24日
気象庁が提供する API ?非公式とはいえ、公式中の公式ともいえる気象庁が!
そして、それを受けて "やじうまの杜" さんが記事にしてくれました。
早速、 Perl で使ってみようというわけです。
#!/usr/bin/env perl use strict; use warnings; use HTTP::Tiny; use JSON qw/decode_json/; my $ht = HTTP::Tiny->new(); my $res = $ht->get("https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json"); my $content = $res->{content}; my $decoded_json = decode_json($content); print $decoded_json->{headlineText}; # 伊豆諸島南部では強風に、伊豆諸島、小笠原諸島では高波に、東京地方では空気の乾燥した状態が続くため、火の取り扱いに注意してください。
おおー、あっさり。
このスクリプトは Perl 入学式の内容に以下を加えた内容です。
- モジュールの使い方
- リファレンスから情報を取り出す方法
Windows のバッチファイルの中に書かれた Perl スクリプトを実行する
さて、Windows 環境で Perl と仲良くする!仲良くなりたい!って昨今です。
まぁ、仲良くする目的は仕事を楽にして twitter 眺める時間を捻出するとか、早く寝るとか、そういう真の目的のためですが・・・
前回は「バッチファイルから Perl を呼び出す」ってのやりました。
そういえば
Perl 入学式でおなじみの Web フレームワークである Mojolicious::Lite ですが、Windows 環境にインストールして利用することも可能です。
もちろん、試験用のサーバ morbo
も Windows で動きます。
そして、この morbo はバッチファイル morbo.bat なのです!
where コマンドで morbo の場所を探すとこんな感じです。
C:\Users\sironekotoro>where morbo C:\Strawberry\perl\bin\morbo C:\Strawberry\perl\bin\morbo.bat
ほんで、この C:\Strawberry\perl\bin\morbo.bat
をメモ帳で開いてみると・・・最初の十数行こそバッチファイルですが、途中から Perl がそのまま書かれてますね!
コードはこちら、クリックで展開
@rem = '--*-Perl-*-- @set "ErrorLevel=" @if "%OS%" == "Windows_NT" @goto WinNT @perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9 @set ErrorLevel=%ErrorLevel% @goto endofperl :WinNT @perl -x -S %0 %* @set ErrorLevel=%ErrorLevel% @if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" @goto endofperl @if %ErrorLevel% == 9009 @echo You do not have Perl in your PATH. @goto endofperl @rem '; #!perl #line 16 use Mojo::Base -strict; use Mojo::Server::Morbo; use Mojo::Util qw(extract_usage getopt); getopt 'b|backend=s' => \$ENV{MOJO_MORBO_BACKEND}, 'h|help' => \my $help, 'l|listen=s' => \my @listen, 'm|mode=s' => \$ENV{MOJO_MODE}, 'v|verbose' => \$ENV{MORBO_VERBOSE}, 'w|watch=s' => \my @watch; die extract_usage if $help || !(my $app = shift); my $morbo = Mojo::Server::Morbo->new; $morbo->daemon->listen(\@listen) if @listen; $morbo->backend->watch(\@watch) if @watch; $morbo->run($app); =encoding utf8 =head1 NAME morbo - Morbo HTTP and WebSocket development server =head1 SYNOPSIS Usage: morbo [OPTIONS] [APPLICATION] morbo ./script/my_app morbo ./myapp.pl morbo -m production -l https://*:443 -l http://[::]:3000 ./myapp.pl morbo -l 'https://*:443?cert=./server.crt&key=./server.key' ./myapp.pl morbo -w /usr/local/lib -w public -w myapp.conf ./myapp.pl Options: -b, --backend <name> Morbo backend to use for reloading, defaults to "Poll" -h, --help Show this message -l, --listen <location> One or more locations you want to listen on, defaults to the value of MOJO_LISTEN or "http://*:3000" -m, --mode <name> Operating mode for your application, defaults to the value of MOJO_MODE/PLACK_ENV or "development" -v, --verbose Print details about what files changed to STDOUT -w, --watch <directory/file> One or more directories and files to watch for changes, defaults to the application script as well as the "lib" and "templates" directories in the current working directory =head1 DESCRIPTION Start L<Mojolicious> and L<Mojolicious::Lite> applications with the L<Mojo::Server::Morbo> web server. =head1 SEE ALSO L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>. =cut __END__ :endofperl @set "ErrorLevel=" & @goto _undefined_label_ 2>NUL || @"%COMSPEC%" /d/c @exit %ErrorLevel%
ほほう!興味深い!!そしてわけわからん!!!
いや、うっすらはわかるけど、気になる、ってことでやっていきますよ。
何をやっていくって? 1 行ごとにコメントつけるんだよ!
頭の良い人はスラスラ推測つけて(しかもその推測あたってる)読み飛ばしていけるんだろうけど、うちはこう、アレがナニでダメな人なので・・・オランダ語の医学書を解読した江戸期の人々みたいにやっていきます。
やっていく
@rem = '--*-Perl-*--
1 行目はバッチファイルのコメント行ですね。
ちなみに、先頭に @
つけるとコマンドプロンプトで結果が非表示になります。すっきり。でもデバッグの時は外すとよいです。
@set "ErrorLevel="
続いて、環境変数を宣言。=
イコール 込みの変数名?ってなんか変ですね
@if "%OS%" == "Windows_NT" @goto WinNT
実行環境の OS の種類を判別して、"%OS%" == "Windows_NT"
であれば goto でラベル WinNT
に飛ばしています。
なお、Windows10 で実行すると Windows_NT
なります。なので、Windows10 環境だとここからラベル WinNT
まで飛びます
C:\Users\sironekotoro>echo "%OS%" "Windows_NT"
うちが動かしているのは Windows10 上なので、以下の 3 行は関係ないのですが念のため・・・
@perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
Perl を -x
と -S
のコマンドラインオプションをつけて、バッチファイルの引数のプレースホルダ 10 個つけて起動してます。
では -x
とは?ここは公式の日本語化をやってる perldoc.jp さんから
-x directory メールのような大きな無関係のASCII テキストのかたまりの中に プログラムが埋め込まれている事を Perl につたえます。 最初の #! で始まり、"perl" という文字列を含む行までの、 先行するゴミは捨てられます。 その行にある意味を持つスイッチは適用されます。 directory が指定されると、Perl はプログラムの実行前に、 そのディレクトリに移ります。 -x スイッチは先行するゴミの処分を制御するだけです。 プログラムの後に無視すべきゴミがある場合には、 END でプログラムを終了する必要があります (その、後に続く ゴミの一部または全部は、必要に応じて DATA ファイルハンドルを通して、 そのプログラムで処理する事ができます)。
へー! Perl って他のテキストファイルとかに埋め込むことできるんですね!知らんかった。というか、この morbo
のバッチが確かにそうだわ。なるほどう!
つまり、ここで #!perl
から __END__
までの Perl スクリプトが動くと。
バッチファイルに書いてある Perl が動く仕組みはこれだったのね。
つぎに -S
とは?
Perl がプログラムを探すときに環境変数 PATH を参照するようにします (プログラム名がディレクトリセパレータを含むときを除きます)。
このあともすごい長い、というか濃い解説が続きますが、Windows の PATH を読んでくれる、ってことね(ざっくり理解)。
@set ErrorLevel=%ErrorLevel%
%ErrorLevel%
は直前のコマンドの実行結果がどうであったか、コマンドの終了コードを取得するもの。それを環境変数 ErrorLevel
(=
なし)に代入している。これは 2 行目で宣言した ErrorLevel=
とは異なる変数。
@goto endofperl
そして、endofperl
のラベルがあるところ(末尾から2行目)まで飛ぶ、と。
まだまだやっていく
:WinNT
WinNT
と判定された 3 行目からこのラベルに移動してくる。
@perl -x -S %0 %*
-x
と -S
は既に調べた。で、引数のところが %0 %*
と短くなってる。既に書いたけど、バッチファイル中の Perl をここで実行する。
@set ErrorLevel=%ErrorLevel%
ここも前述なので飛ばす。と言いつつ書くと、上の perl -x -S ...
の終了コードを代入している。
morbo は ctrl - c
で止めるけど、その終了コードをここで拾っているってことなのかな?
@if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" @goto endofperl
この %COMSPEC%
はコマンドプロンプト の本体、 cmd.exe
の本体パスとのこと。
if NOT
なので、標準のパス以外だったら終了のラベル endofperl
に飛ばしてる。
@if %ErrorLevel% == 9009 @echo You do not have Perl in your PATH.
Perl を実行した結果の終了コードが 9009
の場合には Perl が入っていないと判断する、ってのはわかる。
ググってみたりすると、 9009
ってのはファイルが見つからない時のエラーコードらしい。ほうほう。
@goto endofperl
エラーが出たら、endobperl
ラベルへ〜
@rem ';
コメントで ;
のみだけど、これは区切りって意味かな?
で、ここからは Perl 。引数を受け取って、morbo
を起動してますね。ほんで =encoding utf8
からは POD
で解説と。
#!perl #line 16 use Mojo::Base -strict; use Mojo::Server::Morbo; use Mojo::Util qw(extract_usage getopt); getopt 'b|backend=s' => \$ENV{MOJO_MORBO_BACKEND}, 'h|help' => \my $help, 'l|listen=s' => \my @listen, 'm|mode=s' => \$ENV{MOJO_MODE}, 'v|verbose' => \$ENV{MORBO_VERBOSE}, 'w|watch=s' => \my @watch; die extract_usage if $help || !(my $app = shift); my $morbo = Mojo::Server::Morbo->new; $morbo->daemon->listen(\@listen) if @listen; $morbo->backend->watch(\@watch) if @watch; $morbo->run($app); =encoding utf8 =head1 NAME morbo - Morbo HTTP and WebSocket development server =head1 SYNOPSIS Usage: morbo [OPTIONS] [APPLICATION] morbo ./script/my_app morbo ./myapp.pl morbo -m production -l https://*:443 -l http://[::]:3000 ./myapp.pl morbo -l 'https://*:443?cert=./server.crt&key=./server.key' ./myapp.pl morbo -w /usr/local/lib -w public -w myapp.conf ./myapp.pl Options: -b, --backend <name> Morbo backend to use for reloading, defaults to "Poll" -h, --help Show this message -l, --listen <location> One or more locations you want to listen on, defaults to the value of MOJO_LISTEN or "http://*:3000" -m, --mode <name> Operating mode for your application, defaults to the value of MOJO_MODE/PLACK_ENV or "development" -v, --verbose Print details about what files changed to STDOUT -w, --watch <directory/file> One or more directories and files to watch for changes, defaults to the application script as well as the "lib" and "templates" directories in the current working directory =head1 DESCRIPTION Start L<Mojolicious> and L<Mojolicious::Lite> applications with the L<Mojo::Server::Morbo> web server. =head1 SEE ALSO L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>. =cut __END__
:endofperl
で、ここが終着点のラベル endofperl
と。最終的にはこのラベルにくる、はず?
@set "ErrorLevel=" & @goto _undefined_label_ 2>NUL || @"%COMSPEC%" /d/c @exit %ErrorLevel%
さーて一気にわからなくなった。
ええっと、バッチファイルでの &
、これは commandA & commandB
と書いた場合に、commandA
を実行したあとに commandB
を実行する、というイディオムらしい。
ふむふむ。
この &
の右側は _undefined_label_
これはわからんけど、定義してないラベルに @goto しろってことかな。
で、 ||
はさっきの &
とは異なって、 commandC || commandD
となり、 commandC
が 終了結果が真の場合には commandD
は実行しない、と。
@"%COMSPEC%" /d/c @exit %ErrorLevel%
で、commandC
が偽だった場合にはこれが実行される。
%COMSPEC%
はコマンドプロンプト のパスなので、そこに /d/c
オプションをつけて実行し、@exit
すると。
つまり、 commandX & commandY || commandZ
ってことかな?ええと、これは左?右?どっちから評価されるんだ?
・・・とりあえず、ながかったー!
バッチファイル の中で Perl をうごかす!
長く書いてきたのは全て自分のため・・・ですね!
ってことで、morbo.bat
参考にして作ったバッチファイルがこちらです。
@perl -x -S %0 %* @set ErrorLevel=%ErrorLevel% @if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" @goto endofperl @if %ErrorLevel% == 9009 @echo You do not have Perl in your PATH. @goto endofperl @rem この下からPerl ; #!perl use strict; use warnings; my $str = "hello bat file!"; print "$str\n"; print "@ARGV"; # コマンドライン引数があれば表示される # Perl はこの下の __END__ まで __END__ :endofperl @set "ErrorLevel=" & @goto _undefined_label_ 2>NUL || @"%COMSPEC%" /d/c @exit %ErrorLevel%
参考にしたページ
Perl については文中にページへのリンク書いてきましたが、コマンドプロンプト に関してはこちらのページを多く参考にさせてもらいました。ありがとうございました。
Perl入学式 オンライン 2020 第5回お疲れ様でした
受講された方、サポーターの方、お疲れ様でした。 講師をやったジャージの人です。
講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。
問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitter でハッシュタグ #Perl入学式 をつけて聞いてみてください。
スライド中の練習問題ですが、私の回答例をおいておきます。
Perl入学式 オンライン 2020 第5回
今回は以下の項目について学習しました。
- ハッシュを操作する関数
- サブルーチン
これで、スカラー変数($scalar
)、配列(@array
)、ハッシュ(%hash
)という基本の変数を学習したことになります。
これらの変数に値を格納し、取り出し、削除し、追加する術を学びました。
自分の作りたいプログラム、欲しいものを作るにはこれらだけでは足りないと思うのですが、これらの基本無くしては到達することは難しいでしょう。
何度も立ち返って思い出しつつ学んでみてください。
Perl入学式 オンライン の後に何をすれば良いの?
一昨年以前の集合講義形式で行っていたテキストを参考にしてみてください。
(注意)スライド同期くんの操作は左右で章の切り替え、上下でページの切り替えとなっています
オンライン版では、2019年カリキュラムの第3回までをやったことになります。
この第3回以降に学ぶ「正規表現」「リファレンス」は今後の Perl を学んでいくのに欠かせない内容になります。
「正規表現」によるテキスト処理は Perl が非常に有用な分野です。
「リファレンス」は「スカラー変数の中にハッシュを入れる」など、より複雑なデータ構造を表現するために必須の内容です。
自分の経験からいって、初心者が自習するには難しい内容とは思うのですが(教えていてもそう思う)、挑戦してほしい分野です。
不明点があればコードを添えて discord で質問してください。
お待ちしております。
Perl入学式 オンライン 2021
コロナ禍から始まった Perl 入学式オンラインですが、2021年4月からの春から新年度の開講予定です。
2021年は Perl のメジャーバージョンアップ( 5 → 7 )も控えているイベントイヤーです。
Wandbox ではなく、環境構築を手元のパソコンで行うなど、新たな挑戦を行います。
Perl 入学式は2周、3周の受講を歓迎します。私自身も2周受講しました。
興味ある方の受講をお待ちしています。
1 行増えただけじゃん
長い前置き
平穏な時代
経理のお仕事で、仕訳というのがあります。複式簿記に則ってお金や債権・債務の流れを記録していきます。
いろいろなやり方があると思うのですが、うちの場合、 Google Sheet でマスターデータを書いておいて、それを Google Apps で処理して会計ソフト(弥生会計)取り込み用の csv ファイルにする、という方法をとっています。
マスタデータはこんな感じです。
名前 | 発生 | 金額 | 手数料負担 | 支払手数料 | 摘要 |
---|---|---|---|---|---|
白猫商事 | 2021.12 | 10,000 | 得意先 | 176 | 猫用ホットカーペット |
ひとつの振込先につき支払いはひとつ、という前提があり、こんなフローで仕訳 csv を作成します。
振り込み手数料は 30,000 円未満は 176円、30,000 円以上は275円です。
- マスターデータから 1 行取り込む
- 銀行手数料が得意先負担の場合には、以下の2行を作って仕訳シートに追加する
- 支払額から支払い手数料を算出する
- 支払額から支払手数料を引いた金額を支払額にする
- 支払手数料の行を追加する
- 銀行手数料が当方負担の場合には、金額をそのままに1行作って仕訳シートに追加する
データ構造としてはこんな感じです
[ { 名前 => 白猫商事, 金額 => 10000, 手数料負担 => 得意先, 支払手数料 => 176, ... }, ]
で、仕訳を作るとこんな感じです。
勘定科目 | 金額 | 勘定科目 | 金額 | 摘要 |
---|---|---|---|---|
未払金 | 10,000 | 普通預金 | 9,824 | 白猫商事/2021.12/猫用ホットカーペット |
支払手数料 | 176 | 白猫商事/2021.12/振込手数料 |
相手への支払い義務のあるお金(未払金)を、普通預金から出金して支払うという仕訳です。
ただし、白猫商事は振込手数料を先方が負担するので、手数料分を引いた金額を振込みます。
崩れる前提
ひとつの振込先につき支払いはひとつ、という前提があり、仕訳csvを作る時は
はい。ある日突然、一つの振込先に対する支払いの行を複数にすることになります。
名前 | 発生 | 金額 | 手数料負担 | 支払手数料 | 摘要 |
---|---|---|---|---|---|
黒猫農業 | 2021.12 | 10,000 | 得意先 | 176 | マタタビ |
黒猫農業 | 2021.12 | 30,000 | 得意先 | 176 | 高級マタタビ |
そして、振り込まれる側(この場合は黒猫農業)としては、一括で振り込んで欲しい(一つの支払いごとに支払い手数料を負担したくない)のも当然ですね。
最終的にはこういう仕分けになります。振り込み額が30,000円を超えるので、銀行手数料が変更になります。
勘定科目 | 金額 | 勘定科目 | 金額 | 摘要 |
---|---|---|---|---|
未払金 | 10,000 | 普通預金 | 39,725 | 黒猫農業/2021.12/マタタビ |
未払金 | 30,000 | 黒猫農業/2021.12/高級マタタビ | ||
支払手数料 | 275 | 黒猫農業/2021.12/振込手数料 |
ぎゃー!
こうなってしまうと、マスターデータから1行読み込んで処理するという方法は取れません。
マスターデータ側で合算して 1 行にしてしまうのが一番簡単ですが、会計ソフト上でマタタビ・高級マタタビでそれぞれの費用を分析する場合には不向きになります。
となると、このような感じで処理することになります。
- ハッシュのデータ構造にする。key を名前に、 value には配列を格納するハッシュ(辞書型)のデータ構造を作る
- 同じ key の場合には、value に push する
- 出来上がったデータ構造を key ごとに処理する
となります。この処理を行うためには、こんなデータ構造にする必要があります。
{ 茶虎農業 => [ { 金額 => 10000, 手数料負担 => 得意先, ... }, { 金額 => 30000, 手数料負担 => 得意先, ... }, ], }
ここまでデータ構造の当たりがつけば、要件を満たすことができそうです。
というわけで
この「ぎゃー!」ってなってから実装し直すまで結構時間がかかった(試行錯誤で 1 日半くらい)ので記念に書いてみました。
こうやって書き出して思い出してみると、たいしたことないところで悩んでたなー、とか思うんですが、まぁ、喉元過ぎれば熱さ忘れるってやつですね。
FC2 ブログに記事を投稿する Perl モジュール作った
はい。タイトルのまんまです。
元々は、ちょっと表に出せないような欲望から始まったんですが、その途中でブログに投稿できるモジュールが欲しいなー、ということになり、気づいてみると欲望置き去りにして完成してたのがこのモジュールです。
目的と手段がよくすり変わるという自覚はあります。
手段たーのしー!
今回は目的別にモジュール分けるってのをやってみました。こんなんです。
$ tree lib/WebService/FC2 lib/WebService/FC2 ├── Blog │ ├── Delete.pm │ ├── Edit.pm │ ├── Get.pm │ ├── Media.pm │ └── New.pm └── Blog.pm
問題は全然テスト書いてないことです。
このモジュールは XML-RPC を使って各種の操作を行うのですが、その XML-RPC のモック?を用意しなきゃいけない?の?
ってところで時間切れというか気力がつきました。
Windows のバッチファイルにファイルをドラッグ&ドロップして、Perl でなんか処理する
Windows 使ってます
ということもあり、これらの Windows には Strawberry Perlを入れています。
Windows で簡単にインストールできる Perl としては Active Perl もあるんですが、Strawberry Perl は cpanm が最初からセットアップされていたりで使いやすく、気に入っています。
業務用途だと、プロダクション環境と開発環境で Perl のバージョンを合わせる必要があり Strawberry Perl だと大変なんじゃないかなー、と思うのですが・・・個人で使う分には Strawberry Perl で良いのではないかと思います。
というか、Windows で Perl 動かしてる業務環境はなかなかレアですねー。皆無とは思わないですが、実例知らない・・・
バッチファイル書いていく
Windows でも Perl 使うことができるんですが、Windows では GUI をフルに生かして使いたい!
ってことで、バッチファイルにドラッグ&ドロップしたファイルを Perl に引数として渡すってのを紹介します。
基本的なバッチファイルはこちらで、これに付け足していきます。
メモ帳(notepad.exe)で書いていきます。ちなみに、いまメモ帳のデフォルト文字コードは utf8
なんすよ。
@echo off setlocal cd /d %~dp0 rem %~f1 ドラッグ&ドロップされたファイルのフルパス echo %~f1 pause
この内容で arg.bat
とかいう名前で保存します。
最初の3行はいわばバッチファイルを書くときの「お約束」ですね。3行目はバッチファイル実行時に、このバッチファイルがあるフォルダを実行場所にするためのものです。
@echo off setlocal cd /d %~dp0
その次の rem
から始まるのがコメント行、Perl でいうと #
です。
で %~f1
が引数のフルパスを示すものです。
rem %~f1 ドラッグ&ドロップされたファイルのフルパス echo %~f1
適当なファイルをこのバッチファイルにドラッグ&ドロップするとこうなります。
キーボードの適当なキーを押すと終了します。
バッチファイルから Perl 起動して引数渡す
バッチファイルから起動する Perl のスクリプトはこちらです。Hello! [コマンドライン引数]
と表示するものです。
これもメモ帳で書きます。
#!/usr/bin/env perl use strict; use warnings; print "Hello! @ARGV";
バッチファイルはちょっとだけ手直し。
@echo off setlocal cd /d %~dp0 rem ドラッグ&ドロップされたファイルのフルパスを Perl のコマンドライン引数に渡す perl hello.pl %~f1 pause
この Perl スクリプト とバッチファイルを同じ場所に置いて、適当なファイルをバッチファイルにドラッグ&ドロップ。
無事、Perl のスクリプトにファイルのパスが渡りました。めでたしめでたし!
ドラッグ&ドロップしたテキストファイルの中身を表示すると・・・文字化け!
・・・で、終わればいいんですけどね。
今度は、ドラッグ&ドロップしたテキストファイルの中身を表示してみましょう。
テキストファイルを作ります。中身はこんな感じ。ファイル名は Perl入学式.txt
Perl入学式 2月開催をお楽しみに!
まずはこのファイルをドラッグ&ドロップ。
はい、ちゃんとファイル名が出ましたね。
では、ファイルの中身を表示すべく Perl 側のスクリプトを新たに作ります。ファイル名は open_text.pl
とします。
素直な、ファイル開いて1行1行表示するものです。
#!/usr/bin/env perl use strict; use warnings; open my $FH, '<', $ARGV[0]; for my $line(<$FH>){ chomp $line; print $line . "\n"; } close $FH;
バッチファイルも呼び出す Perl のスクリプト名を変更します。
@echo off setlocal cd /d %~dp0 rem バッチファイルから起動するスクリプトを変更 perl open_text.pl %~f1 pause
はい文字化けー
ということで、Perl を Windows で動かすときの大きな障壁の一つがこの文字化けだと思います。
自分もかつて、ここでつまづきました。
これは、Windows のコマンドプロンプトが cp932
という文字コードであることに対し、Perl で出力する際の標準の文字コードが utf8
であることが原因です。
ですので、これを正しく表示するためには
のいずれかが手取り早いです。
「郷にいれば郷に従え」で、最初の Perl の文字コードを変更する方法が良いのではないかなぁと思います。
Perl のスクリプト側で入出力する文字コードを Windows 環境に合わせる
スクリプト中にコメントで書きましたが、Encode モジュールを使います。
それぞれどの文字コードで読み込むのか、出力するのかを明示したものです。
#!/usr/bin/env perl use strict; use warnings; use Encode; # 文字コードを扱うモジュール open my $FH, '<:utf8', $ARGV[0]; # 入力する文字コードが何なのかを明示する for my $line(<$FH>){ chomp $line; print encode('cp932', $line) . "\n"; # 出力する文字コードは何なのかを明示する } close $FH;
Windows のコマンドプロンプトの文字コードを utf8 にする
コマンドプロンプトで chcp 65001
というコマンドを実行することで、コマンドプロンプトの文字コードを utf8 に変更できます。
この方法をとる場合、変更すべきはバッチファイルの方になります。
@echo off setlocal cd /d %~dp0 rem コマンドプロンプトの文字コードを utf8 に変更 chcp 65001 perl open_text.pl %~f1 pause
こんな感じで
Perl の便利さ + Windows での慣れた GUI 操作 両方のいいとこ取りで、仕事を楽にしていきましょう!
おまけ
7年くらい前、Windows マシンで Perl を勉強していた自分は、この文字コードがどうとかエンコードとか理解できませんでした。
そして Mac を買うという解決策をとったのでした。
Mac はターミナル(Windows でいうところのコマンドプロンプト)の文字コードが utf8 なので文字コードを意識する必要がなかったんですね。
あと、当時はみんな Mac だったなぁという。形から入りました。
・・・とはいえ、ネットから落としてきた何かや、スクレイピングで得た文字列を扱うときには文字コードの問題はついて回るので、結局逃げられなかったという感じです。
追記
バッチファイルから Perl にファイルパスを渡す時なのですが
perl hello.pl %~f1
よりも
perl hello.pl "%~f1"
の方が良いです。
ファイルパス中にスペースがあった場合、スペース以降の文字列を引数として認識してしまいます。
このため、ファイルパスをダブルクォーテーション " "
で囲むことで、ひとまとまりの文字列として扱うことができます。
もちろん、この問題でしっかりハマりました。
Perl つかって 8 年目でディレクトリをコピーをしようとしてハマる
ディレクトリをコピーする機会がなかった
意外なことに、いままでディレクトリのコピーをする用件がなかった。
で、ハマった。
Perl でファイルコピーと言ったら File::Copy という標準モジュール。それで事足りてきた 8 年間。
だもんで、File::Copy でディレクトリもコピーできるだろうと・・・思ったけど、うまくいかない。
「ははーん、これは先に移行先のディレクトリ 作っておけってことね」と思って先行してコピー先のディレクトリを作っておくもダメ。
Mac 上で書いて、Windows 上で動かすコードだったので、文字コードの違いかなー?
と思うも違う。むー
で、File::Copy::Recursive モジュールを使ってあっさり解決した。そうか、File::Copy でディレクトリのコピーはできんかったのか・・・できると思い込んでた。
せっかくなので Windows で日本語ファイル名・フォルダを扱う時の注意
Windowsの文字コードは cp932
ってやつで、ファイル名やパスもこの cp932
です。
そう、ファイル名だけじゃなくて パスも cp932
でエンコードしてあげる必要があります。
フォルダのコピーがうまくいかない時、ちゃんとファイルを認識しているんかな?と思って -e $path
ってファイルテスト演算子使ってみたのですが、みごとに認識しておりませんでした。
-e encode('cp932', $path)
とすることでちゃんとファイルパスを認識してくれました。
コードにするとこんな感じです。utf8
で保存したスクリプトを Windows で実行ってのを想定してます。
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/encode/; use File::Spec; # ダブルクォーテーションだと \ をエスケープ文字と認識してしまい失敗するよ my $path = 'D:\Desktop\新しいフォルダー (2)'; if ( -e $path ) { print "$path is exist\n"; } else { # Windowsで実行すると、ここを通る(文字化けもする) print "$path is not exist\n"; } # utf8のスクリプト上に書かれたパス(9行目)を、パス含めて cp932 でエンコード my $cp932_path = encode( "cp932", $path ); if ( -e $cp932_path ){ # Windowsで実行すると、ここを通る(文字化けしない) print "$cp932_path is exist\n"; } else { print "$cp932_path is not exist\n"; }
追記
さーてバリバリ続き書いてくかー!
って思ったら、ファイルコピー失敗。おいおいなんだよー、ファイルコピーは問題ないはずだろ!・・・と思ったがこういうオチだった。
コピー先の容量不足。
ファイルコピー道は険しい。