Ruby 製アプリを Unicorn で動かす

昨日に引き続きデプロイの続きをするよ。今日の作業はまず Unicorn でアプリを動かすことだ。

Gemfile に unicorn を書き足して bundle install。

source "https://rubygems.org"

gem 'sinatra'
gem 'httpclient'
gem 'json'
gem 'filestorage'
gem 'unicorn'
[lcstorage@rollo lcstorage]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/………….
Resolving dependencies…
Using bundler 2.0.2
Using filestorage 0.1.0
Using httpclient 2.8.3
Using json 2.2.0
Fetching kgio 2.11.2
Installing kgio 2.11.2 with native extensions
Using mustermann 1.0.3
Using rack 2.0.7
Using rack-protection 2.0.7
Fetching raindrops 0.19.0
Installing raindrops 0.19.0 with native extensions
Using tilt 2.0.10
Using sinatra 2.0.7
Fetching unicorn 5.5.1
Installing unicorn 5.5.1 with native extensions
Bundle complete! 5 Gemfile dependencies, 12 gems now installed.
Bundled gems are installed into ./vendor/bundle

Unicorn の設定ファイル unicorn.conf を書く。

listen "9000"
worker_processes 2
working_directory "/home/lcstorage/lcstorage"
pid "/home/lcstorage/lcstorage/unicorn.pid"
stdout_path "/home/lcstorage/lcstorage/unicorn.log"
stderr_path "/home/lcstorage/lcstorage/unicorn.log"
preload_app true

これでいいはず。試しに起動してみる。

[lcstorage@rollo lcstorage]$ bundle exec unicorn -c unicorn.conf -E production

昨日と同じく、別のコンソールからアクセス。

[takatoh@rollo ~]$ curl http://localhost:9000/
File not found.[takatoh@rollo ~]$ 
[takatoh@rollo ~]$ curl http://localhost:9000/path/to/file
File not found.[takatoh@rollo ~]$

うん、ちゃんと File not found. が返ってきてる。昨日も書いたけど、返すファイルがまだ無いのでこれは正しい動作だ。

さて、あとは Nginx との連携だけど、これはエントリを分ける。というわけでとりあえずここまで。

CentOS 8 のマシンに Ruby 製 web アプリをデプロイしようとしたら bundle install でコケた

前にやった時もそうだったような気がするんだよなぁ。

詳しい話に入る前に、作業環境を整理しよう。web アプリのデプロイ先は、この間買った新しい マシン rollo (OS は CentOS 8)。これにいつもの作業用メインマシン apostrophe(OS は Ubuntu 16.04 LTS)から ssh で接続して作業してる。rollo 上での作業はアプリの専用ユーザ lcstorage で行った。

まずは、BitBucket から git clone した。

GitHub じゃないのは公開したくないプライベートなプロジェクトだから。オープンにしてもいいプロジェクトは GitHub、そうでないのは BitBucket と使い分けてる。

閑話休題。git clone が終わったので、必要なライブラリをインストールすべく bundle install したらコケた。

[lcstorage@rollo lcstorage]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/………….
Using bundler 2.0.2
Fetching filestorage 0.1.0
Installing filestorage 0.1.0
Fetching httpclient 2.8.3
Installing httpclient 2.8.3
Fetching json 2.2.0
Installing json 2.2.0 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

current directory:
/home/lcstorage/lcstorage/vendor/bundle/ruby/2.5.0/gems/json-2.2.0/ext/json/ext/generator
/usr/bin/ruby -r ./siteconf20191122-22908-wlblo2.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/share/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in
/home/lcstorage/lcstorage/vendor/bundle/ruby/2.5.0/gems/json-2.2.0 for
inspection.
Results logged to
/home/lcstorage/lcstorage/vendor/bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0/json-2.2.0/gem_make.out

An error occurred while installing json (2.2.0), and Bundler cannot
continue.
Make sure that gem install json -v '2.2.0' --source 'https://rubygems.org/'
succeeds before bundling.

In Gemfile:
  json

どうも json gem が拡張ライブラリで、ビルドできなくてコケてるようだ。いろいろググって試行錯誤した結果、つぎのパッケージを dnf install したらできた。

  • gcc
  • make
  • ruby-devel
  • rpm-build

gcc や make が入ってなかったのか……

ともあれこれでインストールはできた。必要な設定ファイルやらディレクトリやらを作って、試しに起動してみた。

[lcstorage@rollo lcstorage]$ bundle exec rackup
[2019-11-22 22:32:38] INFO  WEBrick 1.4.2
[2019-11-22 22:32:38] INFO  ruby 2.5.3 (2018-10-18) [x86_64-linux]
[2019-11-22 22:32:38] INFO  WEBrick::HTTPServer#start: pid=25617 port=9292

起動は大丈夫のようだ。9292番のポートは開けてないので、別のターミナルから別のユーザで rollo に入ってアクセスしてみた。

[takatoh@rollo ~]$ curl http://localhost:9292/
File not found.[takatoh@rollo ~]$ 
[takatoh@rollo ~]$ curl http://localhost:9292/path/to/file
File not found.[takatoh@rollo ~]$

返すファイルがないので File not found. が帰ってくるのは正しい動作。どうやら大丈夫のようだ。

明日は Nginx を介して外部からアクセスできるようにする予定。

CentOS 8 をインストールしてみた

新しい PC を買ったんだ。これまで Dell か HP だったので、今回は Lenovo にしてみた。ThinkCentre M630e Tiny っていうモデル。てのひらにはちょっとあまるけど、超小型 PC と言っていい大きさだ。

で、せっかくなので最近リリースされたばかりの CentOS 8.0.1905 をインストールしてみた。プレインストールされていた Windows 10 は削除。

インストーラは CentOS 7 と特段変わらない印象。でもデフォルトのインストールタイプ(っていうんだっけ?)が、「サーバー(GUI使用)」になってた。もちろんこれでOK。ひととおりインストールが済んで再起動すると、ちゃんと CentOS が立ち上がった。いつかの Dell の PC みたいに起動しないなんてことはなかった。いい兆候だ。あと、ホスト名は rollo にした。

日本語入力

インストール時に日本語を選んでいるので、メニューとかは日本語になってるんだけど、そのままでは入力はできないようだ。↓このページが参考になった。

cf. デスクトップ環境 : GNOME デスクトップ インストール – Server World

まずは日本語入力プログラムをインストール。

[takatoh@rollo ~]$ sudo dnf -y install ibus-kkc

CentOS 7 までの yum コマンドじゃなくて dnf っていうコマンドでパッケージをインストールするらしい。

それから、背景画面の適当なところで右クリックして設定ウィンドウをひらいて、 Region & Language に移動する。入力ソースの欄で、日本語(かな漢字)を追加すれば準備は完了。画面右上のアイコンをクリックして日本語(かな漢字)を選べば OK だ。あとは半角/全角キーで入力モードを切り替えられる。今日のこのエントリもそうやって書いている。

Ruby とか Python とか

Python が入ってない、と思ったら python3 コマンドだった。

[takatoh@rollo ~]$ python3 -V
Python 3.6.8

Python はデフォルトが 3 系になったらしい。なら python コマンドでいいだろうになんで python3 なんだ。

Ruby は入ってない。のでインストールした。

[takatoh@rollo ~]$ ruby -v
bash: ruby: コマンドが見つかりませんでした…
コマンド ruby' を提供するためにパッケージ 'ruby' をインストールしますか? [N/y] y
キューで待機中… 
パッケージの一覧をロード中。… 
以下のパッケージはインストールされるべきものです:
ruby-2.5.3-104.module_el8.0.0+179+565e49e2.x86_64    An interpreter of object-oriented scripting language
ruby-irb-2.5.3-104.module_el8.0.0+179+565e49e2.noarch    The Interactive Ruby
ruby-libs-2.5.3-104.module_el8.0.0+179+565e49e2.x86_64    Libraries necessary to run Ruby
rubygem-bigdecimal-1.3.4-104.module_el8.0.0+179+565e49e2.x86_64    BigDecimal provides arbitrary-precision floating point decimal arithmetic
rubygem-did_you_mean-1.2.0-104.module_el8.0.0+179+565e49e2.noarch    "Did you mean?" experience in Ruby
rubygem-io-console-0.4.6-104.module_el8.0.0+179+565e49e2.x86_64    IO/Console is a simple console utilizing library
rubygem-json-2.1.0-104.module_el8.0.0+179+565e49e2.x86_64    This is a JSON implementation as a Ruby extension in C
rubygem-openssl-2.1.2-104.module_el8.0.0+179+565e49e2.x86_64    OpenSSL provides SSL, TLS and general purpose cryptography
rubygem-psych-3.0.2-104.module_el8.0.0+179+565e49e2.x86_64    A libyaml wrapper for Ruby
rubygem-rdoc-6.0.1-104.module_el8.0.0+179+565e49e2.noarch    A tool to generate HTML and command-line documentation for Ruby projects
rubygems-2.7.6-104.module_el8.0.0+179+565e49e2.noarch    The Ruby standard for packaging ruby libraries
変更したまま継続しますか? [N/y] y
キューで待機中… 
認証を待ち受け中… 
キューで待機中… 
パッケージをダウンロード中… 
データを要求中… 
変更をテスト中… 
パッケージのインストール中… 
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux] 
[takatoh@rollo ~]$ ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]

2.5.3 がインストールされた。

というところで、今日はここまで。

UbuntuにRMagickをインストール

環境

takatoh@apostrophe $ cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.5 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.5 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
takatoh@apostrophe $ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]

↓このページによると、ImageMagick がバージョン7以降だと RMagick は使えないらしい。

 cf. RMagickのインストールで奈落に落ちた – Qiita

takatoh@apostrophe $ convert -version
Version: ImageMagick 6.8.9-9 Q16 x86_64 2018-09-28 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC
Features: DPC Modules OpenMP
Delegates: bzlib cairo djvu fftw fontconfig freetype jbig jng jpeg lcms lqr ltdl lzma openexr pangocairo png rsvg tiff wmf x xml zlib

6.8.9 だ、ラッキー。

依存パッケージとRMagickのインストール

まずは依存パッケージから。

takatoh@apostrophe $ sudo apt install libmagickcore-dev libmagickwand-dev

そして、RMagick。

takatoh@apostrophe $ sudo gem install rmagick
Building native extensions. This could take a while…
Successfully installed rmagick-2.16.0
Parsing documentation for rmagick-2.16.0
Installing ri documentation for rmagick-2.16.0
Done installing documentation for rmagick after 4 seconds
1 gem installed

完了。

Gem in a Boxでオレオレgemサーバをたてる

自分でしか使わない、外部には公開できないような gem がいくつかあるので、gem サーバをたてることにした。Gem in a Box (geminabox)は、プライベートな gem サーバだ。gem コマンドでインストールして、ちょっと設定ファイルを書くだけで使えるようになる。

じゃ、いってみよう。

環境

  • Ubuntu 16.04 LTS
  • Ruby 2.3.1p112

インストールと設定

前述したように gem コマンドでインストールできる。

takatoh@wplj $ sudo gem install geminabox

~/geminabox ディレクトリを作って、設定ファイルを書く。

takatoh@wplj $ mkdir ~/geminabox
takatoh@wplj $ cd ~/geminabox
takatoh@wplj $ vim config.ru

設定ファイルはこんな感じ、っていうかこれだけ。

require "geminabox"

Geminabox.data = "/home/takatoh/geminabox/data"
run Geminabox::Server

gem ファイルが格納される data ディレクトリを作る。

takatoh@wplj $ mkdir data

これで Gem in a Box 自体のインストールと設定は完了。

Supervisord で管理

さて、これをデーモンにする必要があるんだけど、昨日 Supvervisord を試したので今日も同じようにしてみる。設定ファイル /etc/supervisord.d/geminabox.ini をつぎのように書いた。

[program:geminabox]
command=/usr/local/bin/rackup -p 9999
process_name=geminabox
user=takatoh
directory=/home/takatoh/geminabox
autostart=true
autorestart=true
stdout_logfile=/home/takatoh/geminabox/geminabox.log
stdout_logfile_maxbytes=1M
stdout_logfile_backups=7
stdout_capture_maxbytes=1M
redirect_stderr=true

これで OK のはず。動かしてみよう。

takatoh@wplj $ supervisorctl reload
Restarted supervisord
takatoh@wplj $ supervisorctl status
geminabox RUNNING pid 26835, uptime 0:00:08
sulaiman RUNNING pid 26836, uptime 0:00:08

ちゃんと動いてるようだ。

Nginxのバーチャルホスト設定

gembox というホスト名でアクセスできるように Nginx を設定する。/etc/nginx/sites-available/gembox が設定ファイルだ。

upstream geminabox {
    server 127.0.0.1:9999;
}

server {
    # port
    listen      80;

    # server name
    server_name gembox;

    # document root
    root        /home/takatoh/geminabox;

    # index
    #index       index.php index.html index.htm;

    # log files
    access_log /var/log/nginx/gembox/access.log combined;
    error_log  /var/log/nginx/gembox/error.log  warn;

    keepalive_timeout     60;
    proxy_connect_timeout 60;
    proxy_read_timeout    60;
    proxy_send_timeout    60;

 
    location / {
        proxy_set_header Host $http_host;
        proxy_pass http://geminabox;
    }
}

/etc/nginx/sites-enabled/gembox にリンクをはる。

takatoh@wplj $ sudo ln -s /etc/nginx/sites-available/gembox sites-enabled/gembox

ログファイル用のディレクトリを作成。

takatoh@wplj $ sudo mkdir /var/log/nginx/gembox

そして、Ngixn をリロード。

takatoh@wplj $ sudo systemctl reload nginx

これで完了。ほかのマシンからブラウザで http://gembox/ にアクセスすると、期待どおり Gem in a Box のページにアクセスできた。

[追記]

アクセスはできたけど、試しに gem ファイルを1つアップロードしてみたら「uninitialized constant Gem::Util」というエラーが出てしまった。なんだこりゃ?

[追記2]

解決編。rubygems 自体が古いのが原因だったようだ。

takatoh@wplj $ sudo gem update --system
takatoh@wplj $ gem -v
3.0.1

念のため、データ置き場を作りなおす。

takatoh@wplj $ cd ~/geminabox
takatoh@wplj $ rm -rf data
takatoh@wplj $ mkdir data

リスタート。

takatoh@wplj $ supervisorctl restart geminabox
geminabox: stopped
geminabox: started

これで無事、オレオレgemをアップロードできるようになった。

「ソフトウェアエンジニアならば1時間以内に解けなければいけない5つの問題」をRubyで

ちょっと、というか結構古いけど 「ソフトウェアエンジニアならば1時間以内に解けなければいけない5つの問題」 というのを見つけた。

 cf. 1時間以内に解けなければプログラマ失格となってしまう5つの問題が話題に – SOFTANTENNA BLOG

大元のページはもうなくなっているようだけど、上のページに問題が載っているので Ruby でやってみた。

問題1

forループ、whileループ、および再帰を使用して、リスト内の数字の合計を計算する3つの関数を記述せよ。

def solv1(ary)
  result = 0
  for i in ary
    result += i
  end
  result
end

def solv2(ary)
  result = 0
  while ary.size > 0
    result += ary.shift
  end
  result
end

def solv3(ary)
  if ary.empty?
    0
  else
    x = ary.shift
    x + solv3(ary)
  end
end

puts solv1([1,2,3,4,5])
puts solv2([1,2,3,4,5])
puts solv3([1,2,3,4,5])

問題2

交互に要素を取ることで、2つのリストを結合する関数を記述せよ。例えば [a, b, c]と[1, 2, 3]という2つのリストを与えると、関数は [a, 1, b, 2, c, 3]を返す。

def solv(a1, a2)
  result = []
  until a1.empty?
    result << a1.shift
    result << a2.shift
  end
  result
end

p solv(['a', 'b', 'c'], [1, 2, 3])

問題3

最初の100個のフィボナッチ数のリストを計算する関数を記述せよ。定義では、フィボナッチ数列の最初の2つの数字は0と1で、次の数は前の2つの合計となる。例えば最初の10個のフィボナッチ数列は、0, 1, 1, 2, 3, 5, 8, 13, 21, 34となる。

require 'pp'

def fib(a, b, acc, n)
  if n == 0
    acc
  else
    acc << a
    fib(b, a + b, acc, n - 1)
  end
end

def solv
  fib(0, 1, [], 100)
end

pp solv

問題4

正の整数のリストを与えられたとき、数を並び替えて可能な最大数を返す関数を記述せよ。例えば、[50, 2, 1, 9]が与えられた時、95021が答えとなる。

def solv(ary)
  ary.map{|x| x.to_s }.sort.reverse.join("").to_i
end

puts solv([50, 2, 1, 9])

問題5

1,2,…,9の数をこの順序で、”+”、”-“、またはななにもせず結果が100となるあらゆる組合せを出力するプログラムを記述せよ。例えば、1 + 2 + 34 – 5 + 67 – 8 + 9 = 100となる 。

def combi(ary)
  if ary.size == 1
    ary
  else
    result = []
    x = ary.shift
    c = combi(ary)
    ["+", "-", ""].each do |op|
      result += c.map{|e| x + op + e }
    end
    result
  end
end

def solv
  a = [1,2,3,4,5,6,7,8,9].map{|n| n.to_s }
  result = []
  combi(a).each do |e|
    if eval(e) == 100
      result << e
    end
  end
  result
end

solv.each do |e|
  puts e + " = 100"
end

俺はソフトウェアエンジニアじゃないけど、どうにか1時間以内に5問ともできた。一番難しかったのは問題5だけど、意外にてこずったのは問題1。Ruby の for の使い方(というか for があること自体)を忘れていて、結局ここだけググった。

Railsアプリのログをローテーションした話

Web を見ていてログローテーションの記事を見かけたので、そういえばさくらの VPS で動かしている Rails アプリのログはどうなってるんだろう、と思った。で、調べてみるとつぎの通り。

[takatoh@tk2-254-36564 log]$ ls -lh
合計 1.2G
-rw-r--r-- 1 root root  25K  5月  5 09:53 2017 development.log
-rw-r--r-- 1 root root 1.2G 12月  5 03:56 2018 production.log

うわあああああ、production.log が 1.2GB もあるううう!!!!!

こりゃ、ぜひともログローテーションしなきゃ、と思って調べてみると、Rails にはログのローテーションをする機能があるらしい。アプリのディレクトリ以下の config/environments/production.rb ファイルで、つぎのようにすればいいようだ。

config.logger = Logger.new("log/production.log", 'daily')

ところが、実際に動いているアプリのファイル(の該当部分)を見てみるとつぎのようになっている。

# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)

コメントアウトしてあるのはいいとしても、Rails のバージョンのせいなのか何なのか、全然違う。はっきりいってこういうのはウカツに弄りたくない。

というわけで、logrotate でやってみることにした。さくらの VPS では logrotate がデフォルトで動いている(らしい)ので、/etc/logrotate.d 以下に設定ファイルを書いて置いてやればいい。

/var/www/lathercraft/log/production.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/sbin/service unicorn_lathercraft rotate > /dev/null 2> /dev/null || true
endscript
}

参考にしたページ:
cf. さくらのVPSの設定 – logrotateの設定
テストしてみる。

[takatoh@tk2-254-36564 logrotate.d]$ logrotate -dv unicorn_lathercraft
reading config file lathercraft
reading config info for /var/www/lathercraft/log/production.log 

Handling 1 logs

rotating pattern: /var/www/lathercraft/log/production.log  after 1 days (14 rotations)
empty log files are not rotated, old logs are removed
considering log /var/www/lathercraft/log/production.log
  log does not need rotating
not running postrotate script, since no logs were rotated

エラーらしきものは出てないので、いいのかな。
とりあえずこれで様子を見てみよう。

リスト(配列)の中で隣り合う同じ値をグループ化する

リストでも配列でもいいけど、つまりこういうのを

[1, 1, 2, 2, 3, 1, 1]

こうしたい。

[[1, 1], [2, 2], [3], [1, 1]]

Ruby でやってみた。

def adjacent_group(ary)
  result = []
  current = nil
  ary.each do |x|
    if current.nil?
      current = x
      result << [x]
    elsif x == current
      result[-1] << x
    else
      current = x
      result << [x]
    end
  end
  result
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
^o^ >ruby adjacent_group.rb
[[1, 1], [2, 2], [3], [1, 1]]

おなじく Python で。

def adjacent_group(lis):
    result = []
    current = None
    for x in lis:
        if current is None:
            current = x
            result.append([x])
        elif x == current:
            result[-1].append(x)
        else:
            current = x
            result.append([x])
    return result

print(adjacent_group([1, 1, 2, 2, 3, 1, 1]))
^o^ >python adjacent_group.py
[[1, 1], [2, 2], [3], [1, 1]]

ちょっとベタだな。もう少しスマートにいかないものか、と考えて Ruby の Array#inject が使えそうだと思いついた。

def adjacent_group(ary)
  ary.inject([[]]) do |a, x|
    if a[-1].empty? || x == a[-1][-1]
      a[-1] << x
    else
      a << [x]
    end
    a
  end
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
^o^ >ruby adjacent_group2.rb
[[1, 1], [2, 2], [3], [1, 1]]

うまくいった。

さて、じゃ、Python ではどうか。reduce を使えば同じことができると考えたけど、Python の reduce は初期値がとれない。まあ、それはリストの頭に初期値をつけてやれば済む話ではあるけど、もうひとつ問題がある。Ruby の Array#inject はブロックをとれるけど、Python の reduce には関数を渡してやらなきゃいけないので、関数定義がひとつ増えてしまう。一行では書けないので lambda 式は使えない。
というわけで、上のようにベタに書いたほうがまだマシそうだ。何かいい書き方があったら、誰か教えてください。

[追記](9/25)

Ruby の2つ目の実装では、引数に空の配列を渡したときに期待通りに動作しない([[]] が返ってきてしまう)。そこでちょっと直したのがこれ。

def adjacent_group(ary)
  ary.inject([]) do |a, x|
    if !a.empty? && x == a[-1][-1]
      a[-1] << x
    else
      a << [x]
    end
    a
  end
end

p adjacent_group([1, 1, 2, 2, 3, 1, 1])
p adjacent_group([])
^o^ >ruby adjacent_group.rb
[[1, 1], [2, 2], [3], [1, 1]]
[]

これでいいだろう。

NginxでCGIを動かす

Nginx では CGI は動かない。サポートしてないんだそうだ。まあ、いまどき CGI ってのも……という気はするけど、ちょっと古い CGI プログラムが必要になったのでやってみた。
参考にしたページはここ。

 cf. NginxでCGIを動かそうと頑張った話 – Qiita

これによると、fcgiwrap というのを使えば、CGI を FCGI にラップしてくれて、CGI プログラムを動かせるようだ。ラップするといってもインターフェイスの話で、中身はあくまで CGI なので FCGI なみに早く動作するわけではない。

とにかくやってみよう。
まずは fcgiwrap のインストール。apt でインストールできる。

takatoh@wplj $ sudo apt install fcgiwrap
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  libllvm3.8 libllvm4.0 libllvm5.0 libmircommon5 libpango1.0-0 libpangox-1.0-0
  libqmi-glib1 libqpdf17 linux-headers-4.4.0-101
  linux-headers-4.4.0-101-generic linux-headers-4.4.0-103
  linux-headers-4.4.0-103-generic linux-headers-4.4.0-104
  linux-headers-4.4.0-104-generic linux-headers-4.4.0-109
  linux-headers-4.4.0-109-generic linux-headers-4.4.0-112
  linux-headers-4.4.0-112-generic linux-headers-4.4.0-116
  linux-headers-4.4.0-116-generic linux-headers-4.4.0-119
  linux-headers-4.4.0-119-generic linux-headers-4.4.0-121
  linux-headers-4.4.0-121-generic linux-headers-4.4.0-122
  linux-headers-4.4.0-122-generic linux-headers-4.4.0-124
  linux-headers-4.4.0-124-generic linux-headers-4.4.0-127
  linux-headers-4.4.0-127-generic linux-headers-4.4.0-128
  linux-headers-4.4.0-128-generic linux-headers-4.4.0-130
  linux-headers-4.4.0-130-generic linux-headers-4.4.0-131
  linux-headers-4.4.0-131-generic linux-headers-4.4.0-21
  linux-headers-4.4.0-21-generic linux-headers-4.4.0-64
  linux-headers-4.4.0-64-generic linux-headers-4.4.0-66
  linux-headers-4.4.0-66-generic linux-headers-4.4.0-67
  linux-headers-4.4.0-67-generic linux-headers-4.4.0-70
  linux-headers-4.4.0-70-generic linux-headers-4.4.0-71
  linux-headers-4.4.0-71-generic linux-headers-4.4.0-72
  linux-headers-4.4.0-72-generic linux-headers-4.4.0-75
  linux-headers-4.4.0-75-generic linux-headers-4.4.0-77
  linux-headers-4.4.0-77-generic linux-headers-4.4.0-78
  linux-headers-4.4.0-78-generic linux-headers-4.4.0-79
  linux-headers-4.4.0-79-generic linux-headers-4.4.0-81
  linux-headers-4.4.0-81-generic linux-headers-4.4.0-83
  linux-headers-4.4.0-83-generic linux-headers-4.4.0-87
  linux-headers-4.4.0-87-generic linux-headers-4.4.0-89
  linux-headers-4.4.0-89-generic linux-headers-4.4.0-91
  linux-headers-4.4.0-91-generic linux-headers-4.4.0-92
  linux-headers-4.4.0-92-generic linux-headers-4.4.0-93
  linux-headers-4.4.0-93-generic linux-headers-4.4.0-96
  linux-headers-4.4.0-96-generic linux-headers-4.4.0-97
  linux-headers-4.4.0-97-generic linux-headers-4.4.0-98
  linux-headers-4.4.0-98-generic linux-image-4.4.0-101-generic
  linux-image-4.4.0-103-generic linux-image-4.4.0-104-generic
  linux-image-4.4.0-109-generic linux-image-4.4.0-112-generic
  linux-image-4.4.0-116-generic linux-image-4.4.0-119-generic
  linux-image-4.4.0-121-generic linux-image-4.4.0-122-generic
  linux-image-4.4.0-124-generic linux-image-4.4.0-127-generic
  linux-image-4.4.0-128-generic linux-image-4.4.0-130-generic
  linux-image-4.4.0-131-generic linux-image-4.4.0-21-generic
  linux-image-4.4.0-64-generic linux-image-4.4.0-66-generic
  linux-image-4.4.0-67-generic linux-image-4.4.0-70-generic
  linux-image-4.4.0-71-generic linux-image-4.4.0-72-generic
  linux-image-4.4.0-75-generic linux-image-4.4.0-77-generic
  linux-image-4.4.0-78-generic linux-image-4.4.0-79-generic
  linux-image-4.4.0-81-generic linux-image-4.4.0-83-generic
  linux-image-4.4.0-87-generic linux-image-4.4.0-89-generic
  linux-image-4.4.0-91-generic linux-image-4.4.0-92-generic
  linux-image-4.4.0-93-generic linux-image-4.4.0-96-generic
  linux-image-4.4.0-97-generic linux-image-4.4.0-98-generic
  linux-image-extra-4.4.0-101-generic linux-image-extra-4.4.0-103-generic
  linux-image-extra-4.4.0-104-generic linux-image-extra-4.4.0-109-generic
  linux-image-extra-4.4.0-112-generic linux-image-extra-4.4.0-116-generic
  linux-image-extra-4.4.0-119-generic linux-image-extra-4.4.0-121-generic
  linux-image-extra-4.4.0-122-generic linux-image-extra-4.4.0-124-generic
  linux-image-extra-4.4.0-127-generic linux-image-extra-4.4.0-128-generic
  linux-image-extra-4.4.0-130-generic linux-image-extra-4.4.0-131-generic
  linux-image-extra-4.4.0-21-generic linux-image-extra-4.4.0-64-generic
  linux-image-extra-4.4.0-66-generic linux-image-extra-4.4.0-67-generic
  linux-image-extra-4.4.0-70-generic linux-image-extra-4.4.0-71-generic
  linux-image-extra-4.4.0-72-generic linux-image-extra-4.4.0-75-generic
  linux-image-extra-4.4.0-77-generic linux-image-extra-4.4.0-78-generic
  linux-image-extra-4.4.0-79-generic linux-image-extra-4.4.0-81-generic
  linux-image-extra-4.4.0-83-generic linux-image-extra-4.4.0-87-generic
  linux-image-extra-4.4.0-89-generic linux-image-extra-4.4.0-91-generic
  linux-image-extra-4.4.0-92-generic linux-image-extra-4.4.0-93-generic
  linux-image-extra-4.4.0-96-generic linux-image-extra-4.4.0-97-generic
  linux-image-extra-4.4.0-98-generic linux-signed-image-4.4.0-101-generic
  linux-signed-image-4.4.0-103-generic linux-signed-image-4.4.0-104-generic
  linux-signed-image-4.4.0-109-generic linux-signed-image-4.4.0-112-generic
  linux-signed-image-4.4.0-116-generic linux-signed-image-4.4.0-119-generic
  linux-signed-image-4.4.0-121-generic linux-signed-image-4.4.0-122-generic
  linux-signed-image-4.4.0-124-generic linux-signed-image-4.4.0-127-generic
  linux-signed-image-4.4.0-128-generic linux-signed-image-4.4.0-130-generic
  linux-signed-image-4.4.0-131-generic linux-signed-image-4.4.0-64-generic
  linux-signed-image-4.4.0-66-generic linux-signed-image-4.4.0-67-generic
  linux-signed-image-4.4.0-70-generic linux-signed-image-4.4.0-71-generic
  linux-signed-image-4.4.0-72-generic linux-signed-image-4.4.0-75-generic
  linux-signed-image-4.4.0-77-generic linux-signed-image-4.4.0-78-generic
  linux-signed-image-4.4.0-79-generic linux-signed-image-4.4.0-81-generic
  linux-signed-image-4.4.0-83-generic linux-signed-image-4.4.0-87-generic
  linux-signed-image-4.4.0-89-generic linux-signed-image-4.4.0-91-generic
  linux-signed-image-4.4.0-92-generic linux-signed-image-4.4.0-93-generic
  linux-signed-image-4.4.0-96-generic linux-signed-image-4.4.0-97-generic
  linux-signed-image-4.4.0-98-generic snap-confine ubuntu-core-launcher
これを削除するには 'sudo apt autoremove' を利用してください。
以下の追加パッケージがインストールされます:
  libfcgi0ldbl spawn-fcgi
以下のパッケージが新たにインストールされます:
  fcgiwrap libfcgi0ldbl spawn-fcgi
アップグレード: 0 個、新規インストール: 3 個、削除: 0 個、保留: 28 個。
193 kB のアーカイブを取得する必要があります。
この操作後に追加で 665 kB のディスク容量が消費されます。
続行しますか? [Y/n] Y
取得:1 http://jp.archive.ubuntu.com/ubuntu xenial/main amd64 libfcgi0ldbl amd64 2.4.0-8.3 [161 kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu xenial/universe amd64 spawn-fcgi amd64 1.6.4-1 [14.5 kB]
取得:3 http://jp.archive.ubuntu.com/ubuntu xenial/universe amd64 fcgiwrap amd64 1.1.0-6 [17.5 kB]
193 kB を 0秒 で取得しました (1,165 kB/s)
以前に未選択のパッケージ libfcgi0ldbl を選択しています。
(データベースを読み込んでいます ... 現在 1351986 個のファイルとディレクトリがインストールされています。)
.../libfcgi0ldbl_2.4.0-8.3_amd64.deb を展開する準備をしています ...
libfcgi0ldbl (2.4.0-8.3) を展開しています...
以前に未選択のパッケージ spawn-fcgi を選択しています。
.../spawn-fcgi_1.6.4-1_amd64.deb を展開する準備をしています ...
spawn-fcgi (1.6.4-1) を展開しています...
以前に未選択のパッケージ fcgiwrap を選択しています。
.../fcgiwrap_1.1.0-6_amd64.deb を展開する準備をしています ...
fcgiwrap (1.1.0-6) を展開しています...
man-db (2.7.5-1) のトリガを処理しています ...
systemd (229-4ubuntu21.2) のトリガを処理しています ...
ureadahead (0.100.0-19) のトリガを処理しています ...
ureadahead will be reprofiled on next reboot
libfcgi0ldbl (2.4.0-8.3) を設定しています ...
spawn-fcgi (1.6.4-1) を設定しています ...
fcgiwrap (1.1.0-6) を設定しています ...
insserv: warning: script 'K01tonzlr' missing LSB tags and overrides
insserv: warning: script 'K01bruschetta' missing LSB tags and overrides
insserv: warning: script 'K01ellie' missing LSB tags and overrides
insserv: warning: script 'tonzlr' missing LSB tags and overrides
insserv: warning: script 'ellie' missing LSB tags and overrides
insserv: warning: script 'bruschetta' missing LSB tags and overrides
libc-bin (2.23-0ubuntu10) のトリガを処理しています ...
systemd (229-4ubuntu21.2) のトリガを処理しています ...
ureadahead (0.100.0-19) のトリガを処理しています ...

Nginx の設定。/etc/nginx/sites-available/default ファイルに以下を追記。

location ~ \.cgi$ {
    root /var/www/html;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

これで .cgi 拡張子を持つファイルがすべて CGI プログラムとして認識されるはず。
/var/run/fcgiwrap.socket は fcgiwrap を apt でインストールすると自動的に作られている。

fcgiwrap の設定。/etc/init.d/fcgiwrap ファイルを編集する。

# FCGI_APP Variables
FCGI_CHILDREN="1"
FCGI_SOCKET="/var/run/$NAME.socket"
FCGI_USER="www-data"
FCGI_GROUP="www-data"
# Socket owner/group (will default to FCGI_USER/FCGI_GROUP if not defined)
FCGI_SOCKET_OWNER="www-data"
FCGI_SOCKET_GROUP="www-data"

デフォルトで上のようになっているのを下のように変えた。

# FCGI_APP Variables
FCGI_CHILDREN="1"
FCGI_SOCKET="/var/run/$NAME.socket"
FCGI_USER="www-data"
FCGI_GROUP="www-data"
# Socket owner/group (will default to FCGI_USER/FCGI_GROUP if not defined)
FCGI_SOCKET_OWNER="root"
FCGI_SOCKET_GROUP="root"

変えたのは FCGI_SOCKET_OWNER と FCGI_SOCKET_GROUP だ。

これで設定は終了のはず。fcgiwrap と Nginx を再起動しよう。

takatoh@wplj $ sudo systemctl restart fcgiwrap
takatoh@wplj $ sudo systemctl restart nginx

最後にテスト。/var/www/html ディレクトリにつぎのような hello.cgi ファイルを作って実行権限をつけた。

#!/usr/bin/ruby

print "Content-Type: text/html\n\n"
print ""
print ""
print ""
print "Hello CGI world!"
print ""
print ""

ブラウザでアクセスしてみると、ちゃんと Hello CGI world! と表示されたので OK。

要素の個数を数える

配列内の同じ要素それぞれの個数を数える。

 cf. 要素の個数を数える – 惰力飛行

Ruby でやってみる。Array#group_by が使えそう。

irb(main):001:0> lang = %w(perl python ruby perl python python ruby python ruby lisp)
=> ["perl", "python", "ruby", "perl", "python", "python", "ruby", "python", "ruby", "lisp"]
irb(main):002:0> lang.group_by{|x| x}.map{|k, v| [k, v.size]}.to_h
=> {"perl"=>2, "python"=>4, "ruby"=>3, "lisp"=>1}

いったん [[要素, 個数]] という配列の配列を作って、Array#to_h でハッシュにしている。うまく1行で書けた。