■ Windows Subsystem for Linux の Emacs で利用できる設定


【お知らせ】


<2020/05/06 追記>
WSL2 で動かすために 3) の設定の見直しを行いました。

<2020/03/13 追記>
Windows 10 1903 辺りからだと思いますが、/etc/fstab に下記 2) で説明している設定を追加すると \\wsl$ から始まるパスにアクセスできなかったり、VolFs 上で exe コマンドが実行できなくなる問題が発生します。この問題は、/etc/fstab の LABEL 行の後に次のとおり C:\ のマウント定義を明示することで解決できることが分かりました。WSL のバグだと思いますが、Windows 10 1909 でも直っていないようですので、回避策としてお試しください。(WSL1 の場合の対策です。WSL2 はまだ未確認です。)
LABEL=cloudimg-rootfs	/	 ext4	defaults	0 0
C:\	/mnt/c	drvfs	defaults,noatime,uid=1000,gid=1000	0 0
/etc/fstab を変更した際は、コマンドプロンプトから次のコマンドを入力後、WSLコンソールを再起動してください。
> wslconfig /t <ディストリビューション名>

<2019/06/23 追記>
Windows 10 1903 になって、/mnt/c のマウントパスが C: から C:\ に変更となったので、その対策を行いました。また、drvfs-alist に //wsl$/... の変換用情報を追加登録するようにしました。ただし、//wsl$/... を指すショートカットファイルを dired でシンボリックリンクのように表示することはまだ実現できていません。

<2018/11/06 追記>
Windows 10 1803 以降、マウントしたファイルサーバのエリアに cp -p でファイルをコピーすると、タイムスタンプが変更できないというエラーが発生するようになりました。これは、マウントオプションに uid=1000,gid=1000 を指定することで解決できることが分かりましたので、本説明を修正しました。

<2018/03/12 追記>
Cygwin版 はこちらを参照してください。

【本題】


Windows Subsystem for Linux で、Windowsパス と UNCパス を使えるようにするための設定です。

windows-path.el を参考として作っており、一部 windows-path.el のコードを含んでいますので、windows-path.el のライセンスに従いご利用ください。

1) 認証を必要とするネットワークボリュームを利用する場合は、予め次のページの設定により認証情報を記憶させる。
※ ネットワークボリュームにアクセスした際に開く認証画面で、「資格情報を記憶する」にチェックを入れることでも登録できます。

2) 同じくネットワークボリュームを利用する場合は、以下のような /etc/fstab を設定し、マウントを行うようにする。
<for Windows 10 1709>
\\<ホスト名称>\<共有名>	/mnt/share1	drvfs	defaults	0 0
<for Windows 10 1803 or later>
\\<ホスト名称>\<共有名>	/mnt/share1	drvfs	defaults,noatime,uid=1000,gid=1000	0 0
※ Windows 10 1803 以降では、/etc/fstab があれば自動マウントするデフォルト設定となっています。Windows 10 1709 までは、sudo mount -a が必要となります。
※ /etc/fstab の設定で、IPアドレスを含む UNC パスに対してのマウントポイントを指定する場合、「\\172」で始まる箇所などが8進数認識されてしまいます。この場合、2つ目の「\」を敢えて8進数の「\134」と記載することで対策可能です。
\\134172.16.0.1\<共有名>	/mnt/share1	drvfs	<オプション>	0 0
※ 同一のネットワークボリュームに複数の UNC パスを指定したい場合には、次のようにマウントポイントを同一のものにすることが可能です。この場合、6) で取得したい UNC パスを一番最後に指定するようにしてください。
\\<IPアドレス>\<共有名>			/mnt/share1	drvfs	<オプション>	0 0
\\<ホスト名称>.<domain>\<共有名>	/mnt/share1	drvfs	<オプション>	0 0
\\<ホスト名称>\<共有名>			/mnt/share1	drvfs	<オプション>	0 0
※ Windows 10 1903 辺りからだと思いますが、/etc/fstab に上記の設定を追加すると \\wsl$ から始まるパスにアクセスできなかったり、VolFs 上で exe コマンドが実行できなくなる問題が発生します。この問題は、/etc/fstab の LABEL 行の後に次のとおり C:\ のマウント定義を明示することで解決できることが分かりました。WSL のバグだと思いますが、Windows 10 1909 でも直っていないようですので、回避策としてお試しください。
LABEL=cloudimg-rootfs	/	 ext4	defaults	0 0
C:\	/mnt/c	drvfs	defaults,noatime,uid=1000,gid=1000	0 0
/etc/fstab を変更した際は、コマンドプロンプトから次のコマンドを入力後、WSLコンソールを再起動してください。
> wslconfig /t <ディストリビューション名>

3) 以下の設定を行う。
(require 'cl-lib)

(defun set-drvfs-alist ()
  (interactive)
  (setq drvfs-alist
        (mapcar (lambda (x)
                  (setq x (replace-regexp-in-string "|/.\\."
                                                    (lambda (y)
                                                      (format "|//%o." (string-to-char (substring y 2 3))))
                                                    x))
                  (when (string-match "\\(.*\\)|\\(.*?\\)/?$" x)
                    (cons (match-string 1 x) (match-string 2 x))))
                (split-string (concat
                               ;; //wsl$ パス情報の追加
                               (when (>= (string-to-number (nth 1 (split-string operating-system-release "-"))) 18362)
                                 (concat "/|" (shell-command-to-string "wslpath -m /")))
                               (shell-command-to-string
                                "mount | grep -E ' type (9p|drvfs) ' | grep -v '^tools on /init type 9p' | sed -r 's/(.*) on (.*) type (9p|drvfs) .*/\\2\\|\\1/' | sed 's!\\\\!/!g'"))
                              "\n" t))))

(set-drvfs-alist)

(defconst windows-path-style-regexp "\\`\\(.*/\\)?\\([a-zA-Z]:\\\\.*\\|[a-zA-Z]:/.*\\|\\\\\\\\.*\\|//.*\\)")

(defun windows-path-convert-file-name (name)
  (setq name (replace-regexp-in-string windows-path-style-regexp "\\2" name t nil))
  (setq name (replace-regexp-in-string "\\\\" "/" name))
  (let ((case-fold-search t))
    (cl-loop for (mountpoint . source) in drvfs-alist
             if (string-match (concat "^\\(" (regexp-quote source) "\\)\\($\\|/\\)") name)
             return (replace-regexp-in-string "^//" "/" (replace-match mountpoint t t name 1))
             finally return name)))

(defun windows-path-run-real-handler (operation args)
  "Run OPERATION with ARGS."
  (let ((inhibit-file-name-handlers
         (cons 'windows-path-map-drive-hook-function
               (and (eq inhibit-file-name-operation operation)
                    inhibit-file-name-handlers)))
        (inhibit-file-name-operation operation))
    (apply operation args)))

(defun windows-path-map-drive-hook-function (operation name &rest args)
  "Run OPERATION on cygwin NAME with ARGS."
  (windows-path-run-real-handler
   operation
   (cons (windows-path-convert-file-name name)
         (if (stringp (car args))
             (cons (windows-path-convert-file-name (car args))
                   (cdr args))
           args))))

(add-to-list 'file-name-handler-alist
             (cons windows-path-style-regexp
                   'windows-path-map-drive-hook-function))
※ Emacs を立ち上げた後に mount の追加登録を行った場合は set-drvfs-alist を再実行してください。M-x set-drvfs-alist で実行可能です。

4) find-file コマンド等で Windows パスや UNC パスを入力し、正しく変換されることを確認する。コピペでのパス入力をお薦めします。

5) 次の設定を行う(オプション)。この設定により、Windows で作成したショートカットをシンボリックリンクの様に表示し、辿ることができるようになる。

6) dired で開いているディレクトリの Windows パスや UNC パス をクリップボードに取り込むには以下のキー設定を行う。
(require 'cl-lib)

(define-key dired-mode-map (kbd "C-c w")
  (lambda ()
    (interactive)
    (let ((name (file-truename (dired-current-directory)))
          (case-fold-search nil))
      (cl-loop for (mountpoint . source) in (reverse drvfs-alist)
               if (string-match (if (string= mountpoint "/")
                                    "^\\(\\)/"
                                  (concat "^\\(" (regexp-quote mountpoint) "\\)\\($\\|/\\)"))
                                name)
               return (progn
                        (setq name (replace-match source t t name 1))
                        (setq name (replace-regexp-in-string "/" "\\\\" name))
                        (shell-command-to-string
                         (format "echo -n %s | clip.exe" (shell-quote-argument name)))
                        (message name))
               finally return (message "Windowsのパスには変換できません")))))
この機能は性能を必要としないので(一回変換されればいいだけなので)、wslpath コマンドを使う方法も使えます。
(define-key dired-mode-map (kbd "C-c w")
  (lambda ()
    (interactive)
    (let ((winpath (shell-command-to-string
                    (format "wslpath -w %s 2> /dev/null"
                            (shell-quote-argument (file-truename (dired-current-directory)))))))
      (if (string= winpath "")
          (message "Windowsのパスには変換できません")
        (setq winpath (substring winpath 0 -1))
        (shell-command-to-string
         (format "echo -n %s | clip.exe" (shell-quote-argument winpath)))
        (message winpath)))))


<変更履歴>
  • 2018/03/09 このページを作成した。
  • 2018/03/11 dired で開いているディレクトリの Windowsパス をクリップボードに取り込むキー設定を追加した。
  • 2018/03/22 コンピュータ名が IPアドレスの UNC パスを mount した際でも正常に動作するように対応した。
  • 2018/03/27 mapc 利用箇所を cl-loop の利用に置き換えた。
  • 2018/11/06 cp -p でコピーした際のタイムスタンプが変更できないエラーを解決するため、マウントオプションの見直しを行った。
  • 2019/06/23 Windows 10 1903 になって、/mnt/c のマウントパスが C: から C:\ に変更となったので、その対策を行った。また、drvfs-alist に //wsl$/... の変換用情報を追加登録するようにした。
  • 2019/06/27 3) と 6) で行っているパスのマッチ条件をより厳密な判定となるよう見直した。
  • 2019/07/14 windows-path-convert-file-name 関数に \\wsl$\Ubuntu-18.04 で始まるパスの変換不具合があったので対応した。