<Lua> Luaにおける条件分岐(if文,関係演算子,論理演算子)とコマンド行引数の扱い

Last modified: 2019-07-21

ここではLuaにおける条件分岐(if文,関係演算子,論理演算子)とコマンド行引数についてを扱う。

解説

条件分岐とそのパターン

プログラムを書く際には変数の値などの特定の条件によって分岐を行って別々の処理を行いたい場面があるが、そこで用いるのがif文となる。

以下、分岐の幾つかのパターンを扱うが、条件部分の記述については後述する。また、条件を満たした場合・満たさない場合の処理の記述は見やすさのために改行や字下げをして記述しているが、これは文法上は必須ではなく、1行におさめる書き方もできる。

(1行で記述する例)
$ lua -e "if true then print('true') end"
true

条件を満たした場合にのみ処理を行う

if文の最も単純な形は下のようになり、条件を満たした場合にのみ特定の(thenendの間に記述した)処理を行うようにできる。

if [条件] then
  [条件を満たした場合の処理]
end

条件を満たさなかった場合はthenendの間の処理を飛ばしてendの後ろの処理に進む。

条件によって別々の処理を行う

条件を満たした場合と満たさなかった場合とで別々の処理を行いたい場合は下のように記述する。

if [条件] then
  [条件を満たす場合の処理]
else
  [条件を満たさない場合の処理]
end

条件を細かく場合分けする

上の書き方では1つの条件評価についてしか分岐できないが、最初のifで条件を満たさなかった場合に続けて(最初のifの分岐に対応する別の)条件の評価を行っていくことができる。

if [条件1] then
  [条件1を満たした場合の処理]
elseif [条件2] then
  [(条件1を満たさず)条件2を満たした場合の処理]
elseif [条件3] then
  [(条件2までを満たさず)条件3を満たした場合の処理]
elseif [条件4] then
  [(条件3までを満たさず)条件4を満たした場合の処理]

...

else
  [いずれの条件も満たさない場合の処理]
end

この形の分岐で使われるelseifの部分は言語によって記述が異なり、Luaの場合はelse ifでもelifでもなくelseifとなる。else ifとスペースを入れる形になるのはelseの中の最初にif文が入る形のみで、上の分岐の構造とは異なる。

$ lua -e "if false then print('true') else if true then print('false, true') else print('false, false') end end"
false, true

if文の条件部分の記述

上で “[条件]” として記述してきた条件部分は、何かの条件を “満たす” か “満たさないか” の2通りの状態のいずれかを入れて評価する。

真偽値型の値を用いる

Luaのデータ型には真偽値型という “2通りの状態のいずれかを示す” ための型があり、true(真)とfalse(偽)のいずれかをとる。このいずれかを値として新しい変数に入れると、その変数は真偽値型になる。

既に本記事内のコマンド実行での例で記述しているように、この値それ自体をif文の条件部分に入れて評価することができる(trueのときに条件を満たし、falseのときには満たさない)。

$ lua -e "e = true; if e then print('true') else print('false') end"
true
$ lua -e "e = false; if e then print('true') else print('false') end"
false

真偽値型ではない型は、その内容に関わらず “条件を満たす” と扱われる。

(空文字列の場合)
$ lua -e "e = ''; if e then print('true') else print('false') end"
true
(0の場合)
$ lua -e "e = 0; if e then print('true') else print('false') end"
true

特殊な値nilや未使用の変数名をif文の条件部分に入れると、 “条件を満たさない” と扱われる。

(nilの場合)
$ lua -e "e = nil; if e then print('true') else print('false') end"
false
(未使用の変数名の場合)
$ lua -e "e = 1; if f then print('true') else print('false') end"
false

関係演算子を用いた比較

2つの値(変数や定数)どうしを “関係演算子” という種類の演算子のいずれかで比較することでその比較結果を真偽値として得ることができる。

(1と1の比較)
$ lua -e "print(1 == 1)"
true
(1と2の比較)
$ lua -e "print(1 == 2)"
false

以下はLuaにおける関係演算子の一覧となるが、左右が異なる場合にtrueとなる~=演算子は、他の言語では!=となっていることが多く、注意が必要。

演算子 比較結果が真(true)になる条件
== 左右の型と値が等しい
~= 左右の型もしくは値が異なる
< 左の値が右の値より小さい
> 左の値が右の値より大きい
<= 左の値が右の値より小さいか同じ
>= 左の値が右の値より大きいか同じ

if文を実際に用いる際にはこの関係演算子による比較が行われることが多い。下の例は動作確認用で実用的ではなく、実際には比較する片方もしくは両方に変数が用いられる。

$ lua -e "if 4 > 1 then print('4は1より大きい') else print('4は1より小さい') end"
4は1より大きい
$ lua -e "if 7 % 2 == 0 then print('7は2で割り切れる') else print('7は2で割り切れない') end"
7は2で割り切れない

複数の条件(全ての条件・いずれかの条件・まとめ)

複数の条件を同時に満たすかどうかで分岐したい場合は

if [条件1] and [条件2] then
  [条件を同時に満たした場合の処理]
end

として複数の条件を論理演算子のandで結ぶ。上の “[条件2]” の後ろにandを付けて3つ以上の条件を同時に満たすかどうかで分岐するようにもできる。他の言語ではand&&と書くこともあるが、Luaではandとしてしか書けない。

複数の条件の内、いずれか1つの条件を満たすかどうかで分岐したい場合は

if [条件1] or [条件2] then
  [条件のいずれかを満たした場合の処理]
end

として複数の条件を論理演算子のorで結ぶ。これも “[条件2]” の後ろにorを付けて条件を3つ以上(の中のいずれか)にすることもできる。他の言語ではor||と書くこともあるが、Luaではorとしてしか書けない。

条件は計算と同様に丸括弧でまとめることができ、人間にとって読みやすいものにもなる。論理演算子には優先順位(orよりandが高く、後述のnotは更に高い)があるが、意図した条件が分かりやすいように記述するために括弧は積極的に使ったほうがよい。

条件評価の必要がない場合

andorによる複数の条件の評価1では、全ての条件が必ずしも評価されるわけではなく、結果が確定した段階で評価を終える2

  • [条件1] and [条件2]で条件1がfalsenilになった場合は(この段階で両方を満たすことはありえなくなって)条件2を評価しなくても結果が条件1の内容(falsenil)であることが確実になるため、条件2の評価は行わない
  • [条件1] or [条件2]で条件1がtrueになった場合は(この段階で片方が満たされたために)条件2を評価しなくても結果がtrueであることが確実になるため、条件2の評価は行わない
(1つ目の条件で結果が決まらない場合/2つ目の条件が評価されprint()が呼ばれる)
$ lua -e "print(true and print('abc'))"
abc
nil
$ lua -e "print(false or print('abc'))"
abc
nil

(1つ目の条件で結果が決まる場合/2つ目の条件が評価されずprint()が呼ばれない)
$ lua -e "print(false and print('abc'))"
false
$ lua -e "print(nil and print('abc'))"
nil
$ lua -e "print(true or print('abc'))"
true

条件を逆転する

論理演算子のnotを用いると条件を逆にすることができる。

(trueの逆を評価する)
$ lua -e "if not true then print('true') else print('false') end"
false

(falseの逆を評価する)
$ lua -e "if not false then print('true') else print('false') end"
true

スクリプト実行時のコマンド行引数

Luaスクリプトへのコマンド行引数は “おみくじ/複数の文字列の内の1つを無作為に選択する” で扱ったテーブル型のargという名前の変数で得られ

  • スクリプト実行時の引数の数は#arg
  • スクリプトのパス名はarg[0]
  • それぞれの引数(文字列型)はarg[1],arg[2],…

として取り出せる。例えば3つの引数を付けて

$ lua [スクリプトの場所] aaa bbb ccc

として実行した場合は

  • #arg3
  • arg[1]aaa
  • arg[2]bbb
  • arg[3]ccc

となる。

スクリプトファイルを用いずにシェルを用いて動作を確認すると

$ echo "print(('#arg:%d arg[0]:%s arg[1]:%s arg[2]:%s arg[3]:%s'):format(#arg, arg[0], arg[1], arg[2], arg[3]))" | lua - aaa bbb ccc
#arg:3 arg[0]:- arg[1]:aaa arg[2]:bbb arg[3]:ccc

のようになる3

引数を数値として扱う(文字列型から数値型への変換)

引数に数値を指定するプログラムでは、arg[1]などのように取り出した個別の引数は文字列型となっているため、これを数値として扱いたい場合はtostring()関数にその文字列を入れて数値としての値を戻り値として取り出して用いる。

ただし数値として有効でない文字列(例:abc)を入れた場合にはこの関数は特殊な値nilを返すので、コマンド行引数のように実行時に自由に内容が決められるものは数値にする際にnilかどうかのチェックをするのがよい。

n = tonumber(arg[1])
if not n then
  io.stderr:write('ERROR MESSAGE\n')
  os.exit(false)  -- 失敗の終了ステータスで終了
end

上の例では数値でない場合にos.exit()関数を用いてプログラムを異常終了(処理に失敗して終了)したものとして(OSへ伝えて)終了している。

コード例

下のコードはif文とコマンド行引数の処理の例となる。

[任意]ファイル名:006_if_and_args.lua エンコーディング:UTF-8

#! /usr/bin/lua
-- -*- coding: utf-8 -*-

--[[--  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --
動作確認バージョン: 5.3, JIT2.1
--  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --]]

-----
----- 条件分岐(if文,関係演算子,論理演算子)とコマンド行引数
-----

do
    -- 引数はテーブルargから取り出せる
    -- 追加の引数があるかどうかは#argの値で確認可
    -- arg[0]でスクリプトのパス名が文字列として取り出せる

    -- 「if [条件] then [条件を満たした場合の処理...] end」形式で
    -- 特定の条件を満たした場合のみ処理を行うことができる

    -- このプログラムは2つのコマンド行引数を必須とする
    -- 「[値1] ~= [値2]」は2つの値もしくは型が異なる場合に真となる
    if #arg ~= 2 then
        print(('使い方: %s [数字1] [数字2]'):format(arg[0]))

        -- 引数に指定した値(真偽値可・引数省略時true)を戻り値として実行を終了
        os.exit(false)  -- 失敗の終了ステータスで終了
    end

    -- それぞれの引数は文字列型になっているので
    -- 数値として扱うためにtonumber()関数に入れる
    local a = tonumber(arg[1])
    local b = tonumber(arg[2])
    -- tonumber()に数値文字列以外を入れると値「nil」が入るので
    -- 数値かどうかのチェックができる
    -- 値が「nil」である条件は「not [変数]」となる
    -- ここではaかbのいずれかがnilのときに条件が真となるように
    -- 「not a or not b」とした
    if not a or not b then
        io.stderr:write('数値ではない入力があります\n')
        os.exit(false)
    end

    -- 1つ目の値が整数かどうかの確認
    -- math.floor()は引数の値の小数点以下を切り捨てた値を得る
    if math.floor(a) ~= a then
        print(('1つ目の値 (%s) は整数ではありません'):format(a))
    -- 「else」の後ろ(「end」の前まで)は
    -- 上のifの条件に当てはまらなかった場合に実行される
    -- この場合はaが整数の場合
    else
        -- 偶数か奇数かのチェック
        -- 2で割って余りが0なら偶数
        if a % 2 == 0 then
            print(('1つ目の値 (%s) は偶数です'):format(a))
        else
            print(('1つ目の値 (%s) は奇数です'):format(a))
        end
    end

    local c = a + b
    -- 2つの値の合計の値の範囲による分岐を行う
    if c > 100 then
        print(('2つの値の合計 (%s) は100より大きいです'):format(c))
    -- 「elseif」は対応する最初のifの条件に該当しなかった場合に条件が評価され
    -- 条件を満たせば下の処理が実行される
    elseif c > 0 then
        print(('2つの値の合計 (%s) は0より大きく100以下です'):format(c))
    elseif c > -100 then
        print(('2つの値の合計 (%s) は-100より大きく0以下です'):format(c))
    -- 最初の「if」とこれに対応する後ろの全ての「elseif」で条件を満たさない場合に
    -- 「else」と「end」の間の処理が実行される
    else
        print(('2つの値の合計 (%s) は-100以下です'):format(c))
    end

    -- 複数の条件が全て真のときにのみ条件を満たすようにするには
    -- 複数の条件を「and」でつなげる
    if a > 100 and b > 100 and c > 1000 then
        print(('1つ目の値 (%s) と2つ目の値 (%s) はともに100より大きく、かつ合計 (%s) は1,000より大きいです'):format(a, b, c))
    end
    -- 条件は丸括弧でまとめて組み合わせることもできる
    if (a == 100 or b == 100) and c == 150 then
        print(('1つ目の値 (%s) と2つ目の値 (%s) のいずれかが100で、かつ合計 (%s) が150です'):format(a, b, c))
    end

    -- 関係演算子「==」による比較では両方が同じ型でないと偽となる
    -- aはtonumber()で数値型にしているので
    -- ここで文字列「100」と一致することはない
    if a == '100' then
        print(('1つ目の値 (%s) は"100"です'):format(a))
    else
        print(('1つ目の値 (%s) は"100"ではありません'):format(a))
    end
end

-- 終了

  • 使用したバージョン
    • Lua 5.3.3
    • LuaJIT 2.1.0 Beta 3

  1. 条件を満たすかどうかをチェックする処理 [return]
  2. “短絡評価” という方式で、他の色々な言語でも用いられており、2番目の条件が関数の戻り値を利用する形の場合は1番目の条件によってその関数が実行されるかどうかが変わる [return]
  3. -は標準入力を入力/スクリプトのファイルとして指定している [return]