Python: pipでローカルネットワー上のプライベートなリポジトリからインストールする

仕事で使うちょっとしたツール(大抵はテキスト処理のツール)を Python で書いたりしてるんだけど、一つ不便なことがある。会社のネットワークから PyPI にアクセスできないんだ。

まぁ、セキュリティだの何だの考えるとインターネットへのアクセスを制限するのもわからないではない。

幸いブラウザでは繋がるので、ファイルを一つずつダウンロードしてからインストールすることは可能で、今までそうやってたんだけど不便なことこの上ない。パッケージがファイル一つで済むならまだいいんだけど、他のパッケージに依存してたりすると、正直やる気が失せてくるし、同僚に使ってもらおうというときにもハードルが高い。

さて、今日になって pip install コマンドの --find-links オプションでパッケージ置き場の URL を指定できることを知った。pypiserver みたいな PyPI クローンのサーバじゃなくて、ディレクトリにパッケージファイルを置いておくだけでいい。

早速、自宅の HTTP サーバにディレクトリを公開して試してみた。こんな感じ。

[email protected]:~$ pip install filtre --find-links http://nightschool/py-packages/ --trusted-host nightschool
Looking in links: http://nightschool/py-packages/
Collecting filtre
  Downloading http://nightschool/py-packages/filtre-0.1.0-py3-none-any.whl (1.9 kB)
Installing collected packages: filtre
Successfully installed filtre-0.1.0

http://nightschool/py-packages/ がパッケージファイルを置いてあるディレクトリの URL。--trusted-host nightschool オプションを指定してるのは HTTPS じゃなくて HTTP だから。filtre ってのは自作のパッケージね。

さらに、Windows なら file: スキーマでも行ける。上のディレクトリは Samba で共有フォルダにしてあって file://nightschool/shared/py-packages/ でアクセスできるので、こうすればいい。

[email protected]: Documents > pip install filtre --find-links file://nightschool/shared/py-packages/ --user
Looking in links: file://nightschool/shared/py-packages/
Processing \\nightschool\shared\py-packages\filtre-0.1.0-py3-none-any.whl
Installing collected packages: filtre
Successfully installed filtre-0.1.0

やぁ、これでだいぶ楽になるぞ。

ちなみに、パッケージファイルをダウンロード(インストールじゃなくて)するには pip download コマンドを使えば依存パッケージも含めてダウンロードしてくれる。使いそうなパッケージを自宅でダウンロードしたら、会社に行ってまとめて適当な共有フォルダに放り込んでおけばいい。

[追記]

pip install コマンドの --find-links オプションや --trusted-host オプションは、pip の設定ファイルに書いておけばいちいち指定しなくてもいい。こんな感じ。

[install]
find-links = http://nightschool/py-packages/
trusted-host = nightschool

pip の設定ファイルは、Linux なら ~/.conf/pip/pip.conf、Windows なら %APPDATA%\pip\pip.ini。

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

MediaWikiにYouTubeの動画を埋め込む

ローカルネットワーク上で使ってる MediaWiki に YouTube の動画を埋め込みたいと思って調べたら、そのまんま、YouTube という名前の拡張機能があった。マニュアルに使い方が載ってる。

これを使うことにする。

環境

  • Ubuntu server 20.04 LTS
  • Docker 20.10.8
  • docker-compose 1.25.5
  • MediaWiki 1.35.0

MediaWiki は Docker コンテナ上で動いていて、これは dockerhub から取ってきた公式のイメージを利用してる。

拡張機能のダウンロードとインストール

拡張機能は、上のリンクからたどってダウンロードした。YouTube-REL1_36-c352d8c.tar.gz っていうファイル名だった。

マニュアルによると、これを MediaWiki のディレクトリにある extensions/ ディレクトリに置けばいい。コンテナの中に入って確認すると、(コンテナ内の)/var/www/html/extensions/ に置けばいいことがわかった。

とはいえ、コンテナ内に置く必要はなくて、外(つまりホスト側)に置いてボリュームをマウントすればいい。実際、LocalSettings.php ファイルなんかもそうなっていて、~/docker-environments/wiki/ 以下に置いてある。

というわけで、ここに extensions/ ディレクトリを作って、YouTube 拡張機能を配置する。

$ cd ~/docker-environments/wiki
$ mkdir extensions
$ cd extensions
$ tar xzvf ~/YouTube-REL1_36-c352d8c.tar.gz

これで extensions/ ディレクトリの下に YouTube/ ディレクトリができる。

つぎに、MediaWiki の設定ファイル LocalSettings.php に1行書き加える。

wfLoadExtension('YouTube');

YouTube っていう拡張機能を読み込む設定だ。MediaWiki についてはこれで終わり。

Dockerコンテナの設定と再起動

拡張機能自体はコンテナの外に置いてあるので、ボリュームとしてマウントする。docker-compose.yml ファイルの volumes セクションに書き加える。

      - /home/takatoh/docker-environments/wiki/extensions/YouTube:/var/www/html/extensions/YouTube

最後にコンテナを起動し直して終わり。

$ docker-compose restart wiki

確認

実際に MediaWiki のページに YouTube の動画を埋め込めることを確認した。楽なもんだね。

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

JavaScript:連番の配列を作る – あるいはJavaScriptの奇妙な仕様

JavaScript で連番の整数を要素に持つ配列を作る方法はいくつも考えられるだろうけど、こんなのを見つけた(見つけたコードそのままじゃなくて、少し整理してある)。

const initArray = (n) => [...Array(n)].map((_, i) => i);

いささかトリッキーだと思うけど、まっさきに思い浮かぶであろう for ループを使うよりも簡潔にかけている。もちろん、期待通りに動く。

> initArray(5);
[ 0, 1, 2, 3, 4 ]

自分のメモのために説明しておくと、Array(n) で要素 n 個の配列をつくり、スプレッド構文を使って別の配列リテラルの中に埋め込む。で、map メソッドを呼び出して、各要素をそのインデックス値に置き換える。結果的に 0 からはじまり n 個の連番をもつ配列が出来上がる。

さて、トリッキーだと思うのは、コンストラクタでつくった配列 Array(n) をわざわざスプレッド構文を使って別の配列リテラルに埋め込むところ [...Array(n)] だ。だけどこれがないと期待通りに動かない。

> Array(5).map((_, i) => i);
[ <5 empty items> ]

<5 empty items> ってなんだ。そりゃ初期化してないから空の配列なんだろうけど、長さ(要素数)は 5 なんでしょ?

> Array(5).length;
5

いままで使ったことがなかったので知らなかったけど、配列をコンストラクタ Array() でつくると、長さはあるけど何の値も入ってない、まさに空の配列が返ってくる。

> Array(5);
[ <5 empty items> ]

この「空(から)」は undefined でも null でもない。この配列にインデックスを使ってアクセスすると undefined が返ってくる。

> const a = Array(5);
undefined
> a[3];
undefined
> a[3] == undefined;
true

なら undefined が5個入った配列と等しいのかというとそれも違う。

> a == [undefined, undefined, undefined, undefined, undefined];
false

さらにいえば、リテラルでつくったから配列とも違う。

> a == [];
false

わけがわからないよ。

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

JavaScript:関数の名前を取得する

Function オブジェクトの name プロパティで関数名を文字列として取得できることを知った。

Node.js の REPL で試してみるよ。

> function func1() {}
undefined
> func1.name
'func1'

ほんとだ。

アロー関数ではどうだろう?

> const func2 = () => {}
undefined
> func2.name
'func2'

ほうほう。代入先の変数名が返ってくるとな。

なら、これを別の変数に代入したら?

> func3 = func2
[Function: func2]
> func3.name
'func2'

えー??

ところで、関数の名前を文字列で取得して何に使えるのかと思ったら eval() があるのね。

でも「使うな」って書いてある。

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

Python: あるかどうかわからないメソッドを呼び出す

 んー、タイトルが何をいってるのかよくわからないけれども。

先日使った Lark の lark.Transformer や lark.Interpreter というクラスでは、構文木をたどりながら文法要素の名前をもつメソッドを呼び出していた。「文法要素の名前」はユーザが定義するので、ライブラリである Lark が予め知っているはずがない。でもちゃんと動いていた。ということは何らかの方法があるわけで、Ruby だったら Object#send を使えばいいんだけど Python ではどうやるんだろう、というわけで調べてみた。

こたえ: getattr() でクラスの持つメソッド(存在すれば)を取得できる

試しにこんなクラスを書いた。

>>> class User:
...     def name(self):
...         return 'Andy'
...     def age(self):
...         return 32
...     def info(self, key):
...         f = getattr(self, key)
...         return f()         

試してみよう。

>>> andy = User()
>>> andy.name()
'Andy'
>>> andy.age()
32         

ここまでは普通のメソッド呼び出し。次の User.info() が getattr() を使っている。

>>> andy.info('name')
'Andy'
>>> andy.info('age')
32         

存在しないメソッド名を指定すると AttributeError になる。

>>> andy.info('job')
Traceback (most recent call last):
  File "<stdin>", line 1, in 
  File "<stdin>", line 7, in info
AttributeError: 'User' object has no attribute 'job'         

なるほど。あとは必要に応じて AttributeError の処理をすればいいわけだな。理解した。

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

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 | コメントする