PerlユーザのためのVBA(基本編)

最近、VBAに触れることが多くなってきましたが、あの独特の構文になかなか慣れないので、Perlと対応させてみました。突っ込み歓迎。
いずれこっちにもまとめます。http://bd.nomupro.com

基本仕様

型と変数

Perlには数値型や文字列型のような型はありませんが、スカラ変数、配列変数、ハッシュ変数の様な、変数に格納する値の種類(単一の値、複数の値、キーと値がペアになっている値)によって変数名が異なります。また、変数はmy、our等で宣言し、同時に初期化することが出来ます。

VBAでは、型がたくさんあります。Excelを扱う為に、セルを扱うCells型やSheet型等、特徴的な型があります。
また、変数はDimで変数宣言をします。同時に初期化は出来ません(のはず。。。)。
Perl

# コメントは#(井げた)で書きます
my $scalar = "hoge";my $scalar = 100;my $scalar = 20.0;
my @array = (10, 20, 30);
my %hash = (one => 1, two => 2, three => 3);

VBA

'コメントは'(シングルクォート)で書きます
Dim i As Interger 'Integer型
Dim str As String 'String型
Dim select As Range 'Range型
i = 10
str = "string"
Set select = Cells(1,1) 'オブジェクト型はSetを使う
定数

Perlではconstantプラグマを使います。VBAではDimの代わりにConstを使用し、同時に初期化を行います。
Perl

use constant VAR => "HOGE";
print HOGE, "\n";

VBA

Const STR As String = "HOGEHOGE"
if〜elsif〜else

構造化プログラミングの基本、分岐です。
Perlではなぜかelse ifではなくelsifです。
Perl
標準入力から文字列を読み込み、偶奇を判定するプログラムです。

my $number = <STDIN>;
chomp($number);
if($number !~ /^[0-9]+$/){
    print $number, " is not number\n";
}elsif($number == 0) {
    print $number, " is zero\n";
}elsif($number % 2 == 0){
    print $number, " is even\n";
}else{
    print $number, " is odd\n";
}

VBA
セルから読み取り、偶奇を判定します。

Sub func()
    Dim value As Variant
    value = Cells(1, 1).value
    If Not IsNumeric(value) Then
        Cells(2, 1) = value & " is not number"
    Else
        Dim number As Integer
        number = Cells(1, 1)
        If number = 0 Then
            Cells(2, 1) = number & " is zero"
        ElseIf number Mod 2 = 0 Then
            Cells(2, 1) = number & " is even"
        Else
            Cells(2, 1) = number & " is odd"
        End If
    End If
End Sub
for、foreach、while

次は反復です。
Perl
Perlには色々な反復の書き方がありますね。1..10みたいな書き方、素晴らしいです。
lastでループの中断、nextでループスキップです。

for(my $i = 0; $i < COUNT; $i++) {
    print $i;
}
print "\n";
foreach my $i(0..COUNT - 1) {
    print $i;
}
print "\n";
my $i = 0;
while($i < COUNT) {
    print $i++;
}
print "\n";

VBA
VBAのFor文は、ループの終了合図として、Next <カウンタ変数>という記述をするのが特徴的です。
ちなみに、VBAにはPerlのnextはありません。ループの外側にラベルを貼ってGoToします。

'B列を判定列とし、skipが入っていた場合は次の行へ、
'endが入っていた場合は、ループを終了、
'それ以外はHOGEを入れていく
Sub func2()
    Dim i
    '行の最後までループする
    For i = 1 To Rows.Count
        If Cells(i, 2) = "skip" Then: GoTo CONTINUE
        Cells(i, 1).value = "HOGE"
        If Cells(i, 2) = "end" Then: Exit For
CONTINUE:
    Next i
End Sub

VBAにもWhileがありますが、Do-Loopが汎用的なのでこちらを使う方がいいでしょう。

'10回ループを回し、HOGE + カウンタ値を入力していく関数
Sub func_do()
    Dim counter As Integer
    counter = 1
    Do While counter < 10
        Cells(counter, 1) = "HOGE" & counter
        counter = counter + 1
    Loop
End Sub

コレクションに対しては、For Eachを使うことが出来ます。

'選択範囲に対して、文字列を入力していく関数
Sub selection_input()
    Dim ss As Range
    For Each ss In Selection
        ss.value = "選択範囲です"
    Next
End Sub

関数

関数の基本、引数の渡し方、戻り値です。
Perl
Perlではsubで関数を定義します。戻り値はreturnを付けても付けなくてもどちらでも構いません。また、引数は特殊変数@_に入ります。
関数の呼び出しは 関数名(引数); です。

print plus(10,20), "\n";
sub plus {
    my($val1, $val2) = @_;
    $val1 + $val2;
}

VBA
VBAには関数定義の方法がSubとFunctionの2つあります。引数を返して欲しい場合はFunctionを使用します。Functionで定義した関数は、ユーザ定義関数として、エクセルの表から使うことが出来ます。定義した関数はCallを使って呼び出します。

'main関数から関数を呼び出す
Sub main()
    Call func_Sub(Range("A1").value)
End Sub
Sub func_Sub(str As String)
    Range("A2").value = str
End Sub

以下の様に、他の関数から呼び出すことも可能ですが、セルへ =func_Function(10,20) と入力することで、ユーザ定義関数として呼び出すことも可能です。

'main関数から関数を呼び出す
Sub main()
    Dim i As Integer
    i = func_Function(10, 20)
End Sub
Function func_Function(value1 As Integer, value2 As Integer)
    func_Function = value1 + value2
End Function

配列操作

配列定義、配列アクセス

Perl
Perlは、ご存知の通り、コンテキスト(文脈)によって挙動の変わる言語です。配列の場合、スカラコンテキストとして解釈されると、配列の要素数を返す特徴を持っています。

# 配列を初期化するには空配列()を代入する
my @array = ();
# 配列の要素数は予め決める必要は無い
@array = (10, 20, 30);
# 配列の0番目にアクセスする(出力:10)
print $array[0], "\n";
# 配列の最後の添字を取得する(出力:2)
print $#array, "\n";
# 配列の要素数を取得する(出力:3)
print scalar(@array), "\n";

VBA

Dim strings(10) As String   '10個の配列を宣言
strings(0) = "HOGE"
MsgBox strings(0)           '配列の0番目にアクセスする
MsgBox UBound(strings)      'UBound関数で配列のインデックスの最大値を取得

VBAPerlに比べ、配列の扱いが少し面倒です。
静的配列、動的配列が存在し、静的配列の場合、配列数は一度定義すると変更できません。以下は、静的配列の例と、ファイルから1行ずつ読み込み、動的配列に格納していく例を示します。

Sub func_array()
    Dim strings(10) As String           '10個の配列を宣言
    For i = 0 To UBound(strings)        'UBound関数で配列のインデックスの最大値を取得
        strings(i) = "HOGE" & i + 1
    Next i
    
    For i = 0 To UBound(strings)
        Cells(i + 1, 2) = strings(i)
    Next i
End Sub

Sub func_array2()
    Dim strings() As String             '動的配列を宣言
    Dim count As Integer
    count = 0
    Open "test.txt" For Input As #1     'ファイルから読み込む
    Do Until EOF(1)
        ReDim Preserve strings(count)   'Preserveで配列数のみを変更
                                        '(Preserveでない場合初期化される)
        Line Input #1, strings(count)
        count = count + 1
    Loop
    Close 1
    
    For i = 0 To UBound(strings)
        Cells(i + 1, 1) = i + 1 & ":" & strings(i)
    Next i
End Sub

文字列操作

文字列の結合

Perl
Perlでは文字列結合に「.」を使用します。

print "HOGE"."PGR", "\n";
# 2行に渡る場合改行を入れることが可能
print "HOGE"
      ."PGR", "\n";
# 変数の結合も可能
my($str1, $str2) = ("HOGE", "PGR");
print $str1.$str2, "\n";

VBA
VBAでは文字列の結合に「&」を使用します。

Sub func_string()
    MsgBox "HOGE" & "PGR"
    '2行に渡る場合は「_」を使用し改行する
    MsgBox "HOGE" _
                & "PGR"
                
    Dim str1 As String, str2 As String
    str1 = "HOGE": str2 = "PGR"
    '変数の場合も同じ
    MsgBox str1 & str2
End Sub

ファイル操作

ファイルから1行ずつ読み込み、行数を付与してファイルへ書き出してみます。
Perl
3引数のopenは常識ですよね。第2引数のモード指定で書き込み、読み込みを制御します。

use constant READ_FILEPATH => "test.txt";
use constant WRITE_FILEPATH => "test2.txt";

open(my $rfh, "<", READ_FILEPATH);
open(my $wfh, ">", WRITE_FILEPATH);
my $num = 0;
while(<$rfh>) {
    chomp;
    # 書き込み用にオープンしたファイルハンドルを渡すことで書き込む
    print $wfh ++$num.":".$_, "\n";
}
close($rfh);
close($wfh);

VBA
VBAの場合、ファイルハンドルは数字です。また、モードは、Input(読み込み)、Output(書き込み)、Append(追記)、Random(ランダムアクセス)、Binary(バイナリ)の5種類あります。

Sub func_file()
    Const READ_FILEPATH As String = "test.txt"
    Const WRITE_FILEPATH As String = "test2.txt"
    
    Open READ_FILEPATH For Input As #1      '読み込み用ファイルハンドル
    Open WRITE_FILEPATH For Output As #2    '書き込み用ファイルハンドル
    
    Dim num As Integer
    num = 1
    Do Until EOF(1)
        Dim str As String
        Line Input #1, str
        Print #2, num & ":" & str
        num = num + 1
    Loop
    Close
End Sub