Python: Larkを使って構文解析器をつくってみた(その2)

こないだつくった構文解析器をちょっと改良、というか修正して、入力データをコマンド形式にしてみた。

コマンド形式、っていうのは、入力データ(の内部表現)を作るための一種の DSL だ。代入も制御構造もないシェルスクリプトみたいなものと考えてくれればいい。コマンド形式にしておけば、あとから機能追加によって入力データの項目が増えたとしても対応するコマンドを追加するだけ済み、パーサ(構文解析器)を修正する必要がない。

入力データの見た目はほとんど変わらなくて、* がなくなっただけ。これは、こないだのはデータの種類を表すラベルだったのに対して、今回のはコマンド名を表すからそれらしくした。

// Example for input data.
MODEL
  RO
GAMMA0.5  // %
  0.150
HMAX
  0.200
PLOT      // %
  0.0001
  0.0002
  0.0005
  0.001
  0.002
  0.005
  0.01
  0.02
  0.05
  0.1
  0.2
  0.5
  1.0
  2.0
  5.0
 10.0
END

各コマンドは、コマンド名(行の先頭から始まる必要がある)と 0 個以上の引数からなり、改行で終わる。空白文字で始まる行は前の行の続きとみなすのと// から行末までをコメントとするのもこないだと同じ。

文法定義ファイルはこうなった。

?script : statement+

statement : command arg* "\n"

command : /[A-Z0-9.]+/

arg : string
    | real

string : UCASE_LETTER+

real : FLOAT

%import common.UCASE_LETTER
%import common.FLOAT
%import common.WS_INLINE
%ignore WS_INLINE

コメントや継続行の処理も文法でできるようだけど、今回はパースする前の前処理でやってしまうことにした。そのほうが文法が楽だったから。

さて、Lark には lark.Interpreter というクラスが用意されている。このクラスの visit() メッソッドを使うと、パースした木構造を他のデータ構造に変換する代わりに、ノードをたどりながら処理を実行できる。今回はコマンド名と引数リストを出力してるだけだけど、これでデータを構築する処理を書けばいいわけだ。

from lark import Lark
from lark.visitors import Interpreter
import sys
import re
from pprint import pprint


class MyInterpreter(Interpreter):
    def script(self, tree):
        print("SCRIPT")
        for c in tree.children:
            self.visit(c)

    def statement(self, tree):
        cmd = self.visit(tree.children[0])
        args = [ self.visit(a) for a in tree.children[1:] ]
        print("  STATEMENT")
        print("    COMMAND: " + cmd)
        print("    ARGS: " + str(args))

    def command(self, tree):
        return tree.children[0]

    def arg(self, tree):
        return self.visit(tree.children[0])

    def string(self, tree):
        return "".join(tree.children)

    def real(self, tree):
        return float(tree.children[0].value)


def contract(text):
    lines = text.splitlines(keepends=False)
    lines_new = []
    for line in lines:
        line = re.sub(r"//.*$", "", line)
        if len(line) == 0:
            pass
        elif line.startswith(" "):
            lines_new[-1] += line
        else:
            lines_new.append(line)
    return "\n".join(lines_new) + "\n"


with open("larksample2.lark", "r", encoding="utf-8") as grammar:
    parser = Lark(grammar.read(), start="script")

with open(sys.argv[1], "r") as f:
    input_data = contract(f.read())

tree = parser.parse(input_data)
MyInterpreter().visit(tree)

実行するとこうなった。

[email protected]:larksample$ python larksample2.py larksample2.dat
 SCRIPT
   STATEMENT
     COMMAND: MODEL
     ARGS: ['RO']
   STATEMENT
     COMMAND: GAMMA0.5
     ARGS: [0.15]
   STATEMENT
     COMMAND: HMAX
     ARGS: [0.2]
   STATEMENT
     COMMAND: PLOT
     ARGS: [0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0]
   STATEMENT
     COMMAND: END
     ARGS: []
カテゴリー: Python | コメントする

Python: Larkを使って構文解析器をつくってみた

構文解析器を「つくってみた」というと、四則演算や簡単なプログラミング言語をつくってみたというのをよく見かけるけど、個人的には何かのプログラムの入力データをパース(構文解析)することが多い。

四則演算ができたところで結局は「つくってみた」以上のものではないし、プログラミング言語を(本格的に)つくってみようという人もどちらかといえば稀だろう。

一方でプログラム(それが何であれ)というのはたいてい入力が必要なものだし、ファイルから入力を読み込むことも少なくない。ただデータが並んでいればいい、というのでなければ何らかのフォーマットを決める必要がある。JSON とか YAML とかいった汎用のフォーマットもあるけど、必ずしも目的に対して適当とは言えないこともある。

というわけで構文解析器(パーサ)の出番になるわけだ。

以前には Haskel のパーサコンビネータ(Parsec)や Ruby のパーサジェネレータ(Racc)を使ったことがあるけど、Python でははじめて。かるく調べてみたところ、タイトルに書いた Lark というライブラリが良さげだったので試してみることにした。

今回はずいぶん前に Haskell でつくったあるプログラムの入力ファイルをサンプルとしてとりあげる。↓こんなデータ。

// Example for input data.
*MODEL
  RO
*GAMMA0.5  // %
  0.150
*HMAX
  0.200
*PLOT      // %
  0.0001
  0.0002
  0.0005
  0.001
  0.002
  0.005
  0.01
  0.02
  0.05
  0.1
  0.2
  0.5
  1.0
  2.0
  5.0
 10.0
*END

* で始まる文字列がデータの種類を表すラベルで、その後に1つ以上のデータが空白で区切られて続く。改行は意味を持たない(空白とみなす)。ただし // から行末まではコメント。

さて、Lark ではまず解析対象の文法を定義したファイルを用意する。文法は EBNF ベースの構文で定義する。今回の例ではこうなった。

?input : model gamma hmax plot end

model : "*MODEL" modelname

modelname : UCASE_LETTER+

gamma : "*GAMMA0.5" real

hmax : "*HMAX" real

plot : "*PLOT" real+

end : "*END"

real : FLOAT

%import common.UCASE_LETTER
%import common.FLOAT
%import common.WS
%ignore WS
%import common.CPP_COMMENT
%ignore CPP_COMMENT

で、これを Lark に渡せばパーサを返してくれる。プログラムはこう。

from lark import Lark
import sys


with open("larksample.lark", "r", encoding="utf-8") as grammar:
    parser = Lark(grammar.read(), start="input")

with open(sys.argv[1], "r") as f:
    input_data = f.read()

tree = parser.parse(input_data)
print(tree)

たったこれだけで構文解析をして構文木を返してくれる。実行してみると:

[email protected]:larksample$ python larksample.py larksample.dat
 Tree('input', [Tree('model', [Tree('modelname', [Token('UCASE_LETTER', 'R'), Token('UCASE_LETTER', 'O')])]), Tree('gamma', [Tree('real', [Token('FLOAT', '0.150')])]), Tree('hmax', [Tree('real', [Token('FLOAT', '0.200')])]), Tree('plot', [Tree('real', [Token('FLOAT', '0.0001')]), Tree('real', [Token('FLOAT', '0.0002')]), Tree('real', [Token('FLOAT', '0.0005')]), Tree('real', [Token('FLOAT', '0.001')]), Tree('real', [Token('FLOAT', '0.002')]), Tree('real', [Token('FLOAT', '0.005')]), Tree('real', [Token('FLOAT', '0.01')]), Tree('real', [Token('FLOAT', '0.02')]), Tree('real', [Token('FLOAT', '0.05')]), Tree('real', [Token('FLOAT', '0.1')]), Tree('real', [Token('FLOAT', '0.2')]), Tree('real', [Token('FLOAT', '0.5')]), Tree('real', [Token('FLOAT', '1.0')]), Tree('real', [Token('FLOAT', '2.0')]), Tree('real', [Token('FLOAT', '5.0')]), Tree('real', [Token('FLOAT', '10.0')])]), Tree('end', [])])

パースした結果が木構造になってるのがなんとなくわかる。とはいえさすがにこれは見づらいので、Tree クラスに用意されてる pretty() メソッドを使ってみよう。print(tree) を print(tree.pretty()) に変えてやればいい。

[email protected]:larksample$ python larksample.py larksample.dat
 input
   model
     modelname
       R
       O
   gamma
     real    0.150
   hmax
     real    0.200
   plot
     real    0.0001
     real    0.0002
     real    0.0005
     real    0.001
     real    0.002
     real    0.005
     real    0.01
     real    0.02
     real    0.05
     real    0.1
     real    0.2
     real    0.5
     real    1.0
     real    2.0
     real    5.0
     real    10.0
   end

見やすくなったね。

この木構造をプログラムで利用しやすい形に変換するには lark.Transformer クラスを継承したクラスをつくってやる。プログラムはこうなった。

from lark import Lark
from lark import Transformer
import sys
from pprint import pprint


class MyTransformer(Transformer):
    def input(self, tokens):
        (model, gamma, hmax, plot, _) = tokens
        return {
            "model" : model,
            "gamma0.5" : gamma,
            "hmax" : hmax,
            "plot" : plot,
        }

    def model(self, tokens):
        (m,) = tokens
        return m

    def modelname(self, tokens):
        return "".join(tokens)

    def gamma(self, tokens):
        (g,) = tokens
        return g

    def hmax(self, tokens):
        (h,) = tokens
        return h

    def plot(self, tokens):
        return list(tokens)

    def end(self, tokens):
        return None

    def real(self, tokens):
        (r,) = tokens
        return float(r)


with open("larksample.lark", "r", encoding="utf-8") as grammar:
    parser = Lark(grammar.read(), start="input")

with open(sys.argv[1], "r") as f:
    input_data = f.read()

tree = parser.parse(input_data)
result = MyTransformer().transform(tree)
pprint(result)

lark.Transformer を継承した MyTransformer クラスの transform() メッソッドにパースした結果を渡してやると、変換した結果が返ってくる。今回は普通の辞書にした。

MyTransformer クラスに定義しているメソッドは、それぞれ文法ファイルで定義した構文要素に対応していて、transform() は構文木をたどりながらノード(つまり構文要素)に対応するメソッドを呼び出す。各メソッドの引数 tokens は子ノードのリスト。だからこれらのメソッドから適切な値を返せば、最終的に目的の構造を持ったデータ(今回は辞書)が手に入る。

これを実行するとこんなふうに出力される。

[email protected]:larksample$ python larksample.py larksample.dat
 {'gamma0.5': 0.15,
  'hmax': 0.2,
  'model': 'RO',
  'plot': [0.0001,
           0.0002,
           0.0005,
           0.001,
           0.002,
           0.005,
           0.01,
           0.02,
           0.05,
           0.1,
           0.2,
           0.5,
           1.0,
           2.0,
           5.0,
           10.0]}

無事、目的のものが手に入った。

[追記]

インストール:

[email protected]:larksample$ pip install lark-parser

参考ページ:

その他:

  • PyPI には lark てのと lark-parser てのがあるけどどう違うの?
  • lark.Transformer.transform() から呼び出される各メソッドは、子要素(を評価した値)のリスト tokens を引数にとり、自身を評価した値を返す。
  • 子要素とはつまり、文法ファイルで定義した : の右側の要素のこと。
  • ではあるんだけど、文法ファイルで文字列を直接使う(たとえば "*MODEL" のように)と、メソッドの引数リストに入らない。これ、気づくまでにちょっとかかった。
  • よく使いそうな文法要素(FLOAT とか UCASE_LETTER とか)は予め定義されてて、インポートして使うことができる。ドキュメントには全部は書いてないみたいなので GitHub のリポジトリでソースファイルを見るといい。

カテゴリー: Python | コメントする

Dockerコンテナ上のMariaDBにホスト側から接続する

MariaDB には mysql コマンドで接続で来るはずだけど、Docker コンテナに乗ってるときには追加のオプションが必要だったので、メモしておく。

docker-compose.yml はこんなの。

version: "3"

services:

  db:
    image: mariadb:10.5.6-focal
    container_name: test-db
    restart: always
    ports:
      - 8802:3306
    volumes:
      - ./mysql:/var/lib/mysql
      - ./initdb.d:/docker-entrypoint-initdb.d
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=test
      - MYSQL_USER=test
      - MYSQL_PASSWORD=test
      - BIND-ADDRESS="0.0.0.0"
    tty: true

コンテナを起動する。

[email protected]:test$ docker-compose up -d

で、ポート 8802 に繋いであるんだからそれを指定してやればいいんだろうと、次のようにやったらダメだった。ポートを指定してるのにソケットがないと怒られる。

[email protected]:test$ mysql -u root -p -P 8802
Enter password: 
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

解決策は簡単に見つかった。次のようにすればいい。

[email protected]:test$ mysql -u root -p -h localhost -P 8802 --protocol tcp

--protocol オプションで tcp を使うってことを明示する。-h オプションはなくても大丈夫だった。多分ほかの PC から接続するときには必要なんだろう。

カテゴリー: misc | コメントする

Ubuntu Serverの一般ユーザでdockerを実行できるようにする

先日インストールした Ubuntu Server だけど、インストール時に Docker も一緒にインストールしたのに、一般ユーザでは使えない。なら docker グループにユーザを追加すればいいんだろう、と思って次のようにしたら docker なんてグループはないと怒られた。

[email protected]:~$ sudo usermod -a -G docker takatoh

グループ作ってくれてないのか……

で、ググったら↓のページを見つけた。

docker グループがなければ作ってやればいいらしい。

[email protected]:~$ sudo addgroup --system docker
Adding group `docker' (GID 117) ...
Done.
[email protected]:~$ sudo adduser takatoh docker
Adding user `takatoh' to group `docker' ...
Done.

これで一般ユーザでも docker コマンドと docker-compose コマンドが使えるようになった。

カテゴリー: Ubuntu | コメントする

モニタなしでUbuntu Serverを起動する

新しい PC に Ubuntu Server をインストールしてから、もう3週間以上も経ってしまった。モニタなしで起動させるのにだいぶ苦労したけど、なんとか解決した。

どういうことかというと、インストールが問題なく終わって別の PC から SSH で接続できるのを確認したあと、もう必要ないと思ってモニタとキーボードを外して電源を入れても OS が起動しないのだ。

モニタを繋いでいれば1分もかからないくらいでちゃんと起動するのに、モニタなしだとダメ(キーボードはなくても大丈夫だった)。たっぷり5分は待ってみても SSH で接続できないし、ping も返ってこない。この状態でモニタを接続すると、起動プロセスのログ(っていうのか?)が流れて、やがてログイン待ち画面になる。どうもモニタが繋がってないせいで起動プロセスの何処かで止まってるみたいに見える。

ググってみると、GRUB っていうブートローダの設定を変えたらモニタなしでも起動できた、というブログ記事をいくつか見つけた。ただ、Ubuntu のバージョンが古かったり Raspberry Pi での話だったりして、Ubuntu Server 20.04 でも同じなのかわからなかった。とはいえ、ほかに手がかりもないので試してみた。でもダメ。そうこうしてるあいだに(週末しか作業ができなかったとはいえ)3週間以上も時間が過ぎてしまった。

で、今日見つけた情報でやっと解決したわけだ。それがこれ、Ubuntu 日本語フォーラムのページ。

このページを参考にして GRUB の設定を変更したらうまく行った。

GRUB の設定は /etc/default/grub ファイルを編集して行う。結果として次のようにした。

# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
#   info -f grub -n 'Simple configuration'

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="nomodeset"

# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"

# Uncomment to disable graphical terminal (grub-pc only)
#GRUB_TERMINAL=console

# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480

# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true

# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"

# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"

もとのファイルと diff を取るとこう(.orig がついてるのがもとのファイルのコピー)。

[email protected]:~$ diff /etc/default/grub.orig /etc/default/grub
11c11
< GRUB_CMDLINE_LINUX=""
---
> GRUB_CMDLINE_LINUX="nomodeset"

/etc/default/grub ファイルを修正したら、これを反映するために sudo update-grub コマンドを実行する。

[email protected]:~$ sudo update-grub

で、再起動する。

これでモニタなしでも起動するようになった。

いやー、時間がかかった。というか、モニタなしで使うことも多いであろう Server 版なのになんでモニタなしで起動できない設定になってるの?今まで使ってた Desktop 版(16.04 や 18.04)はモニタなしでもそのままでちゃんと起動してるのに。

カテゴリー: Ubuntu | コメントする

Ubuntu Server 20.04.1 LTSのインストール

ローカルネットワークのサーバ OS 一新計画、その第一弾。新しく購入した PC に Ubuntu Server 20.04.1 LTS をインストール。

今回は先日作った USB メモリのインストールメディアからインストールした。今までは DVD を使ってたけど特に変わったことはない。もっと前から USB メモリにしときゃよかった。

Ubuntu Server のインストールは初めて。インストーラが GUI じゃないけど、基本的には Desktop 版と同じような手順。ただ一点、デフォルトでは LVM(後述)が有効になってて、使えるディスクスペースが小さい。なので LVM を無効にしてインストールをやり直した。

  • 言語の選択肢に日本語がないので「English」を選択
  • Guided storage configuration の画面で「Set up this disk as LVM group」のチェックを外す
  • ユーザー名とホスト名入力
  • 途中で OpenSSH をインストールできる
  • SSH のキーを GitHub から取得できる
  • Featured Server Snaps の画面でいろいろ追加インストールできるようだけど Docker だけ選択

ほかはデフォルトのまま。最後にリブートして終了。

LVM

LVM というのは Logical Volume Manager の略で、日本語でいうと「論理ボリュームマネージャー」。複数の物理ボリューム(HDDやSSD)をまとめてひとつの論理ボリュームを作る機能だ。本格的なサーバなんかを運用するときには、あとからディスクを追加しても LVM があれば既存の論理ボリュームを大きくすることができたりするらしいので、そういうのには便利なんだろう。だけど個人で使うだけのサーバでは不要と言っていい機能だ。なしでいいと思う。↓ここの記事が参考になった。

カテゴリー: Ubuntu | コメントする

ローカルネットワークのサーバを置き換えるんだけど、古いRailsアプリはどうすりゃいいんだ……

置き換え用にと新しく買ったサーバ用の PC が明日届く、とメールがきた。まあ、届いたからって平日なのですぐに作業できるわけでもないんだけど……

それはさておき。現状でローカルネットワークで運用してるサーバ3台を順繰りに押し出すように置き換えていって、最後に押し出される一番古い PC は万が一のときの予備にまわす。この際だからサーバは全部 Ubuntu Server 20.04 に統一しようと、すでにインストールメディアの準備は済んでいる。

で、さて。その一番古いサーバでもいくつかの web アプリを動かしているんだけど、そのひとつがだいぶ前に Ruby on Rails で作ったものなんだ。なんと Rails 4.1.4。ちなみに OS やなんかのバージョンは次の通り:

  • Ubuntu 16.04
  • Ruby 2.3.1
  • Ruby on Rails 4.1.4
  • unicorn 5.4.1

これを最新の Ubuntu 20.04 の環境に置き換えようとしてるんだけど……

とにかく、開発用マシン(Ubuntu 20.04,Ruby 2.7.1)で動かせるかどうか試してみたら、案の定ダメだった。アプリを動かすどころか bundle install の途中でコケる。どうも json とか therubyracer の gem をインストールするところでうまく行かないみたいだ。なら DockerHub にある Ubuntu 公式イメージの一番古いバージョン 16.04 の上でならどうか、と試してみたけどやっぱりダメ。

新しい OS のサーバに移行するのに合わせてアプリもバージョンアップしなきゃな、とは思ってたんだけど、開発環境で動作させられないんじゃそれも難しい。どうすりゃいいんだか…… 

カテゴリー: Ruby, Ubuntu | コメントする

USBメモリにUbuntuのインストールメディアを作る

CentoOS 8 の開発終了のニュースを受けて、ローカルネットワークのサーバ OS を置き換える計画をしてる。Ubuntu をインストールして使ってるサーバもバージョンが 16.04 で EOL が今年の4月と迫っている。そういうわけなので、この際まとめて Ubuntu 20.04 LTS に置き換えようと考えた。

今日はその準備としてインストール用メディアを作る。これまではダウンロードした ISO イメージを DVD-R に焼いてたんだけど、サーバにしてる PC は DVD ドライブついてないし(インストール時には外付けドライブを繋いだ)、せっかくなので USB メモリのインストールメディアを作ってみることにした。

作業環境は次の通り:

  • 作業用マシン:Windows 10
  • インストール用USBメモリ作成:Rufus

Rufus (ルーファス) ってのは起動可能な USB フラッシュドライブを作るソフト。↓ここからダウンロードした。

同様のソフトはほかにもあるようだけど、下調べした感じではこれが一番使いやすそうだった。メニューも日本語だし。バージョンは 3.13。

USB メモリは余ってた 16GB のもの。インストール用メディアとしてはもっと容量が小さくてもよさそうだけど、余ってるんだからそれを使う。

あと、肝心の OS は、Ubuntu Server 20.04.1 LTS。https://jp.ubuntu.com/download からダウンロードした ISO イメージを用意した。

さあ、始めよう。Rufus はインストールの必要はなく、ダウンロードしたファイルをダブルクリックして実行すればいい。

書き込み先(デバイス)と書き込む ISO イメージ(ブートの種類)の設定をしたところがこの画面。デバイスが「回復(G:) [16 GB]」ってなってるのは Windows の回復ドライブに使ってた USB メモリだから。ISO イメージは「選択」ボタンをクリックしてファイルを選択した。他はデフォルトのまま。

これで「スタート」をクリックすると

と表示されて追加のファイルをダウンロードするらしい。「はい」をクリック。

これもこのまま「OK」。

最終確認。問題なければ「OK」をクリックして書き込み開始。2分ほどで終わって↓の画面になる。

状態が「準備完了」になってるけど、これで書き込み終了してる。

試しに作ったばかりの USB メディアから PC を起動してみたら、ちゃんと Ubuntu のインストーラが起動した。

というわけで今日の任務完了。

カテゴリー: Ubuntu, Windows | 1件のコメント

Docker上のMediaWikiにファイルをアップロードする

公式の Docker イメージを使って立てた MediaWiki だけど、ファイルをアップロードするにはやっぱりひと手間必要だった。なので、そのメモ。

MediaWiki にはファイルをアップロードする機能があるけど、デフォルトでは無効になっている。有効にするには MediaWiki と PHP 自体の設定ファイルを修正する必要がある。

  • LocalSettings.php – MediaWiki の設定ファイル
  • php.ini – PHP 自体の設定ファイル

どこをどう修正すればいいかはマニュアルに書いてある。

LocalSettings.php

LocalSettings.php ファイルは、MediaWiki をセットアップしたときにホストにダウンロードして、Docker コンテナには volume としてマウントしてあるので、ホスト側のファイルを編集すればいい。

$wgEnableUploads = true;

$wgEnableUploads に true を設定。

php.ini

で、問題はこっち。php.ini ファイルは MediaWiki の公式 Docker イメージに含まれてるものをそのまま使ったので、ホスト側にはない。なので、まずはコンテナの中で編集して、動作が変わるかどうか確認することにした。

[email protected] $ docker exec -it wiki bash
[email protected]:/var/www/html# php -i | grep php.ini
Configuration File (php.ini) Path => /usr/local/etc/php

php.ini ファイルの場所は php -i コマンドで確認できる(と MediaWiki のマニュアルに書いてある)。このコマンドの出力は結構な量を吐くので grep で php.ini にヒットする行だけ抜き出した。ともあれ、/usr/local/etc/php にあることがわかった。

ところが、そこに php.ini ファイルはなかった。

[email protected]:/var/www/html# cd /usr/local/etc/php
[email protected]:/usr/local/etc/php# ls
conf.d  php.ini-development  php.ini-production

そういうものなのかと疑問に思いながらも、ひとまずは php.ini-production ファイルの中身を見てみようとしても less コマンドがない。

[email protected]:/usr/local/etc/php# less php.ini-production
bash: less: command not found

当然のように vim もない。cat はあったので中身は見れたけど編集はできない。

といわけで、方針を変更してファイルをホスト側にコピーして編集し、LocalSettings.php ファイルと同様にコンテナにマウントすることにした。ファイルをホスト側にコピーするのは次の通り:

[email protected] $ docker exec -it wiki cat /usr/local/etc/php/php.ini-production > php.ini

cat コマンドをコンテナ側で実行して、ホスト側の php.ini ファイルへリダイレクトしている。

で、その php.ini ファイルを編集。3行続けて載せたけど、ファイル中では別々のところにある。

file_uploads = On
post_max_size = 16M
upload_max_filesize = 8M

file_uploads は元から On になってた。post_max_size と upload_max_filesize はそれぞれ 8M2M だったのを大きくした。

docker-compose.yml

編集した php.ini ファイルを LocalSettings.php ファイルと同じディレクトリに配置したら、コンテナにマウントすべく docker-compose.yml を修正。

  wiki:
    image: mediawiki:1.35.0
    container_name: wiki
    restart: always
    depends_on:
      - mysql
    volumes:
      - /home/takatoh/var/wiki/images:/var/www/html/images
      - /home/takatoh/var/wiki/LocalSettings.php:/var/www/html/LocalSettings.php
      - /home/takatoh/var/wiki/php.ini:/usr/local/etc/php/php.ini
    ports:
      - 9090:80

これでOK。

最後にコンテナを起動しなおしたら、無事、ファイルをアップロードできるようになった。

カテゴリー: misc, PHP | コメントする

JavaScript: アロー関数で再帰

あけおめ、ことよろ。

正月早々、奇妙なものを見つけた。元記事は去年(というか昨日)のもので、タイトルの通りなんだけど、こんなのだった。

> const fibonacci = ((fb = n => n > 1 ? n * fb(n - 1) : 1) => fb)();

どう見ても階乗を求める関数なのに名前が fibonacci っておかしいだろ、というのは置いといて。確かに期待通りに動作する。

> fibonacci(5);
120 

元記事の説明によると:

  • アロー関数 n => n > 1 ? n * fb(n - 1) : 1 を変数 fb に代入し
  • その代入した関数を即時関数によって関数 fb として返すように
  • 代入(fibonacci)する

となってて、まあその通りではある。でも、その即時関数っていうのが単に引数をそのまま返してるだけなんだから、それ、いらねんじゃね?

というわけで即時関数なしでやってみると、やっぱり、ちゃんと期待通りに動作するじゃん。

> const fact = n => n > 1 ? n * fact(n - 1) : 1;
undefined
> fact(5);
120 

と、ここまで書いて気がついた。上では即時関数が云々と書いたけど、要するにその即時関数の引数であるアロー関数を変数 fb に代入してそれを呼び出してるだけだ。即時関数は何の関係もない。元記事の筆者がどこでネタを仕入れたのか知らないけど、不必要にトリッキーなだけだ。それとも以前のバージョンの JavaScript ではこう書かなきゃできなかったのかな。

ちなみに確認したのは Node v14.15.3。

[email protected]: takatoh > node -v
v14.15.3

というわけで、元記事から得たものといえば、関数呼び出しの引数部分に代入式が書けることか。使うかな、これ。

あ、あと、ブログのネタにはなった。

カテゴリー: JavaScript | コメントする