Emacs Lispでシェルコマンドを活用する

Emacs上からのコマンド実行方法は多くありますが、Emacs Lispの関数から使うことに焦点をあてます。


# 基本パターン

## 一番単純な使い方
`pwd`を実行して結果を文字列で得ます。

```el
(defun my-pwd ()
  (interactive)
  (shell-command-to-string "pwd"))
;; (my-pwd) ; => "/Users/username\n"
```

`M-x my-pwd`もしくは`M-: (my-pwd)`で実行できます。
(`pwd`: カレントディレクトリを返すコマンド)



## 非同期でコマンドを実行

Mac OSXのsayコマンドでAlexに喋ってもらいます。`shell-command-to-string`で同期実行した場合はAlexが喋っている間にEmacsを動かすことができません。そこで非同期実行の`async-shell-command`を使って実行中もEmacsを動かせるようにしています。

```el
(defun my-say-hello ()
  (interactive)
  (async-shell-command "say -v Alex Hello! I am Emacs, at your service."))
```



## 選択範囲に対してコマンドを実行

選択範囲のaをbに置き換えます。perlコマンドの正規表現で置換

```el
(defun my-replace-a-b ()
  (interactive)
  (when (region-active-p) ;; 選択範囲があるとき
    (shell-command-on-region (region-beginning) (region-end) "perl -0 -p -w -e \'s/a/b/g\'" nil t)))
```



## Dired上のファイル/フォルダに対してコマンドを実行

カーソル位置の画像ファイルの情報を表示します。マークしたファイルも対象になります。

```el
(defun my-dired-display-file-info ()
  (interactive)
  (let ((files (dired-get-marked-files t current-prefix-arg)))
    (dired-do-shell-command "file *" nil files)))
```


## その他

### コマンドのチェック

パスが通っていて使用可能ならコマンドのパスが返ります。

```el
(executable-find "cmigemo") ; => "/usr/local/bin/cmigemo"
```

### Emacs用にコマンドへのパスを通す

こちらを参考に
:link: [PATH の設定](http://sakito.jp/emacs/emacsshell.html#path)




# 実践サンプル

Mac OS X向けのコマンドが多めです。



## 選択範囲を指定したタグで囲む

選択範囲をミニバッファに入力したタグ名で囲みます。

```el
(defun my-wrap-lines-with-html-tag (tag)
  (interactive "sTag: ")
  (if (region-active-p)
      (shell-command-on-region
       (region-beginning) (region-end)
       (concat "perl -0 -p -w -e \'"
               "s/^([^\\S\\r\\n]*)(\\S.*?)[^\\S\\r\\n]*$/$1<"
               tag ">$2<\\/" tag ">/gm\'")
       nil t)))
```


## カレントディレクトリ以下の特定ファイルを削除

カレントディレクトリ以下の.DS_Storeファイルを削除します。

```el
(defun my-delete-DS_Store-under-current-directory-recursively ()
  (interactive)
  (shell-command "find . -name '*.DS_Store' -type f -delete")
  ;; Diredを開いていたらバッファを更新
  (if (eq major-mode 'dired-mode)
      (revert-buffer)))
```


## Diredからsqliteファイルの内容を閲覧

```el
(defun my-dired-sqlite ()
  (interactive)
  (let ((file (dired-get-filename))
        (buffer (format "SQLite-%s" (random 100000))))
    (async-shell-command (format "sqlite3 '%s'" file) buffer)
    (switch-to-buffer buffer)
    (insert ".header on")
    (execute-kbd-macro (kbd "RET")) ;; returnキー実行
    (insert ".mode column")
    (execute-kbd-macro (kbd "RET"))
    (insert ".tables")
    (execute-kbd-macro (kbd "RET"))))
```


## Diredでマークしたファイルの特殊属性を除去

```el
(defun my-dired-delete-mac-atmark-permission ()
  (interactive)
  (let ((files (dired-get-marked-files t current-prefix-arg)))
    (mapc (lambda (file)
            ;; マークしたファイルそれぞれに対して実行
            (dired-do-shell-command "xattr -c *" nil (list file)))
          files)
    (dired-unmark-all-marks) ;; マーク解除
    (revert-buffer) ;; バッファ更新
    ))
```


## Diredでマークしたファイルを別アプリに送る

[Image Optim](https://imageoptim.com/)アプリへ送り、画像の容量を落とします。

```el
(defun my-dired-image-optim ()
  (interactive)
  (let ((files (dired-get-marked-files t current-prefix-arg)))
    (mapc (lambda (file)
            (cond ((string-match ".*\\.\\(png\\|jpe?g\\|gif\\)$" file)
                   ;; 指定した種類の画像ファイルの場合
                   (dired-do-shell-command "open -a ImageOptim.app *" nil (list file)))))
          files)
    (dired-unmark-all-marks)
    (revert-buffer)))
```


## Diredでマークしたファイルを入力値を使って処理

実行後にミニバッファに値を入力します。その値で画像のリサイズを行います。
(sips: 画像をリサイズする Mac OS X 標準コマンド)

```el
(defun my-dired-image-width-resize-with-sips (width)
  (interactive "sWidth: ")
  (let ((files (dired-get-marked-files t current-prefix-arg)))
    (mapc (lambda (file)
            (cond ((string-match ".*\\.\\(png\\|jpe?g\\|gif\\)$" file)
                   (dired-do-shell-command
                    (concat "sips --resampleWidth " width " *")
                    nil (list file)))))
          files)
    (dired-unmark-all-marks)
    (revert-buffer)))
```


## 選択範囲を任意の正規表現で置換

私のお気に入りのコードです。
`M-x my-perl-replace-command`のあとに例えば`s/[\n\r]//g`とすると、選択範囲の改行を削除します。
他によく使うのが、`s/\t/  /g`でタブ文字を空白文字2つに置換しています。標準の変換関数より高速です。


```el
;; 履歴用
(defvar my-perl-replace-history nil
  "History for the `my-perl-replace-command'.")
(defun my-perl-replace-command (command)
  "Utilize Perl regular expression to replace string in the current buffer."
  (interactive (list (read-shell-command "s[perl regexp] : "
                                         ""
                                         'my-perl-replace-history)))
  (when (string-match "'" command)
    (setq command (replace-regexp-in-string "'" "\\\\x27" command)))
  (if (region-active-p)
      (shell-command-on-region
       (region-beginning) (region-end) (concat "perl -0 -p -w -e \'" command "\'") nil t)
    (let ((po (point))
          (win (window-start)))
      (mark-whole-buffer)
      (shell-command-on-region
       (region-beginning) (region-end)
       (concat "perl -0 -p -w -e \'" command "\'") nil t)
      (goto-char po)
      (set-window-start (selected-window) win))))
```