それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

chroot環境下でホスト側のデバイス/devなどを使う。

/dev などをホスト側と共通にする

chroot 環境って、あれこれ試したい時に便利なんですよね。

grub を作りなおしたり、ビルドしたい時にたまに使うんだけど、chroot ってなんかこう、思い通りに行かない時があって。

たとえば イメージディスク中にあるものを触りたい

mount -o loop してイメージディスク中にある、イメージをあれこれ調整したい時があります。

今回は ディスクイメージ中のgrubを触ろうと思った。

ほかにもディスクイメージ中のraid構成をfstab をチェックしたいとかそういうとき、必要でした。

# cd /mnt/new-raid
# mount -t proc proc proc/
# mount -t sysfs sys sys/
# mount -o bind /dev dev/
# mount -t devpts pts dev/pts/
# chroot /mnt/new-raid

コピペ用

sudo mount -t proc proc proc/
sudo mount -t sysfs sys sys/
sudo mount -o bind /dev dev/
sudo mount -t devpts pts dev/pts/

mount してchroot 環境に持っていく

ホスト側(元)からChroot側(マウント先)へmoount を移動するだけでした。

  $ sudo mount /dev/sdXZ /mnt
  $ sudo mount /dev/sdXY /mnt/boot/efi
  $ sudo mount --bind /dev /mnt/dev
  $ sudo mount --bind /dev/pts /mnt/dev/pts
  $ sudo mount --bind /proc /mnt/proc
  $ sudo mount --bind /sys /mnt/sys
  $ modprobe efivars
  $ sudo chroot /mnt

  # grub-install /dev/sdX
  # update-grub
  # exit

  $ sudo umount -lf /mnt/sys
  $ sudo umount -lf /mnt/proc
  $ sudo umount -lf /mnt/dev/pts
  $ sudo umount -lf /mnt/dev
  $ sudo umount -lf /mnt/boot/efi
  $ sudo umount -lf /mnt

/dev/ /proc って単なるマウント

じつは、/procファイルシステムや /dev/ ってたんにマウントなんだって事に気づいて目からうろこだった。

なるほどー!!

Linuxシステムにおけるマウントって便利すぎる概念ですね。

追記 2016-11-05

chroot でナニするんだコレって思いながらメモしましたが、最近 grub-install で頻繁に使ってます。

for i in dev proc sys ; do sudo mount /$i /mnt/target/$i;done
sudo chroot /mnt/target
grub-install ......
exit
for i in dev proc sys ; do sudo umount /mnt/target/$i;done

EFIパーティションを作ってインストールするときにテンプレのように叩いてます。

2022-01-07

誤字修正

参考資料

http://www.webzoit.net/hp/it/internet/homepage/env/cs/bootstrap/multiboot/usb_memory_stick/usb_gpt_grub2/howto/grub2-install/

http://www.ibm.com/developerworks/jp/linux/library/l-lpic1-102-2/

https://wiki.gentoo.org/wiki/GRUB2/ja#.E3.83.96.E3.83.BC.E3.83.88.E3.83.AD.E3.83.BC.E3.83.80.E3.83.BC.E3.81.AE.E3.82.A4.E3.83.B3.E3.82.B9.E3.83.88.E3.83.BC.E3.83.AB

https://wiki.ubuntulinux.jp/UbuntuTips/Others/ReinstallGrub2 https://wiki.ubuntulinux.jp/UbuntuTips/Others/ReinstallGrub2

http://www.nslabs.jp/raid1-convert.rhtml

C++のクラスの作り方の基本サンプル

C++でAnimalを作るとどうなるのかを、基本的なサンプルを使って調べてみた。

クラスの作り方

class Animal {

  protected:
    std::string  voice;
    Animal(){
    }
  public:
    void say(){
       printf( "%s\n",  this->voice.c_str()  );
    }
};

クラスの継承

クラスの継承は、こんな感じになるらしい。

class Cat : public  Animal {
  public : Cat(){
    voice = "にゃ-";
 }
};

インスタンス化とポリモーフィズム

        Animal *ani;
        ani = new Cat;

ふむ。なるほど。

cpp まだよくわからないけど

基本的なことがわかれば少しはかけるようになるかもしれない。

ただ、Win32とかあのへんのCPPは型が多すぎてかんたんには手に負えない。

全てをまとめるとこんな感じ?

ちょっとしたことも書けないとダメだよね・・・・

#include <stdio.h>
#include <string>

class Animal {

  protected:
    std::string  voice;
    Animal(){
    }
  public:
    void say(){
       printf( "%s\n",  this->voice.c_str()  );
    }
};
class Cat : public  Animal {
  public : Cat(){
    voice = "にゃ-";
 }
};
class Dog : public  Animal {
  public : Dog(){
    voice = "わんわん";
 }
};
class Lion : public  Animal {
  public : Lion(){
    voice = "がおー";
 }
};

int main( void ) {

    char str[256];
    int ret ;
  while(1){
    printf("動物(いぬ,ねこ,ライオン)  or exit? ");
    ret = scanf("%s", str);
    if ( ret == EOF ){
      break;
    }
    if (
        strcmp( "ネコ", str ) == 0 ||
        strcmp( "ねこ", str ) == 0 ||
        strcmp( "neko", str ) == 0 ||
        strcmp( "cat", str ) == 0 ||
        strcmp( "Cat", str ) == 0 ||
        strcmp( "CAT", str ) == 0
        )
    {
        Animal *ani;
        ani = new Cat;
    }
    else if (
        strcmp( "いぬ", str ) == 0 ||
        strcmp( "イヌ", str ) == 0 ||
        strcmp( "inu", str ) == 0 ||
        strcmp( "dog", str ) == 0 ||
        strcmp( "Dog", str ) == 0 ||
        strcmp( "DOG", str ) == 0
        )
    {

      Animal *ani;
      ani = new Dog;
      ani->say();
    }
    else if (
        strcmp( "らいおん", str ) == 0 ||
        strcmp( "ライオン", str ) == 0 ||
        strcmp( "lion", str ) == 0 ||
        strcmp( "Lion", str ) == 0 ||
        strcmp( "LION", str ) == 0
        )
    {

      Animal *ani;
      ani = new Lion;
      ani->say();
    }

  }

  printf("\n");
  return 0;
}

binding.pry で止まるのを止める。ループを一気に抜けて強制終了!

binding.pry 便利すぎて書きまくったら地獄だった。

pry で binding.pry を使いまくるのですが、binding.pryをループ中で使うと大変なことになる。

loop {
  binding.pry
}

抜けられない・・・

each/mapなどの loop 中でpry すると抜けらないのですが、これを手軽に強制終了する方法があります。

exit!

ruby といえばビックリマーク・エクスクラメーションマークです。pryでもコレを使えばいいんです。

pry >>> exit! 

これで手軽に抜けられることがわかって。とても快適。

参考資料

binding.pry 使ってる時に、一気にループを抜ける方法 - scramble cadenza

パーティションを含むディスクイメージをloopデバイスにマウントする。

ディスクイメージをマウントする

ただし、ディスクイメージにはパーティションが複数含まれる。

随分と前に、設定してたので、久しぶりにやろうとしてハマったので、改めて書き直し

簡単な方法

mount 時に offset を指定する。でも面倒くさいので、今回は loop0p1 みたいな指定を扱いたかったんですね。

loop デバイスを経由するパーティションを扱う場合

loopデバイスがさらに、パーティションを扱えるように、loop の設定を変える必要があった。

/etc/modprobe.d/loop.conf

modprobe の設定を以下のように追加した。

options loop max_part=63 max_loop=8

modprobe をする

sudo rmmod loop
sudo modprobe loop

losetup で loopデバイスを設定する。

dd したイメージをlosetup でつなぐ

cd 
sudo losetup -f out.img

結果を確認。

losetup
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE
/dev/loop0         0      0         0  0 /home/takuya/out.img

パーティションを確認

これで、パーティションを見えるし扱えるようになる。

$ ls /dev/loop0*
/dev/loop0  /dev/loop0p1  /dev/loop0p2

もし見えないときは、明示的にパーティション認識のオプションを付けてあげる。

losetup -P  out.img

なぜか、明示的な- P が必要な時があって。これをつけないとパーティションを認識されないことがあるようです。違いがよくわかってない。

mkfs でファイルシステムを作ることも出来る。

$sudo mkfs.ext4 /dev/loop1p2
mke2fs 1.42.12 (29-Aug-2014)
/dev/loop1p2 contains a ext4 file system
    created on Fri Jul 15 13:45:18 2016
Proceed anyway? (y,n) y # テストなので強制上書き
Discarding device blocks: done
Creating filesystem with 91136 1k blocks and 22848 inodes
Filesystem UUID: 8df6ad88-747b-4744-b782-22098f3a5edd
Superblock backups stored on blocks:
    8193, 24577, 40961, 57345, 73729

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

マウントも出来る

sudo mount /dev/loop1p2 ./mnt

マウントできるので随分楽

パーティションを指定してマウントできる。パーティションを指定出来るようになるのでほんとうに便利。

もっと良いやり方ないのかな。

ハードディスクのバックアップのマウントは結構頻出の作業なので、最初からloopデバイスで扱えればいいんだけどmodprobe する以外にないのかなぁ。

2020-11-16

-P オプションに言及

関連資料

http://takuya-1st.hatenablog.jp/entry/2014/12/12/202139

参考資料

https://stackoverflow.com/questions/37227233/having-losetup-read-the-partition-table

PowerDNSでDNSサーバーを作る。

PowerDNS 通称 pdns が便利。

pdns が便利そう。なので使ってみてる。

Power DNS で出来ること

PowerDNSはDNSで出来ることが一通り全てできる。

  • 権威サーバー
  • ミラーサーバー
  • キャッシュサーバー

これらをちゃんと実装している。

さらに、コンテンツサーバーについては、ゾーンとレコードをSQLバックエンドに放り込むことが出来る。

SQLでバックエンドに放り込めるだけでもすごいのに

  • bind
  • mydns
  • tinydns

これらのファイル形式に対応してて、レコードを様々な形で持つことが出来る。いいな、これ。

あと、LDAPに格納したレコードも使えるらしい。無敵か。

さらにHTTP機能がある。

WEBサーバー機能があって、PowerDNSのモニタリングができて、HTTP APIまでもがついてくる。

インストールして使ってみる。

インストールに必要なものと、インストールした環境

こんかいはCentOSを引っ張り出してきた。

準備 : 1

DNS を扱うのでDNSを扱うのに必要な dig コマンドを用意しておく

sudo yum install bind-utils

dig いれたら準備段階として dig の結果とネットワーク疎通を見ておく

dig +short t.co

その他に bash-completion nmap vim-enhanced などを入れた

準備 : mysql をインストール

mysql サーバーにDNSレコードを作ることにするので、MySQLを準備してくる

sudo yum install mysql-community-server

mysql のroot の基本設定をしておく

sudo mysql_secure_installation

MySQLにユーザーとデータベースとテーブルを準備する。

MySQLをインストールしたので、ユーザーを作る

create user 'pdns'@'%' identified by 'password'; 

MySQL に pdns のデータベースを作る。

もう管理が面倒くさいのでユーザー名と同じにした。

create database pdns;

pdns ユーザーにデータベースの権限をGRANTする

grant all on pdns.* to 'pdns'@'%'

一通りがおわったら、flush しておく

flush privileges;

pdns ユーザーでログインしてみる

mysql -u pdns -p 

ログインできたら権限をチェックしておく

use pdns

データベースにテーブルを作る

CREATE TABLE domains (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255) NOT NULL,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INT DEFAULT NULL,
  type                  VARCHAR(6) NOT NULL,
  notified_serial       INT DEFAULT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX name_index ON domains(name);


CREATE TABLE records (
  id                    INT AUTO_INCREMENT,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(64000) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  change_date           INT DEFAULT NULL,
  disabled              TINYINT(1) DEFAULT 0,
  ordername             VARCHAR(255) BINARY DEFAULT NULL,
  auth                  TINYINT(1) DEFAULT 1,
  PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername);

テーブル構造の詳細は公式ドキュメントにある。今回は、domains/zonesさえアレば動くので、これだけ持ってきておいた。

っていうか。テーブル構造は全然うるさくない。自分で適当なテーブルで作ってもいいし、名前をマッピングしておけばちゃんと動くらしい。好きなカラム追加しても良い。緩さがいいよな。使いやすい。

MySQL以外にSQLitePostgreSQLを使う時もほぼ同じだと思う。

pdns のインストール

準備が終わったので、pdnsをインストールすることにする。

sudo yum install pdns

さらに、mysql をバックエンドに使うので mysql-backend をインストールしておく。

sudo yum install pdns-backend-mysql

pdns の設定: mysql

mysql を使うように設定するので

launch=gmysql
gmysql-host=127.0.0.1
gmysql-user=pdns
gmysql-dbname=pdns
gmysql-password=XXXXXXXXXXX

これを書いた。

その後

service pdns restart

接続の確認

nmap localhost 

53 番が空くことを確認

dig example.com @127.0.0.1

とりあえず接続して応答は返ってくることを確認。もちろんレコードは何もない。

レコードの追加

サーバーが起動したのでレコードを追加する。

mysql のデータベースにDNSゾーンとDNSレコードとしてテーブルにレコードを追加する。

mysql -p -u pdns pdns 

接続できたら、SQLで流し込む。

INSERT INTO domains (name, type) values ('example.com', 'NATIVE');
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'example.com','localhost ahu@ds9a.nl 1','SOA',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'example.com','dns-us1.powerdns.net','NS',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'example.com','dns-eu1.powerdns.net','NS',86400,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'www.example.com','192.0.2.10','A',120,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'mail.example.com','192.0.2.12','A',120,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'localhost.example.com','127.0.0.1','A',120,NULL);
INSERT INTO records (domain_id, name, content, type,ttl,prio)
                      VALUES (1,'example.com','mail.example.com','MX',120,25);

これで、example.com のNSにwwwを登録することが出来た。

問合せてみる

これで完成なので、早速問合せてみる。

 dig example.com any  @127.0.0.1

; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7_2.3 <<>> example.com any @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36246
;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 2
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;example.com.           IN  ANY

;; ANSWER SECTION:
example.com.        120 IN  MX  25 mail.example.com.
example.com.        86400   IN  NS  dns-eu1.powerdns.net.
example.com.        86400   IN  SOA localhost. ahu.ds9a.nl. 1 10800 3600 604800 3600
example.com.        86400   IN  NS  dns-us1.powerdns.net.

;; ADDITIONAL SECTION:
mail.example.com.   120 IN  A   192.0.2.12

;; Query time: 6 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Jul 14 20:15:11 JST 2016
;; MSG SIZE  rcvd: 189

うん、ちゃんと応答する。

HTTP のAPIを準備する。

ちゃんと応答したので、手軽に更新するために curl から叩いたら便利だよね。

MySQLをレコード使えば更新できるので、RailsMySQL更新するAPIを作っても良いんだけど、最初からあるなら、ソレ使ったほうが楽だよね。

HTTPのAPIを準備する。

webserver=yes
webserver-address=127.0.0.1
webserver-port=80

##
experimental-json-interface=yes
experimental-api-key=changeme
experimental-logfile=/var/log/pdns.log

とりあえず、WEBSERVERをつけたら、状態をモニタリング出来るようになってる。

json-interfaceをオンにしたら、WEB-APIが解放される。楽ね。

API を叩いてみる。

curl -H 'X-API-Key: changeme' http://127.0.0.1:80/servers/localhost/zones 

これだけ!あーカンタン。幸せ。

APIはなんでもできるから管理をしっかりしないといけない。

まとめ

最近、ダイナミックDNSが軒並みシンドイ事になってきた。

ダイナミックDNSスパマーがIPをコロコロ変えるのに利用されたり、クラウドの普及で需要が減ったりしてる。そのため何処も収益化に必死になってきた。

たいへん不自由なので、自分でDNSサーバを作ったほうが良いと判断しました。

DNSの管理は大変だし、乗っ取られたら大変なので注意が必要だし、いろいろと考えることが多い。

でもDNSを自分で管理できるととても便利だし、クラウドが手軽に使えるのにDNSを自由に使えない。結局は、DNSが使い勝手や手軽さのボトルネックになってしまう。

お名前.com のようにTTLが長すぎて変更に時間がかかったり value-domain のように変な挙動する dns だったり。もうね。

GoogleAmazonのサービスはちゃんとDNSが基本セットに入ってるんだよ。DNS手軽に使えないのものをクラウドと読んでほしくないというか。

参考資料

https://doc.powerdns.com/3/httpapi/README/#powerdns-api

https://doc.powerdns.com/3/authoritative/installation/

http://www.slideshare.net/mzdakr/powerdns

シェルの組込関数(ビルトイン)の使い方manを見る方法

Shell Built'in Functions のヘルプを見たい

Man ページだと見れないんだよ。man だと。

man history  ## 見れない。

man で見ると、shell builtins に飛ばされる。そしてオプションを見られない。

help を使う。

 help history

help コマンドを使う。

シェル組み込み関数を見るには help を使う

help のhelp を見る

ヘルプの使い方をとりえず見てみるのがいいですね man man するのと同じ感覚です。

takuya@~/Desktop$ help help
help: help [-dms] [pattern ...]
    組み込みコマンドの情報を表示します。

    組み込みコマンドに関する簡潔な要約を表示します。もし PATTERN が
    指定された場合は、PATTERN に一致する全てのコマンドに対する詳細な
    ヘルプを表示します。それ以外はヘルプトピックを表示します。

    オプション:
      -d    各ヘルプトピックに対して短い説明を出力します
      -m    使用法を擬似的な man ページ形式で表示します
      -s    一致した各トピックに対して簡単な使用法のみを表示します
        PATTERN

    引数:
      PATTERN   ヘルプトピックを指定するパターン

    終了ステータス:
    PATTERN が見つからないか無効なオプションが与えられない限り成功を返します。

history の使い方を見てみる。

history コマンドって使わない日が無いですよね。history -a なんて、bashrc に書いてて秒間ですげー数が実行されているし。

history: history [-c] [-d offset] [n] または history -anrw [filename] または history -ps arg [arg...]
    ヒストリ一覧を表示または操作します。

    行番号をつけてヒストリを表示します。操作した各項目には前に`*'が付きます。
    引数 N がある場合は最後の N 個の項目のみを表示します。

    オプション:
      -c    ヒストリ一覧から全ての項目を削除します。
      -d offset OFFSET 番目のヒストリ項目を削除します。

      -a    このセッションからヒストリファイルに行を追加します
      -n    ヒストリファイルからまだ読み込まれていない行を全て読み込みます
      -r    ヒストリファイルを読み込み、内容をヒストリ一覧に追加します
      -w    現在のヒストリをヒストリファイルに書き込みます。そしてそれらを
        ヒストリ一覧に追加します

      -p    各 ARG に対してヒストリ展開を実行し、結果をヒストリ一覧に追加し
        しないで表示します
      -s    ARG を単一の項目としてヒストリ一覧に追加します

    FILENAME を与えた場合、FILENAME がヒストリファイルをして使用されます。それが
    無く、$HISTFILE に値がある場合その値が使用されます。そうでなければ
    ~/.bash_history が使用されます。

    もし $HISTTIMEFORMAT 変数が設定され、NULL で無ければ、strftime(3) の書式
    文字列として各ヒストリ項目の時刻を表示する際に使用されます。それ以外は
    時刻は表示されません。

    終了ステータス:
    無効なオプションが与えられるかエラーが発生しない限り成功を返します。

history コマンドの使い方がわかりますね。

if / else の使い方も見れる

help if
if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
    条件に従ってコマンドを実行します。

    `if COMMANDS' を実行します。この終了ステータスが 0 の場合、`then COMMANDS'
    を実行します。そうでない場合は、各 `elif COMMANDS' を順番に実行し、その
    終了ステータスが 0 の場合に、関連した `then COMMANDS' を実行し、if 文が
    完了します。それ以外の場合、 `else COMMANDS' が存在する場合には実行され
    ます。文全体の終了ステータスは、最後に実行したコマンドの終了ステータスか、
    または、テストした条件に true となるものが無い場合は 0 です。

    終了ステータス:
    最後に実行したコマンドの終了ステータスを返します。

シェルの関数や機能を見るにはhelp

man ではなくて、help を使います。

参考資料

http://unix.stackexchange.com/questions/18087/can-i-get-individual-man-pages-for-the-bash-builtin-commands

Mission Control :この遅すぎる機能をなんとかしたい

ミッションコントロールの切替が遅い

ミッションコントロール、遅くないですか。とくにWindow一覧。遅いんですよ思考が止まる

WindowsのAlt+Tabの機敏な動きが本当に懐かしい。

というわけで、調べてたら出てきた

defaults write com.apple.dock expose-animation-duration -float 0.05; killall Dock

これで、トラックパッドを上にしたにスライドした時の動き、また、F3をおした時の動きがキビキビしてて快適ですね。

元に戻すには

defaults delete com.apple.dock expose-animation-duration; killall Dock

ただ一度設定して慣れてしまうと、もう二度と元に戻れない体になると思います。

参考資料

http://www.makeuseof.com/tag/use-multiple-desktops-mac-os-x/

URLに含まれる数字のIDを隠したい時にhashid を使うと楽

Id丸見えっていろいろヤバイですよね。

とくに連番だと、連番のインクリメンツだと、つい出来心で連番を踏んでみたくなりますよね。

あと、マナー悪いクローラーがババっと来そう。

IDを隠す

IDを隠すにはmd5crc / sha で hexdigest すりゃイイんだけど、文字数が多すぎて見栄えが悪い。

URL 短縮系のサイトでやってる bitlyや google shorten と同じようなことが出来ないか考えてみたけど。

ちょっとアルゴリズムが思いつかなかったので、ライブラリを探してみました。

HashID で 数字のIDを隠す

hashids.org

数字を「配列」にすると、アルファベットでエンコードしてくれます。

各種言語で書かれているのでいいですね。

BASE64 だと辛いし BASE58 くらいでいいんですけどね。

BaseXX で文字を符号化する感じですね。Saltが使えるので便利です。

サンプル

hashids = Hashids.new "this is my salt"
id = 123
id = id.to_s
id = id.split //
id = id.map{|e| e.to_i }
id = hashids.encode(*id) ## ここで連番の数字を配列にしたものを渡す。
numbers = hashids.decode(id)

楽しいですね。

アルファベットだけでIDドコまで行けるか

6桁もアレば十分ですね

(26*2)6 =19,770,609,664

余裕見ても8桁ですかね。

php imagick でページ指定を簡略化して pdf を高速に処理する

ruby で出来たので、php でもできるかなと思って調べてみたら。

<?php
<?php 
$imagick = new Imagick(); 
$imagick->readImage('myfile.pdf[0]'); 
$imagick = $imagick->flattenImages(); 
$imagick->writeImage('pageone.jpg');
?> 

php のマニュアルにバッチリなコードが書いてあった。

でも動かすの大変だった。

php-magick で pdf を処理するには gsが必要なんだが、gs が入っててもコマンドラインオプションがあわなくて起動しなくて、解決方法を探してる間に時間が過ぎてしまった。

apt で入れた php-magick ではpdf が扱えない。

モジュールの依存関係、とくにphp はちょっとめんどくさいのでpecl 経由入れて解決させるしかないと思うけど、apt で使えないのは不便極まりないな。。。

エラー

PHP Fatal error:  Uncaught exception 'ImagickException' with message 'FailedToExecuteCommand `"gs" -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 "-sDEVICE=pngalpha" -dTextAlphaBits=4 -dGraphicsAlphaBits=4 "-r72x72"  "-sOutputFile=/tmp/magick-31919s74grPtp69Oe%d" "-f/tmp/magick-31919cbojU1OZVN2V" "-f/tmp/magick-31919lAaYk4BJvvgD"' (-1) @ error/delegate.c/ExternalDelegateCommand/461' in /home/takuya/test.php:7
Stack trace:
#0 /home/takuya/test.php(7): Imagick->readimage('out.pdf')
#1 {main}
  thrown in /home/takuya/test.php on line 7

2016-11-10

debianphp のバージョンアップをしたので無事動かすことが出来た。

PDFをページ数指定で高速に読み込む。

PDF 読み込み遅んだよ!

PDFってなんでこんなに無駄にページ数多いの。ってなりなす。PDFをMagickで読み込んだら遅いのなんのって。

require 'Rmagick'
name = 'out.pdf'
im = Magick::Image.read(name) # めっちゃ遅い
im.esize #=> 300

PDFが枚数多いとものすごく時間かかるんですよ。メモリも食うしたまったもんじゃない。PDF遅いってなる。

早く読む方法を見つけた

高速に処理できる。なんと、ページ数を指定して読み込むことが出来る。

require 'Rmagick'
name = 'out.pdf[0]'
im = Magick::Image.read(name) # 超早い!
im.esize #=> 1

PDFの各ページに配列のインデックスを扱うようにアクセスできる。便利。

速度を測定してみる。

out.pdf を作る

for i in {1..10}; cp src.png $i.png ; done 
convert *.png out.pdf

とりあえず、10枚程度のPNGを放り込んだPDFで実験する。

速い!

takuya@~/Desktop$ time ruby  while_pdf.rb

real    0m4.624s
user    0m4.132s
sys 0m0.226s
takuya@~/Desktop$ time ruby  one_page.rb

real    0m1.331s
user    0m1.211s
sys 0m0.047s

10枚のウチ1枚だから10%とまでは行かないけれど、4倍くらい速いかった。強い。

参考資料

RMagick で PDF を jpg や png に変換する - happy lie, happy life

cron/crontab で 複数行の実行には波括弧 {} を使う

crontab に書ける機能少なく無いですか?

crontab の限界ってあるんですよ。波括弧使えたらもう少し楽に記述できるのに。

でも出来ないんですよ curly brace が使えない。

使えたらイイのにと思って調べたらとてもカンタンな解決方法が見つかった

やりかた

  • cron で起動するシェルを bashにする
  • bashの機能を用いて複数コマンドを実行する
  • 方法1 波括弧 { cmd_01; cmd_02;cmd_03;}
  • 方法2 && に依る実行 cmd_01 && cmd_02 && cmd_03

cd などで、ワーキングディレクトリを変えて、crontab が実行されるディレクトリを変えるときに便利など。

使う方法

cron のシェルをbash にする。

cron は SHELL=sh で動作しているので SHELL=bash に切り替えてあげる。

記述例

crontab

SHELL=bash

0 12 * * * *  { cd /var/www ; /var/scripts/my_command; } > /dev/null

これで、穏やかにシェルスクリプト実行をCRONで実行できそう.

例えば日付と曜日を条件に入れるなどの処理を書けそうですね。

波括弧で複数行を書くかわりが出来ますね

波括弧を使わない解決方法

スクリプトの実行ディレクトリを書き換える程度であれば、&& で繋いであげれば実現可能

SHELL=sh

0 12 * * * *  cd /var/www && /var/scripts/my_command  

これでも出来ますけど、波括弧使えたほうが楽ですね。

注意点

SHELL=bash
SHELL=sh

とサンプルで指定してるのには意味があって、{ command; command; }はシェルの機能なので、使ってるシェルに依っては動作しなかったりするので注意が必要。私は bash なので特に問題なく動作している。

参考資料

http://stackoverflow.com/questions/24488962/why-cant-i-use-curly-brackets-in-crontab

2018-04-02

セミコロンの抜け修正。あと細かい記述を改訂

crontab で 複数行に渡る、cron ジョブを記述するには

こちらを参照

詳しい内容はこちらに書きました。

cron/crontab で 複数行の実行には波括弧 {} を使う - それマグで!

複数行のcrontab コマンドってかけないのかな

調べたけど、かけないことがわかった。

man によると

The ``sixth'' field (the rest of the line) specifies the command to be run.  The entire command portion of the line, up to a newline or % character, will
       be executed by /bin/sh or by the shell specified in the SHELL variable of the crontab file.  Percent-signs (%) in the command, unless escaped with  back‐
       slash  (\),  will  be  changed  into newline characters, and all data after the first % will be sent to the command as standard input. There is no way to
       split a single command line onto multiple lines, like the shell's trailing "\".

記号 % は改行として、sh に渡されるが、バックスラッシュを渡すようなものはない。って書いてありました。

残念。

ぐぐったら minix のcrontab は出来るっぽいけど、Linuxは無理ですね。

man に書いてないんだから、試すまでもないか

追記:できるっぽいので別エントリに

SlideShareを自動ブラウジングしてJPGからPDFを取出す。

以前作ったSlideShareの保存スクリプトを改良した

Prawn使ってたので、どうしてもA4サイズになっちゃって困ってたので、JPEGをそのままPDFにすることにした

#!/usr/bin/env ruby 
#

require 'rmagick'
require 'mechanize'
require 'open-uri'
require 'pry'

url = ARGV.shift
m = Mechanize.new
m.user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36"
target_size = 'full'

begin

  m.get  url

  xpath = "//img[@class=\"slide_image\"]/@data-#{target_size}"
  # binding.pry
  list =  m.page.search(xpath).map{|e| e.text() }

  f_name = m.page.title.to_s + '.pdf'

  imgs = Magick:: ImageList.new
  list.each{|image_url|
    sub_m = m.clone
    sub_m.get(  image_url   )
    imgs.push( Magick::Image.from_blob( sub_m.page.body )[0] ) 
  }
  imgs.write(f_name)
  


rescue => e
  puts e 
  if target_size == 'full' then 
    target_size ='normal'
    retry
  end
   if target_size == 'normal' then 
     target_size = 'small'
     retry
   end
  raise e 
end

使い方

スクリプトを保存して、URLを引数に起動するだけ。

 ./slideshare~download.rb http://www.slideshare.net/XXX/YYY

SlideShare を見てるだけです。

繰り返しになりますが、Mechanizeはブラウザです。

LTEの回線パケット上限が逼迫するので、仕方なくオフラインキャッシュを作りました。

SlideShareは通信無駄に多過ぎなんですよ!

関連資料

takuya-1st.hatenablog.jp

ruby imageMacick RMagick で 画像からPDFを作る

rmagick で PDFを作るのがちょっと手間だったのでメモ

rubyimagemagick 実装である rmagick が有ります。

RMagick を使えば、magick 関連が使えるbindings で重宝する。

無論、Rubyからmagickを使うには、convert/mogrifyのシェル呼び出しで済むのだ。

だけど、シェル呼び出しだと一時ファイルを作ったり消したりになるので、デバッグが一手間増えるんですよ。

シェル呼び出しは確実なんだけど、パパパパパパッと試したい時に不便だったりする。

RmagicでPDF作成

シェル呼び出しだとこれだけで済むのですが

convert sample1.jpg sample2.jpg out.pdf 

これと同じをRMagickでやるには

#!/usr/bin/env ruby

require 'RMagick'

r = Magick::ImageList.new()

r.push( Magick::Image.read( 'sample1.jpg')[0] )
r.push( Magick::Image.read( 'sample2.jpg')[0] )

r.write('out.pdf')

このようになる。

ポイントはImageListもImageも配列であるということ

ruby から処理を行うと Magick::ImageMagick::ImageList のどちらも、配列風のオブジェクトであるという点にある。

そのため、push しようとしても「配列なんていられるか!バーカ!」と rmagickに怒られることになる。

/Users/takuya/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rmagick-2.15.4/lib/rmagick_internal.rb:1291:in `is_an_image': Magick::Image required (Array given) (ArgumentError)
    from /Users/takuya/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rmagick-2.15.4/lib/rmagick_internal.rb:1729:in `block in push'
    from /Users/takuya/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rmagick-2.15.4/lib/rmagick_internal.rb:1728:in `each'
    from /Users/takuya/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rmagick-2.15.4/lib/rmagick_internal.rb:1728:in `push'
    from rmagick-test.rb:9:in `<main>'

そのため、Imageから先頭を取出す(どうせ要素数1なのに面倒くさい)

image_list = Magick::ImageList.new()

i = Magick::Image.read( 'sample1.jpg')[0] 
image_list.push i

このような一手間増える処理が必要だった。

これを行うことで、JPEGだけから構成されるPDFを作成することが出来る。

画像JPEG だけから構成されるPDFはZipファイルをより管理が容易だし、メールなどで送付する際にも大変便利。

setup.py install したパッケージを消す

setup.py で インストールしたパッケージを消すには

python setup.py install

よく出てきます。よく使います。でも間違ってインストールした時にどうするの

pip list 
pip uninstall パッケージ名

pip 使えば出てきます。

pip がない場合は入れて下さい.