アメブロから更新時に一部を抽出して通知する

私はかなりの頻度でアメブロのとあるブログエントリの更新をチェックしているのだが、ほとんどの場合重要な情報はそのごく一部である。 しかし、アメブロにスマホでアクセスするたびに、くだらない広告や関心のないTV出演者の話に通信量が割かれ、時間も割かれることを大変に腹立たしく思っていた。

そこで、PCからページを取得し、スクレイピングする方針で考えたものである。 スクレイピングした内容はDiscordで通知する。

具体的な内容は差し障るので抽象化する。

コード

解説

「アメブロから」という難しさ

ユーティリティスクリプトでは「問題自体を可能な限り引き下げ、ゆるく解決する」のが重要である。 開発コストを下げるべきなのだ。

amebloの内容を見ると、独特なエディタから生成されるものだけに、エディタ上での記述はちょっとした違いで大幅に異なるHTMLコンテンツが生成される。 私は投稿者とは連携していないので、ちょっとした記述のブレで抽出すべきコンテンツを見失う可能性がある。

生成されるタグと比べると見た目上の違いは少ない。 言い回しの違いや、スペースの有無程度である。

そこで、w3mを活用する。w3m-dumpオプションは、見た目に近い形でレンダリングした上でテキストにして出力する。 アメブロの執筆者はエディタ上の見た目で書いている可能性が高く、このようにレンダリングしてしまったほうがブレは抑えられる。

その中から情報を抽出する。 ある正規表現に一致する行、またはある正規表現の範囲をアドレスとして出力すれば多くの場合は十分だろう。

続くエントリや広告のゴミ値を拾ってしまわないように、エントリ終了時にはSedを終了させる。 多くの場合、/^AD$/で良いかと思うが、/いいね!した人.*リブログ/というパターンも考えられる。

例えばおやつ工房 ひびのやをdumpした結果からメニューを取得するとすれば、筆者はメニューの表記前に本日のメニューは、という行を置く習慣があるようで、またブログの締めとして本日もよろしくお願いいたしますを置いている。 これに基づけば

メニューそのものを抽出するならば、で囲むようにしているようなので、そこに限定しても良い。

しかし、どうもこれは項目であるようだから、メニューとしては成立しないかもしれない。 項目終わりは空行を空けているようなので、この項目から空行まで抽出してみよう。 一応、スペースを入れていた場合にも対応しておく。

しかし次のようなケースが発見された。

*天然酵母パン*
⚫︎食パン1斤→焼き上がりました
自家製カボス酵母、春よ恋使用。
乳製品不使用のシンプルなパンです。国産のお粉の素朴な風味をそのまま味わっていただけます。 

⚫︎レーズンとくるみのパン→焼き上がりました
自家製レモン酵母、春よ恋使用。

⚫︎カンパーニュ →焼き上がりました
自家製ラ・フランス酵母使用。
ライ麦粉と全粒粉を配合。

なので、項目開始だけでなく小項目開始も受け入れることにする。

結果次のように抽出できた。

*マフィン*
プレーン⬇
ポップシュガーに戻しました。
きのこみたい

*シフォンケーキ*
プレーン小

*天然酵母パン*
⚫食パン1斤→焼き上がりました
自家製カボス酵母、春よ恋使用。
乳製品不使用のシンプルなパンです。国産のお粉の素朴な風味をそのまま味わっていた
だけます。 

⚫レーズンとくるみのパン→焼き上がりました
自家製レモン酵母、春よ恋使用。

⚫カンパーニュ →焼き上がりました
自家製ラ・フランス酵母使用。
ライ麦粉と全粒粉を配合。

日付も入れるならば、投稿時タイムスタンプの行を追加しよう。

/[0-9]\{4\}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/ p
/*.**\|^⚫/, /^\s*$/ p
/^AD$/ q
//いいね!した人.*リブログ/ q

なお、サンプルとして「ameblo 本日のメニュー」で検索して最上位に出たおやつ工房 ひびのやさんを使わせていただいた。 埼玉県越谷市にあり、東京スカイツリーライン大袋駅、せんげん台駅が最寄りとなる、パンとお菓子のお店だそうである。11時から18時営業、金曜日休業。

更新されたかどうか

単純に考えれば取得したページをteeで保存して比較したいところだが、 広告などランダムに生成される部分があるため、これは失敗する。

amebloはDateヘッダを含み、Content-Lengthヘッダを含まないため1、ヘッダ部分だけ保存するという方法もある。 curl--dump-headerを使用し、内容は捨てるという方法が良いだろう。

もうひとつはそもそも抽出した内容を比較するという方法がある。 リクエスト回数が減るため合理的だ。

いずれにせよ、前回取得分をローテーションして、今回取得分と比較する。

diff -q ~/.cache/someameblo/header ~/.cache/someameblo/header.prev > /dev/null

通知する

今回の場合、Discordのwebhookを使用している。

Twitterという手もある。ここではtwを使用する。 セルフDMの送信自体は可能だが、Twitter webや公式アプリでは確認/通知はなくなっている。

LINE Notifyという方法もある。

メールが配送できる環境からであればMMS宛というのも悪くない

ジョブスケジューラによる定期実行

スクリプトを書いても手動実行するというのはちょっとださい。 ジョブスケジューラによって自動的に実行してほしいところだ。

anacronあたりを使うのが妥当なのだろうけれど、私はSystemd Timer (User)を使用することにした。

まずはserviceユニットを書く。 ここでは~/.config/systemd/someameblo.serviceを作成している。

[Unit]
Description=Get ameblo entry.

[Service]
Type=simple
ExecStart=/bin/zsh "/home/aki/opt/someameblo/getamebloentry.zsh"

[Install]
WantedBy=default.target

次に対応したtimerユニットを書く。 ここでは起動から10分で初回実行し、それ以降4時間ごとに起動している。

[Unit]
Description=Get ameblo entry.

[Timer]
OnBootSec=10min
OnUnitActiveSec=4hour
Unit=someameblo.service

[Install]
WantedBy=timers.target

ユーザーユニットをリロードする。

タイマーを起動&有効化

「ゆるい解決」について

このようなユーティリティは基本的に人間を補助するものである。 オートメーションに属するものの、このユーテリティが人間に無断で何かを決断するわけではない。 また、人間にとってこのユーテリティによって与えられる唯一絶対の情報というわけでもない。

できるだけ精度が高ければ嬉しいけれども、もしも例外的ケース(例えば著者が普段とはまるで違う書き方をした場合など)においてうまく機能しなかったとしたならば、それは単に普通にブログを見に行けばいいだけである。 例外的ケースにおいて問題が発生しないように設計し、成功しない可能性については許容する。

同様に情報が常に完璧なフォーマットであることを求める必要もない。 ソースが完璧なフォーマットで書かれない可能性は高いので、完璧を求めることは不毛である可能性が高い。 これもまた、不十分な情報となったときには自らブログを見に行く選択肢もあるのだ。

ただし、十分に機能させるためには、抽出条件はfalse negative(取りこぼしがある)よりはfalse positive(ゴミがはいる)ほうが好ましい。 ゴミがあっても無視すればいいが、取りこぼしがあると必要な情報が損なわれる可能性があるためだ。

プログラミングはプログラムを書くことよりも、どのようにアプローチするかということのほうが重要な場合は少なくない。 プログラミング初級者のうちは設計をないがしろにしがちだが、プログラムの出来や困難性はだいたい設計によって決まる。 今回の主題も、正攻法ではかなり難しい条件を、考え方を変えることで簡単な問題に変換しているということだ。


  1. あるいはcurlがContent-Lengthを含まないのかもしれない

シェルスクリプト Forced Todo

Forced Todoはなかなか進まない進捗に対して、一定の時間でTODOを読み上げ、進捗を迫るものである。 あまりに進捗しないので作ってみた。

Gist

Systray

Notifyを開始するとSystrayに常駐し、Systrayから終了できるようにした。

Systrayへの常駐はMailDeliver2同様、yadを使うようにした。

notifypid=$!

yad --notification --image=player-time --text="TODO NOW!" --command="yad --text-info --timeout=15 --title=TODO --width 300 --height 300 --filename=$HOME/.yek/forcedtodo/todotext" --menu='Edit!'"$task_editor $HOME/.yek/forcedtodo/todotext"'|Quit!quit' --tooptip="Immidiately Task" 
kill $!

Yadのオプションについて分析してみよう。

オプション 意味
--notification Systrayに常駐する
--image=player-time Systrayのアイコンを選択
--text="TODO NOW!" Notificationの名前(ポップアップに影響)
--command 左クリック時の実行コマンド
--menu 右クリックメニュー。項目名とコマンドを!で分けている。quitで終了

ネストされているYadは次の通り

オプション 意味
--text-info テキストダイアログ
--timeout=15 15秒でダイアログが閉じる
--title=TODO ウィンドウタイトル
--width 300 ウィンドウの幅
--height 300 ウィンドウの高さ
--filename=... テキストの内容となるファイル

Zenityとは結構な違いがあることに注意。

Yadは通常ブロックするため、Yadが終了しなければ戻らない。 そのため、Notifyするためのプログラムをサブシェルでバックグラウンドプロセスとして実行し、そのプロセスID($!)を取得し、Yadが戻ったらKillすれば良い、ということになる。

Notify (Open JTalk)

Notifyは単一のプロセスIDを振るためにサブシェルで実行し、バックグラウンドプロセスとすることでループさせる。

(
  while sleep $notify_interval
  do
  perl -pe 's/\n/。/g' ~/.yek/forcedtodo/todotext | open_jtalk "$jtalk_tuning[@]" -m $hts_voicefile -x $mecab_dictdir -ow $jtalk_tmpfile
  play $jtalk_tmpfile >| /dev/null &|
  notify-send -t $notify_time "TODO" "$(<$HOME/.yek/forcedtodo/todotext)"
  done
) &

単純なsleepによるループである。

基本的な手順としては

  1. Open JTalkで音声ファイルを作成
  2. 合成した音声ファイルをバックグラウンドプロセスとして再生
  3. Notify Sendを使って通知に表示

となる。

ここで大きな情報はOpen JTalkだろう。

Open JTalkはスピーチシンセサイザーだが、eSpeak, SVOX Pico, Festivalといったソフトウェアと比べて非常に自然で、日本語に対応している。 ただし、かなり複雑なソフトウェアだ。

Manjaro(Arch)では、AURにあるopen-jtalkパッケージをインストールする。

NAIST JdicファイルはAURにも存在していないが、辞書ディレクトリとして/usr/share/open-jtalk/dic/を使用することができる。精度は異なるかもしれない。

NAIST JDicをインストールするためには、mecabをインストールし、さらにNaist JDicで/usr/libexecが指定されている部分を/usr/libに変更しなくてはいけない。

女声を使用するためにはopen-jtalk-voice-meiをインストールする。

Open JTalkは非常に自然で、Twitterの読み上げにも使えそうだが(twとの組み合わせがいいだろう)、複数行を扱うことができないため、Perlで変換している。 また、Open JTalkは標準出力に吐くことはできないようだ。

シェルスクリプトで並列実行制御を行う

シェルスクリプトを書く場合において、処理を順次おこないたいことは多いはずだ。
多くのディレクトリや処理のリストなど、順に処理を適用していくケース。

まず、この設計だが、「ある特定の場所でしか実行されないスクリプト」はnoexecが指定されていない限りはそのルートに設置すべきだと思う。
対象ディレクトリごとにスクリプトが異なってくる場合はその対象ディレクトリごとにスクリプトを設置する。
もしそうでなく、その違いがディレクトリのパス自体に含まれるのであれば、単純に対象ディレクトリに空のドットファイルを置けばよいと思う。

例えば、…/.somescriptを実行するのであれば、トップに

for i in **/.somescript
do
  ( cd ${i:h}; zsh .somescript )
done

のようなスクリプトを書けば良い。Gitが「Gitを実行するディレクトリがリポジトリになる」という仕様なので、このように方法にらも随分なれてしまった。
もし、パス要素自体が重要になるのであれば、

for i in **/.target
do
  fooscript ${${i:h}:t}
done

という方法も考えられる。

それは良いとして、その各処理が時間がかかるとしたらどうだろう?
これは別にディレクトリ単位であった場合に限らない。テキストファイルに1行1エントリ形式で書いて読みながら処理する場合も同様だ。

別に最初からターゲットをグループ分けしてもいいし、xargsを使って3つずつ実行する、といったことで並列化することもできる。
だが、できれば常に3スレッドで実行する、といったモデルのほうが好ましいのではなかろうか。

これを実現するためにシェルスクリプトで並列実行制御したいのだが、残念ながらこれはかなり難しい。
Zshでもzthreadがあるような話も目にするのだが、まぁ実際はそうもいかなそうだ。

並列実行の難しさは、「同時アクセス」にある。あるリソースに同時にアクセスした場合、いろいろな形でおかしなことになる。
ファイルデスクリプタを共有すれば良いのではないかと考えたのだが、

worker() {
  workern=$1
  typeset val
  while read val
  do
    print "Worker $workern: $val"
  done
}

print -l {1..100} | (
  for n in {1..3}
  do
    worker $n &
  done
)

wait

結局read時に同時アクセスするとおかしな値を取ることになり(空文字列だったり、複数行がぐちゃぐちゃに混ざったものだったりする)、ちゃんと動作しない。

なお、ここでのポイントをまとめておこう。

  • ()はfork子プロセスを生成し、子プロセスで実行する。
  • この子プロセスに渡されたパイプは、子プロセス自体の標準入力として受け取ることになる。リダイレクトしないプロセスはこのファイルデスクリプタを共有する
  • &でバックグラウンドで実行する。子プロセスを生成したかどうかは関係がない
  • 外部コマンドはfork+execで子プロセスを生成するが、関数は生成しない
  • waitはジョブを共有待ち合わせる。引数なしですべてのジョブを待つ
  • 同じファイルデスクリプタを共有している場合、IOの位置もいずれかのプロセスが動かせばすべてのプロセスで動く

結局、アクセスしたら要素をひとつ返してくれるQueueがほしいのだ。

そこでまじめに考えてみた。一番単純なのはflockを使う方法なのだが単純にはいかない。プロセスの中で処理したければ、ファイルデスクリプタを使った、複雑な方法が必要になる。
その中で比較的スマートに処理できると考えたのがGistのスクリプトだ。

この場合、.lockは空ファイルであり、単なるロックでしかない。無駄なファイルを作るのにはちょっと抵抗があるが、方法としては比較的簡単だ。
この方法はbashでもほぼ同様に書くことができる。あまりzshらしい方法とは言えない。

なんか悔しいので、Zshらしい方法として、Socketを使うという方法を提案してみる。
UNIXドメインソケットはファイルパスを用いてプロセス間通信を行う。TCP同様、サーバーが接続を受け付け、IOを確立するものだ。

一般的にはサーバーは並列処理できるように接続の受け付けはマルチスレッドで行う。

zsocket -l foo
integer server=$REPLY
	
while zsocket -a $server
do
(
  integer io=$REPLY
  ...
) &
done

だが、シングルスレッドで行った場合はどうなるか。listenはしているがacceptはしていない状態だと接続しようとするclientはブロックされ、acceptされるまで待たされることになる。
結果的に、あるリソースにアクセスし、供給する部分がシングルスレッドになる。同時にアクセスしてもproducerはそれをブロックして順番に渡していくことになる。

これはごく普通のマルチプロセスモデルであり、Perlはthreadが非推奨で、UNIXドメインソケットの利用を推奨している。
そのため特に目新しいものではないのだが、Zshでおこなう(シェルスクリプトで行う)というとまたちょっと味があるのではないか。

Gist

様々なブラウザと様々なプロファイルを切り替えて使うスクリプト

基本解説

GitHub

これまでFirefox Latestスクリプトを使ってきたが、これではFirefoxしか利用できなかったり、異常終了やフリーズすると問題が生じる可能性があった。

Firefox Latestの仕組みは次のようなものだ。

Gist

要はシンボリックリンクで.mozillaを切り替えて起動→終了したらシンボリックリンク修正、をしているだけだ。

ちなみに、もともとはMageiaがFirefoxをLTS版を採用していて、それが気に入らなかったのでこのようにしている。
だが、Manjaroはその必要はないので、Firefoxだけでなく、任意のブラウザを任意のプロファイルで起動できるようなスクリプトを書いた。

多くの関数が定義されているが、まずはプロファイル切り替えの方法をまとめる必要があった。

Chromium系は--user-data-dirオプションを使用する。

これに該当するのは

  • Google Chrome
  • Chromium
  • Opera (Blink系)
  • Vivaldi
  • SRWare Iron
  • Maxthon

Maxthon for LinuxはあまりにもOUT OF DATEが過ぎるが。

Firefox系は-P profile-profile pathのふたつ。設定全体を切り替えることはできず、プロファイルのみの保存切り替えだが、問題はあまりなさそうだった。これに該当するのは

  • Firefox
  • SeaMonkey
  • Palemoon

それ以外

  • Midoriは-c directory
  • qupzillaは-p directory
  • Rekonqは--config directory

この5タイプに対応した上で、それぞれのブラウザに対応した関数を用意し、簡単に起動できるようにしている。

例えば

mybrowsers[shopping]="chr $HOME/.browser-settings/chromium/shopping"

とした上で、

$ mybrowser.zsh shopping

とすれば、$HOME/.browser-settings/chromium/shoppingを設定ディレクトリとしてChromiumが起動することになる。

Zsh Assoc

今回のスクリプトではZshの連想配列を2つ使用している。

ひとつは、ブラウザの起動設定を行うためのものだ。これは

eval "$mybrowsers[$1]"

という部分で作用している。

もうひとつは、ブラウザのコマンドを修正するものだ。Chromeはgoogle-chromeだったりするかと思うが、Archではgoogle-chrome-stableだったりもする。

そこでこれを修正するため、ブラウザの各コマンドは"${${modify_browsers[$browser]}:-$browser}"とすることで修正可能にした。

サイト構築で画像関連機能

ウォーターマークをいれた画像を半自動で用意する

Gist

ウォーターマークのベースはさすがに自動生成できるレベルにはなかったので、Inkscapeで作成した。
縦と横の二種類を用意している。

fig/にはオリジナルサイズの、thumb/にはサムネイルサイズの画像を配置するが、この時にcompositeを使ってウォーターマークを合成している。

また、幅1600pxを越える画像はリサイズしている。

@figbaseuriの不完全さ

PureDocにもわずかな変更を加えたが、これはattrを排除するためにすぎない。

大きいのはParser::PureDocにおける

        # Other settings for PureDoc
        begin
        if config[:purebuilder_config][:puredoc_tune].kind_of? Proc
          config[:purebuilder_config][:puredoc_tune].call(::DOC)
        end
        rescue
          STDERR.puts "!!ERROR in PureDoc TUNE:" + $!
        end

という変更だろう。

これによって、@config[:puredoc_tune]を使って各PureDocオブジェクトに対して設定が可能になった。
これはMarkdownドキュメントに対しては無効となる。

Gist

「各article要素を取得してループ→各要素に対して各figure要素を取得してループ→イベントリスナー追加」という単純な構造ではある。

ライトボックス用に用意されたimg要素のsrcを変更し、さらに最大幅と最大高をウィンドウサイズに合わせてから表示する、という処理が追加され、ちゃんと機能するものになった。

figure要素に入っていないものはサムネイルの構造を持っていないとみなす方式。

暗号化ディスクをsystemdでがんばる

これまで単純にスクリプトで暗号化ディスクをマウントしていた。
systemdスクリプトにするのは簡単で、実際にSystemdで自動マウントしていた時期もある。

だが、今回は「ちゃんと」Systemdを使うことにした。

スクリプトはGitHubで公開している。

私の場合、btrfsのボリュームとして4つのデバイスを使い、その4つのデバイスはディスク全体をdm-crypt plainで暗号化したものだ。
つまり、おおまかにはcrpytsetupのあとmountする必要があり、かつcryptsetupは全ディスク分ループしなくてはいけない。

これ自体はスクリプトとして用意してあり、これまでそれを使っていた。
Systemd対応にするのも、単純にSystemd経由にするだけなら、以前のエントリの通り簡単なユニットで良い。

今回はスクリプトは、「後処理」に対応した。
もちろん、スクリプトに後処理を組み込むなら非常に簡単な話だ。
だが、それでは「後処理に失敗するとユニット全体が失敗」してしまう。
そこで、systemdのExecStartPostを使うことにした。
それに伴って、スクリプトは起動スクリプトらしくなったし、設定ファイルを使うようになったりしている。

elif [[ "$1" == post ]]
then
  ### StartPost
  if whence -f opendisk_after >&2
  then
	opendisk_after
  else
	exit 0
  fi

fi

これでだいぶ整った。これを呼ぶほうは

ExecStartPost=/usr/local/sbin/opencryptdisk.zsh post /etc/opencryptdisk/%I.conf

%Iについては後ほど説明。

しかしここでだいぶ躓いた。ExecStartの完了を待たずにExecStartPostしてしまうため、ここに処理が入っているとコケるのだ。

これは、Typeを省略しているとsimpleとして扱われる。これは、フォアグラウンドで走るデーモンのためのユニットタイプで、「実行と同時に起動完了・実行終了とみなす」というもの。
実際はスクリプトの実行が終わった時に起動完了・実行終了としてほしいため、

Type=oneshot

を追加した。

同じような理由でExecStopが入っているとこけていたため、スクリプトにはcloseも入っているが、ExecStopが外されている。
ExecStopでアンマウントするのであれば、

RemainAfterExit=yes

として、「スクリプト終了時に起動完了・実行は継続とみなす」にする必要がある。
ただ、マウントと暗号化デバイスは、多分終了時に自動的にうまくやってくれるため、必要ないと判断して外してある。

systemdユニットがname@.serviceの形になっていると、name@param.serviceとして起動することができ、@paramをユニット内で%Iとして使うことができる。
複数ボリュームのマウントを可能にするため、この機能を使用している。


Systemdについてだが、非常に高機能だが、一方で複雑でめんどくさい。

例えば、マウントもスクリプト内でやるのではなく、.mountユニットにして、そのAfterで復号化してから実行、という形もとれたが、明らかにmountを書くほうが早い。

やはりかなりの暗黒面だ。

Pureminder (リマインダ)とZsh socket programming

Tasqueにはリマインダ機能がない。

Taskcoachということも考えはするが、残念ながらTaskcoachは私の環境ではSystem trayに収まってくれないため、非常に邪魔である。

そこで、リマインダを作ってみた。GitHubで公開している。今回はどちらかというとNoddy寄りだが、きちんとした形で公開している。

PureminderはZshで書かれている、クライアント/サーバーである。
サーバーはメッセージを受け取り、SOX(play)で音を再生し、Zenityで画面に表示する。

わざわざクライアントサーバーモデルをとっているのは、atでの利用に問題があるためだ。

単にatでcallすると、Zenityは表示すべきディスプレイサーバーを見つけられない。
$DISPLAY変数によって指定することはできるが、マルチユーザー環境での動作が信用できない。

そこで、確実に機能させるため、現在のデスクトップ上で起動し、ユーザー別に作られるUNIXドメインソケットでメッセージを受け取る、という仕様とした。

だが、PureminderはZshだ。
一般にはなかなか見かけることのない、Zsh socket programmingをちょっと紹介しよう。

まず、モジュールをロードする。

zmodload zsh/net/socket

次にzsocket -lでソケットを作成する。

zsocket -l "$sockfile"

$REPLYにファイルデスクリプタ(Integer)が入るので、とっておく。

typeset -g sock=$REPLY

zsocket -aで接続を待ち受ける。引数はソケットのファイルデスクリプタ。

zsocket -va $sock

$REPLYに接続のファイルデスクリプタが入る。この接続は全二重。
閉じる時は次のようにする。

exec {fd}>& -

シェルでrcファイル更新時に既存シェルに再読み込みさせる

にゃおきゃっとさん(@nyaocat])がbashrcの更新に合わせて全bashに反映させたいというツイートをしていたので、

exec bashする方法を提案してみた。

で、結構うまくいくようなので、採用されたみたい。

この点について解説してみる。

exec(2)は現在のプロセスを置き換えるシステムコールで、execコマンドは引数コマンドを現在のプロセス実行し、実行中プロセスと置き換える。

これによって新規bashが起動される。この際

  • PIDは変わらない
  • プロセスの親子関係も変わらない
  • execされた場合、bashはジョブに対してSIGHUPを送らない(子プロセスが終了したりはしない)
  • ジョブテーブルは現行プロセスに固有の情報なので、引き継がれない。バックグラウンドジョブがあった場合、再読み込みされたシェルでjobsしてもリストされない
  • シェル起動後に覚えさせた変数、関数などは引き継がれない。環境変数も消滅する
  • 既に動いている子プロセスに対する影響は全く無い

子プロセスに影響がないのは、親プロセスの変更は子プロセスに伝播しないためだ。 環境変数の変更はあくまでそのプロセスと、そのプロセスから生成されるプロセスに対する影響である。プロセス生成時に引き継がれるだけだからだ。

それどころか、親プロセスが死んで孤児になると、initが引き継ぐが、それでも子プロセスには影響しない。

問題は、積極的にインタラクティブシェルで変数や関数やエイリアスを活用している人は、それらが全てリセットされてしまうことだろう。 あと、ジョブテーブルが消えるのも、ジョブを活用している人には痛い。

.zshrcなら以下で、SIGHUPを送った時に読みなおすようになる。

TRAPHUP() {
  exec zsh -l
}

連番ファイルの並べ替え、差し込み

彼女に書籍の電子化作業を頼んだのだが、あまりにも雑でかなり困った。

まず大量にあった、ページ順が逆になっているものについては、次のスクリプトをかいた

#!/usr/bin/zsh

files=(*)
dfiles=(${(aO)files})

print -l $dfiles

for i in "$files[@]"
do
  mv -v "$i" "$i.tmp"
done

integer index

for (( index = 1; index <= ${#files} ; index++ ))
do
mv -v "$files[$(( index ))].tmp" "$dfiles[ $(( index )) ]"
done

逆順はまだしも、差し込みはきつかった。 どこで何をしたかは説明したくないので、打ったコマンドをまとめて。 (Zsh)

for i in *
do
mv $i $i[6,8].jpg
done


for i in <100->*
do
mv $i $(( ${i:r} + 36 )).jpg
done

for i in <23-99>*
do
mv $i 0$(( ${i[2,3]:r} + 36 )).jpg
done

for i in *
do
mv $i $i[6,8].jpg                 
done

files=(*)

for i in ${(aO)files}
do
mv $i 0$(( ${i[2,3]:r} + 17 )).jpg
done

for i in *
do
mv $i $i[6,8].jpg                 
done

for i in <100->*
do
mv $i $(( ${i:r} + 4 )).jpg 
done

for i in <93-99>*    
do
mv $i 0$(( ${i[2,3]:r} + 4 )).jpg   
done

for i in 01??.*
do
mv $i $i[2,-1]
done

for i in *
do
mv $i 0$(( ${i[3]:r} + 90 )).jpg
done

重要なのは

  • 必ず後ろからやる
  • nnnなので、100をこえるものは先にやる
  • 0nnなものはArithmatic expansionの前にちゃんと0をつける

そして、紙がまとめて通ってしまったものはどうしようもないし、ページが何の順番でもないものは再スキャンしたほうが早いので、ゴミ箱から探して組み直し。 所要時間はだいたい4時間。

ほんっとに疲れた。

BerryjackをTwitpic対応に

これはあくまで学術研究目的のレポートである。活用は自己責任にてお願いしたい。

まずはdiff

diff --git a/berryjack b/berryjack
index 378a970..c00e889 100755
--- a/berryjack
+++ b/berryjack
@@ -124,11 +124,12 @@ function get_media_url()
       fi
     done
     # image
-    for image in $( grep -Eo 'https://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+\.(jpg|png)' $tmp | sort | uniq | \
-      (if [ $orig -eq 0 ]; then
-        cat
+    for image in $( grep -Eo 'https://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+\.(jpg|png)|https?://twitpic.com/[a-z0-9]*' $tmp | sort | uniq | \
+      (
+      if [ $orig -eq 0 ]; then
+        sed 's#twitpic.com/\([0-9a-z]*\)#twitpic.com/show/full/\1#'
       else
-        sed 's/$/:orig/g'
+        sed 's#twitpic.com/\([0-9a-z]*\)#twitpic.com/show/full/\1#' | sed '/pbs\.twimg\.com/ s/$/:orig/g'
       fi
       ) )
     do
     

TwitpicのURIを拾うのは、単純にorでgrepがTwitpicのURIを拾ってくれるようにすれば良い。 だが、TwitpicのURIは画像のURIではなくページのURIなので、sedで画像のURIに変換している。 pbs.twimg.comのURIも対象にしているが、該当部分はおそらくないため、単純に無視される。 なお、URIはhttps?://twitpic.com/[a-z0-9]*に合致するもののみを想定している。

これで取得できる。 Crawlするためのシンプルなコード。保存するベースとなるディレクトリにおき、<userid>/.idというファイルを作成する。

#!/usr/bin/zsh

for idfile in */.id
do
  (
 	  print -- "-*- -*- -*- ${${idfile:h}:t} -*- -*- -*-"
   	cd "${idfile:h}"
   	berryjack "${${idfile:h}:t}"
  )
done