pecoを使って上の階層にcdする

シェルで深い階層まで潜っているとき、何階層か上にcdしようとすると cd ../../../ とか書くハメになり面倒くさい。
pecoでちょっと簡単にできるようにした。

やりたいこと

カーソル移動だけで上の階層に移動できるようにする。

cdup_example.gif

方法

~/.bashrc に以下のように書いておく。

function cdup(){
  cd `for i in $(seq 0 $(pwd | awk -F/ '{print NF}')); do pwd | grep -oE "^/([^/]*/){$i}" ; done | peco`
}

説明とか

  • $(pwd | awk -F/ '{print NF}')
    pwd/foo/bar/baz のような文字列が返る。
    awk -F/ '{print NF}' ではスラッシュ区切りとしてその文字列にいくつフィールドがあるかが返る。
    /foo/bar/baz の場合は4が返る。
  • pwd | grep -oE "^/([^/]*/){$i}"
    grep のoオプションはヒットした文字列だけを取り出す。Eオプションは拡張正規表現を使えるようにする。
    pwd の文字列で、最初のスラッシュを0番目として、 $i 番目のスラッシュまでを取り出すみたいな感じ。
  • forループの中で $i には無駄に大きい値が入るが、どうせマッチしないだけなので無視
  • 全体をバックスラッシュで囲んでcdしてるのが気になる件
    本当は

    for i in $(seq 0 $(pwd | awk -F/ '{print NF}')); do pwd | grep -oE "^/([^/]*/){$i}" ; done | peco | xargs cd
    

    のようにパイプでつなぎたかった。でもできない。どうやらcdはシェルのビルトインコマンドなのでできないようだ。

  • (おまけ) mkdirしたあとに cd <ESC> . でそのディレクトリにcdするコマンドになる
    知らなかった…。ただシェルによるよう。

参考

Emacsで指定範囲の色を変える

この記事は ウェブクルー Advent Calendar 2017の4日目の記事です。
昨日は@wc_moriyamaさんの「わが社のSMS活用事例と5分でできる配信トライアル – Qiita」でした。


いま作っているEmacsプラグインで必要になった機能が、わりと実装の面倒くさいものだったのでメモ。
なおEmacs Lispは初心者なので、変なことやっているかもしれない(可能性大)。
Emacs 25.3以降で動くことは確認した。

やりたいこと

こうしたい。テキストは青空文庫から。

やりたいこと

指定した矩形範囲の色を変えたい。
ポイントは以下の2点。

  1. 色を変える範囲をウィンドウ上の行番号・列番号で指定したい(複数行にわたっても)
  2. 指定範囲に文字(空白も含む)がなくても色を変えたい

なお等幅フォントを使っていない場合はきれいに矩形状に色が変わらないが、考慮しない。
(でも等幅でも変になることがあるようだ?)

基本方針: オーバーレイ

今回のように色を変えるには、Emacsではオーバーレイというのを使うようだ。これを駆使して実現してみる。
基本的な使い方は以下。

;; make-overlayでオーバーレイの範囲を指定(後で操作するため、 ovr という変数に入れておく)
(setq ovr (make-overlay 2 12))

;; オーバーレイで背景色を指定
(overlay-put ovr 'face '(t :background "red"))

;; オーバーレイで、指定範囲の後に文字列を追加
(overlay-put ovr 'after-string "あいうえお")

;; オーバーレイを削除
(delete-overlay ovr)

ここではオーバーレイの属性として faceafter-string を使った。他にもいろいろある

オーバーレイの基本的な使い方

難しいこと

make-overlay は範囲をポイントで指定する。つまりそのバッファでの何文字目から何文字目という形で指定する。

今回やりたいのはウィンドウ上での行番号・列番号で範囲を指定すること。なので行番号・列番号をポイントに変換する処理が必要。

悩んだのは、文字のないところにどうやってオーバーレイをかけるかということ。文字のないところにポイントはないので、これはEmacsの機能として不可能なのでは…と思った。

実現しているelisp

調べたところ、文字のないところの色を変えているelispをいくつか見つけた。なので工夫すればできるよう。

  • vline.el
    現在カーソルがある列をハイライトする。主にこれを参考にした。
  • Emacsの矩形選択
    C-x SPC (rectangle-mark-mode)でできるもの。 rect.el に実装がある。
    Emacs 24.4から標準搭載らしい。
  • popup.el
    コンテキストメニュー的なものやツールチップを表示できる。

文字のないところにオーバーレイをかける方法

文字のあるところには普通に face で背景色を指定する。
文字のないところには after-string で空白を追加すればいい。

以下では example-put-ovrs が現在行の列番号 bgn-column から end-column の色を変える。
example-put-ovrs-rectangle はこれを何度も使って、矩形状に色を変える。

コードと実行画像

(defvar example-ovrs-list nil)

(defun example-make-add-string (line-end-column bgn-column end-column face)
  "`line-end-column'の後に追加する文字列を返す。
色を変える範囲は`bgn-column'と`end-column'で指定。色は`face'で指定する。"
  (let* ((transparent-blanks
          (make-string
           (max
            0
            (- bgn-column line-end-column))
           ? ))
         (colored-blanks
          (propertize
           (make-string
            (max
             0
             (- end-column (max bgn-column line-end-column)))
            ? ) 'face face)))
    (concat transparent-blanks colored-blanks)))

(defun example-make-ovrs (line-end-column bgn-column end-column)
  "現在行にかける2つのオーバーレイをコンスセルの形で返す。
文字があるところにかけるオーバーレイと、文字がないところに加えるオーバーレイの2つ。"
  (let* ((before-p (< line-end-column bgn-column))
         (after-p (> line-end-column end-column))
         (line-end-point (save-excursion
                           (example-move-to-column line-end-column) (point)))
         (bgn-point (save-excursion
                      (example-move-to-column bgn-column) (point)))
         (end-point (save-excursion
                      (example-move-to-column end-column) (point)))
         (text-ovr (cond (before-p
                          (make-overlay line-end-point line-end-point))
                         (after-p
                          (make-overlay bgn-point end-point))
                         (t
                          (make-overlay bgn-point line-end-point))))
         (add-string-ovr (make-overlay line-end-point line-end-point)))
    `(,text-ovr . ,add-string-ovr)))

(defun example-move-to-column (column)
  "現在行の列番号`column'に移動する。"
  (vertical-motion `(,column . 0)))

(defun example-get-line-end-column ()
  "現在行(物理行)の末尾の列番号を返す。"
  (save-excursion
    (vertical-motion `(,(1- (window-body-width)) . 0))
    (% (current-column) (window-body-width))))

(defun example-put-ovrs (bgn-column end-column face)
  "現在行の、列番号で指定した範囲`bgn-column'から`end-column'の色を`face'で指定した色にする。"
  (let* ((line-end-column (example-get-line-end-column))
         (ovrs (example-make-ovrs line-end-column bgn-column end-column))
         (text-ovr (car ovrs))
         (add-string-ovr (cdr ovrs))
         (add-string (example-make-add-string
                      line-end-column bgn-column end-column face)))
    (overlay-put text-ovr 'face face)
    (overlay-put add-string-ovr 'after-string add-string)
    (push text-ovr example-ovrs-list)
    (push add-string-ovr example-ovrs-list)))

(defun example-put-ovrs-rectangle (left-top right-bottom face)
  "矩形状にオーバーレイをかける。
矩形は左上の点`left-top'と右下の点`right-bottom'で指定する。"
  (let* ((bgn-line (car left-top))
         (end-line (car right-bottom))
         (lines (number-sequence bgn-line end-line))
         (bgn-column (cdr left-top))
         (end-column (cdr right-bottom)))
    (save-excursion
      (dolist (line lines)
        (move-to-window-line line)
        (example-put-ovrs bgn-column end-column face)))))

(defun example-clear-ovrs ()
  "オーバーレイを全消去。"
  (dotimes (x (length example-ovrs-list))
    (delete-overlay (pop example-ovrs-list))))

;; 使用例
"
列番号の目盛:
0         1         2         3         4         5         6         7         8         9         0         1         2         3
012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
"
(example-put-ovrs 10 30 '(t :background "red"))
(example-put-ovrs 100 120 '(t :background "blue"))
(example-put-ovrs 10 100 '(t :background "white"))
(example-clear-ovrs)


(example-put-ovrs-rectangle '(0 . 0) '(10 . 40) '(t :background "red"))
(example-put-ovrs-rectangle '(11 . 80) '(20 . 100) '(t :background "yellow"))
(example-put-ovrs-rectangle '(21 . 20) '(30 . 90) '(t :background "green"))
(example-clear-ovrs)

文字のないところにオーバーレイをかける方法

example-make-add-string

追加する空白文字列を作っている。
この関数は行末 line-end-columnbgn-columnend-column の関係で返却値を変える。

  • 行末が bgn-column より前にあるとき
    行末から bgn-column までの色なし空白 + bgn-column から end-column までの色付き空白
  • 行末が bgn-columnend-column の間にあるとき
    行末から end-column までの色付き空白
  • 行末が end-column より後にあるとき
    空文字

example-make-ovrs

オーバーレイを2つ、コンスセルにして返す。 (text-ovr . add-string-ovr) の形。
text-ovr は文字のあるところにかかり、色を変えるためのオーバーレイ。
add-string-ovr は文字のないところに文字を追加するためのオーバーレイ。
text-ovr は行末 line-end-columnbgn-columnend-column の関係で範囲が変わる。
add-string-ovr は常に行末。

text-ovr の範囲は以下のようになる。

  • 行末が bgn-column より前にあるとき
    行末が指定される。ここに文字はないので色は変わらない。
  • 行末が bgn-columnend-column の間にあるとき
    bgn-column から line-end-column までが指定される。
  • 行末が end-column より後にあるとき
    bgn-column から end-column が指定される。

これから

  • 設定(?)によってうまくいかないことがある気がするので、できるだけいつでも使えるようにする
  • これだと同じ行に複数のオーバーレイを追加すると崩壊するので何か考える
  • 本題のプラグインを早く…

明日は@asukameさんです。よろしくお願いします!

WordPressのスパムを撲滅する

WordPressに大量にスパムが届いていた。
WordPressの管理画面から一つ一つ処理してられないので、対策をしたメモ。

状況

特定IPアドレス XXX.XXX.XXX.0/24 (XXX.XXX.XXX.なんちゃら)から7000通以上のスパムが届いていた。
このアドレスを whois で調べると海外で、この地域にはこのブログを読みたい人はいないだろう。すべてスパムと判断していい。
WordPressではAkismetというスパムフィルタがあり、自動的にスパムとしてマークしてくれるようだが、これはスパムと判断してくれなかった。
そのためWordPressから「現在○○通のコメントが承認待ちです」という旨のメールが度々送られ、精神衛生上悪い。
なのでまずスパムを削除し、その上でこのIPアドレスからはアクセスが来ないように設定する。

環境

  • OS
    CentOS 7.4
  • ブログ
    WordPress 4.9
  • DB
    MySQL 5.5
  • Webサーバ
    nginx 1.12

スパムの削除

MySQLで一括で削除する。
が、なんとなく怖いので、ゴミ箱行きに留める。

DB名は wp-config.php で設定しているもの。
テーブル wp_comments がコメントのテーブル。これを操作する。
(デフォルトの場合。接頭辞を変えている場合は 接頭辞 + _comments がテーブル名。)
このテーブルのカラム comment_approved はいまいちドキュメントが見つからないが、以下のようになっているようだ。

  • 0
    承認待ち
  • 1
    承認済み
  • spam
    スパムとしてマーク
  • trash
    ゴミ箱に入れる

まずシェルで以下のコマンド。

$ mysql -u <USER_NAME> -p

パスワードを入れてMySQLログイン。

-- DB一覧
show databases;

-- DB選択(`wp-config.php` で設定しているもの)
use <DATABASE_NAME>;

-- 特定IPアドレス `XXX.XXX.XXX.0/24` からのコメント数を確認
select count(1) from wp_comments where comment_author_IP LIKE 'XXX.XXX.XXX.%';

-- 特定IPアドレス `XXX.XXX.XXX.0/24` からのコメントをゴミ箱に入れる
update wp_comments SET comment_approved='trash' where comment_author_IP LIKE 'XXX.XXX.XXX.%';

-- 問題なければコミット
commit;

これで指定IPアドレスからのコメントがすべてゴミ箱に入ったはず。
あとはゴミ箱を空にするなりなんなりすればいい。

指定IPアドレスからのアクセスを拒否

nginxの設定で拒否する。
設定ファイル(サーバの設定を自分でいろいろ書いているはず。たとえば /etc/nginx/conf.d/default.conf)に

server {

のような記述があるので、ここにIPアドレスの拒否設定を書く。

server {
    deny XXX.XXX.XXX.0/24;

みたいな感じ。
(ただ、他のサイトを見ているとserverブロックで書いているところはない。locationブロックで書いている。別にどっちでもいいようだが。)

このあとnginxの設定ファイルをチェックし、nginx再起動。

# 設定ファイルの文法チェック
# nginx -t

# 上でsyntax is okみたいに出たら下を実行。出なければ設定ファイル見直し
# systemctl restart nginx.service

これでもうこのIPアドレスからはアクセスできなくなる。

deny <IP_ADDRESS> はそのIPアドレスからのアクセスを拒否し、
allow <IP_ADDRESS> はそのIPアドレスからのアクセスを許可する。
どっちも書く場合は順番が重要だが、今回は拒否だけなので deny だけ書いておけばいい。

参考

WordPress

nginx