sironekotoroの日記

Perl で楽をしたい

Perl で気象庁の非公式 API を叩いて天気情報を取得する

長いこと無料で天気情報APIを提供してくれていた Livedoor Weather さんがサービスを終えて以来、お手軽に利用できる天気情報APIがなかったのですが、最近こんなツイートを見かけました。

気象庁が提供する API ?非公式とはいえ、公式中の公式ともいえる気象庁が!

そして、それを受けて "やじうまの杜" さんが記事にしてくれました。

forest.watch.impress.co.jp

早速、 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 入学式の内容に以下を加えた内容です。

  • モジュールの使い方
  • リファレンスから情報を取り出す方法

エンドポイントが https なので、環境構築としては、SSL のライブラリとモジュールが必要です。

Windows のバッチファイルの中に書かれた Perl スクリプトを実行する

さて、Windows 環境で Perl と仲良くする!仲良くなりたい!って昨今です。

まぁ、仲良くする目的は仕事を楽にして twitter 眺める時間を捻出するとか、早く寝るとか、そういう真の目的のためですが・・・

前回は「バッチファイルから Perl を呼び出す」ってのやりました。

sironekotoro.hateblo.jp

そういえば

Perl 入学式でおなじみの Web フレームワークである Mojolicious::Lite ですが、Windows 環境にインストールして利用することも可能です。

もちろん、試験用のサーバ morboWindows で動きます。

そして、この 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 さんから

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 については文中にページへのリンク書いてきましたが、コマンドプロンプト に関してはこちらのページを多く参考にさせてもらいました。ありがとうございました。

www.pg-fl.jp

Perl入学式 オンライン 2020 第5回お疲れ様でした

受講された方、サポーターの方、お疲れ様でした。 講師をやったジャージの人です。

講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。

appslideshare.tugougaii.site

問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。

スライド中の練習問題ですが、私の回答例をおいておきます。

github.com

Perl入学式 オンライン 2020 第5回

今回は以下の項目について学習しました。

  • ハッシュを操作する関数
  • サブルーチン

これで、スカラー変数($scalar)、配列(@array)、ハッシュ(%hash)という基本の変数を学習したことになります。

これらの変数に値を格納し、取り出し、削除し、追加する術を学びました。

自分の作りたいプログラム、欲しいものを作るにはこれらだけでは足りないと思うのですが、これらの基本無くしては到達することは難しいでしょう。

何度も立ち返って思い出しつつ学んでみてください。

Perl入学式 オンライン の後に何をすれば良いの?

一昨年以前の集合講義形式で行っていたテキストを参考にしてみてください。

(注意)スライド同期くんの操作は左右で章の切り替え、上下でページの切り替えとなっています

appslideshare.tugougaii.site

オンライン版では、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. マスターデータから 1 行取り込む
  2. 銀行手数料が得意先負担の場合には、以下の2行を作って仕訳シートに追加する
    1. 支払額から支払い手数料を算出する
    2. 支払額から支払手数料を引いた金額を支払額にする
    3. 支払手数料の行を追加する
  3. 銀行手数料が当方負担の場合には、金額をそのままに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 行にしてしまうのが一番簡単ですが、会計ソフト上でマタタビ・高級マタタビでそれぞれの費用を分析する場合には不向きになります。

となると、このような感じで処理することになります。

  1. ハッシュのデータ構造にする。key を名前に、 value には配列を格納するハッシュ(辞書型)のデータ構造を作る
  2. 同じ key の場合には、value に push する
  3. 出来上がったデータ構造を key ごとに処理する
    1. value の要素が 1 つだけの場合(白猫商事、黒猫物産の場合)は従来通り
    2. value の要素が複数の場合
      1. 支払額を合算
      2. 支払手数料も合算した額で算出
      3. 合算した支払額を1行目に集約
      4. 2行目以降は未払金のみ記載、支払額欄は空欄
      5. 最後の行に支払手数料を入れる

となります。この処理を行うためには、こんなデータ構造にする必要があります。

{
    茶虎農業 => [ 
        { 金額 => 10000, 手数料負担 => 得意先, ... }, 
        { 金額 => 30000, 手数料負担 => 得意先, ...  },
     ],
}

ここまでデータ構造の当たりがつけば、要件を満たすことができそうです。

というわけで

この「ぎゃー!」ってなってから実装し直すまで結構時間がかかった(試行錯誤で 1 日半くらい)ので記念に書いてみました。

こうやって書き出して思い出してみると、たいしたことないところで悩んでたなー、とか思うんですが、まぁ、喉元過ぎれば熱さ忘れるってやつですね。

FC2 ブログに記事を投稿する Perl モジュール作った

はい。タイトルのまんまです。

github.com

元々は、ちょっと表に出せないような欲望から始まったんですが、その途中でブログに投稿できるモジュールが欲しいなー、ということになり、気づいてみると欲望置き去りにして完成してたのがこのモジュールです。

目的と手段がよくすり変わるという自覚はあります。

手段たーのしー!

今回は目的別にモジュール分けるってのをやってみました。こんなんです。

$ 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ストロベリー パールを入れています。

strawberryperl.com

Windows で簡単にインストールできる Perl としては Active Perl もあるんですが、Strawberry Perl は cpanm が最初からセットアップされていたりで使いやすく、気に入っています。

業務用途だと、プロダクション環境と開発環境で Perl のバージョンを合わせる必要があり Strawberry Perl だと大変なんじゃないかなー、と思うのですが・・・個人で使う分には Strawberry Perl で良いのではないかと思います。

というか、WindowsPerl 動かしてる業務環境はなかなかレアですねー。皆無とは思わないですが、実例知らない・・・

バッチファイル書いていく

Windows でも Perl 使うことができるんですが、Windows では GUI をフルに生かして使いたい!

ってことで、バッチファイルにドラッグ&ドロップしたファイルを Perl に引数として渡すってのを紹介します。

基本的なバッチファイルはこちらで、これに付け足していきます。

メモ帳(notepad.exe)で書いていきます。ちなみに、いまメモ帳のデフォルト文字コードutf8 なんすよ。

f:id:sironekotoro:20210131162043p:plain

@echo off
setlocal
cd /d %~dp0

rem %~f1 ドラッグ&ドロップされたファイルのフルパス
echo %~f1

pause

この内容で arg.bat とかいう名前で保存します。

f:id:sironekotoro:20210131132039p:plain

最初の3行はいわばバッチファイルを書くときの「お約束」ですね。3行目はバッチファイル実行時に、このバッチファイルがあるフォルダを実行場所にするためのものです。

@echo off
setlocal
cd /d %~dp0

その次の rem から始まるのがコメント行、Perl でいうと # です。

%~f1 が引数のフルパスを示すものです。

rem %~f1 ドラッグ&ドロップされたファイルのフルパス
echo %~f1

適当なファイルをこのバッチファイルにドラッグ&ドロップするとこうなります。

f:id:sironekotoro:20210131132400p:plain

キーボードの適当なキーを押すと終了します。

バッチファイルから 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 スクリプト とバッチファイルを同じ場所に置いて、適当なファイルをバッチファイルにドラッグ&ドロップ。

f:id:sironekotoro:20210131141930p:plain

無事、Perlスクリプトにファイルのパスが渡りました。めでたしめでたし!

ドラッグ&ドロップしたテキストファイルの中身を表示すると・・・文字化け!

・・・で、終わればいいんですけどね。

今度は、ドラッグ&ドロップしたテキストファイルの中身を表示してみましょう。

テキストファイルを作ります。中身はこんな感じ。ファイル名は Perl入学式.txt

Perl入学式 2月開催をお楽しみに!

f:id:sironekotoro:20210131152252p:plain

まずはこのファイルをドラッグ&ドロップ。

はい、ちゃんとファイル名が出ましたね。

f:id:sironekotoro:20210131151454p:plain

では、ファイルの中身を表示すべく 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

f:id:sironekotoro:20210131152634p:plain

はい文字化けー

ということで、PerlWindows で動かすときの大きな障壁の一つがこの文字化けだと思います。

自分もかつて、ここでつまづきました。

これは、Windowsコマンドプロンプトcp932 という文字コードであることに対し、Perl で出力する際の標準の文字コードutf8 であることが原因です。

f:id:sironekotoro:20210131153529p:plain

ですので、これを正しく表示するためには

のいずれかが手取り早いです。

「郷にいれば郷に従え」で、最初の 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

f:id:sironekotoro:20210131155825p:plain

こんな感じで

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 でディレクトリのコピーはできんかったのか・・・できると思い込んでた。

www.nqou.net

perldoc.jp

せっかくなので 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";
}

追記

さーてバリバリ続き書いてくかー!

って思ったら、ファイルコピー失敗。おいおいなんだよー、ファイルコピーは問題ないはずだろ!・・・と思ったがこういうオチだった。

f:id:sironekotoro:20210127192645p:plain

コピー先の容量不足。

ファイルコピー道は険しい。