連載
» 2019年04月19日 05時00分 公開

ITの教室:【WSL入門】第3回 WSL活用の落とし穴:LinuxからWindowsフォルダへのアクセス完全マスター

WSLを活用するには、bashのヒストリー機能をマスターするのが近道。またWSLとのWin32相互運用性を理解するのが重要だ。今回はこの2つについて解説する。

[塩田紳二,著]

ITの教室 WSL入門」では、WSLを活用するための基礎をインストールから解説していく。



 WSLのシェルである「bash」の最大の魅力は、コマンドラインで指定する引数に対してさまざまな表現方法が用意されている点にある。例えば、Windows OSでは、制御文字(改行やタブなど)を引数として指定することはできない。しかし、bashでは8進数や16進数を表現する方法が用意されているため、Linuxのコマンドには引数として制御文字を受け付けられるものがある。

 また、複数のファイルを正規表現や数式、範囲指定などで表現できるため、複数の、それも大量のファイルを引数として受け付けるコマンドも多い。bashでは、こうした機能を「展開」といった用語を使い「履歴展開(ヒストリー展開)」「パス名展開」「変数展開」「算術式展開」などの機能がある。

bashを使ってフォルダ内の特定拡張子のファイル名を変更する bashを使ってフォルダ内の特定拡張子のファイル名を変更する
このように特定の拡張子のファイル名のみ(これはWindows OSでも可能だが)、正規表現を使って名前を変更するといったことが簡単に行える。

 Linuxのコマンドラインが便利なのは、コマンドが多数用意されているというだけでなく、bashが提供する強力な「展開」機能により、引数の指定が容易かつ強力というのも理由だ。まずは、簡単に履歴展開を見てみよう。

bashの便利なコマンドライン

 Windows OSのコマンドラインにはヒストリー機能があり、[↑]キーで前に実行したコマンドを呼び出し、編集した上で再度実行することができる。この機能はもともとMS-DOS 5.0(1991年)から搭載されたDOSKEYと呼ばれる常駐プログラムが管理していた関係で、現在でもdoskey.exeコマンドが使われている(詳細はdoskey.exe /?を参照のこと)。

 これに対してWSLのbashにも、カーソルキーでヒストリー(履歴)を呼び出す機能があるが、さらに「!」文字を使った「履歴展開」機能により、似たようなコマンドや似たような引数を繰り返し利用することができる。

 そもそも、UNIXの時代、コンピュータは「ターミナル」(端末)と呼ばれるハードウェアを介して利用が行われていた。端末とは、キーボードとディスプレイを備えた装置で、表示は文字のみである。コンピュータから文字列を受け取るとそれをディスプレイに表示し、キーボードが打鍵されると、その文字をコンピュータに送り返す、という機能を持つ(Windows OSのコンソールウィンドウは、この端末をソフトウェア化したものといえる)。

 このため、UNIXでは、作業効率を上げるため打鍵数をなるべく少なくすることが考えられた。頻度の高いコマンドを2文字にしたり、ヒストリーや編集機能を使って、一回入力したコマンドを再利用したりできるようにするといった工夫が行われた。

 Windows OSのコマンドラインでは、dirコマンドを使って、フォルダの様子を見て、必要ならそこにcdで移動するといったことはよくやる作業だ。これは、Win32側では、以下のような感じになる。

>dir \this\is\very\deep\folders
>cd \this\is\very\deep\folders


カレントフォルダを移動するコマンド(Windows OS標準)

 ここで2つ目のcdコマンドは、ヒストリー機能で、1つ目のdirコマンドを呼び出し、カーソルを先頭に持っていって、「dir」を「cd」に書き換えるといった方法でも実行できる。

 もちろん、同じような操作はbashでもできるが、もっと打鍵数が少なくなる、以下のコマンドが使える。

$ ls /this/is/very/deep/folders
$ cd !*


bash上でのカレントディレクトリ(フォルダ)を移動するコマンド

 ここの「!*」は、「直前のコマンドの引数部分全体(=コマンド名を除いた部分)」という意味だ。WSLのbashのコマンドラインでは、「!」は、「履歴展開」のための記号で、さまざまな記号と組み合わせて以前に実行したコマンド(bashではこれをイベントと呼ぶ)から情報を取り出すことができる。

 簡単なものだけでも、下表のようなものがある。こうした「ヒストリー置換」を使いこなすと、コマンドラインの入力を少ない打鍵数で行えるようになる。

パターン 意味
!n ヒストリーのn番目のコマンド(nは1以上の整数。ヒストリー番号はhistoryコマンドで確認できる)
!-n ヒストリーでn個前に実行したコマンド(nは1以上の整数)
!! 直前に実行したコマンド(!-1と同じ)
!str 文字列strで「始まる」、最後に実行したコマンド
!?str? 文字列strを「含む」、最後に実行したコマンド
^str1^str2^ 文字列str1をstr2に置換して直前のコマンドを再度実行(!が付かないことに注意)
history ヒストリーの一覧リスト表示
bashのヒストリー置換(主なもの)

 WSLのbashにはこれ以外にもいろいろな表記方法があるので「man bash」や「info history」コマンドなどで調べてほしい。ヒストリー機能に慣れると、同種のコマンドや再度同じコマンドを実行するのが簡単になる。実際やってみると多くの作業で同じようなコマンドを繰り返し使っていることがある。lsやechoなどの比較的無害なコマンドを使って実際に試してみるといいだろう。

bashのヒストリー機能の優れた点

 もちろんWSLでも、Win32のdoskey.exeと同じくカーソルの[↑]キーでヒストリーからコマンドを呼び出せる。しかし、ヒストリー置換に慣れるとより少ないキーストロークでコマンドを再実行でき、しかも後述するようにパラメーターの置き換えなども可能で、カーソルキーでヒストリーを呼び出して再編集するよりも簡単だ。ある意味、最小打鍵数でコマンドラインが利用できる。

 例えば、過去に「echo」で始まるコマンドを実行していたとしたら、「!echo」で、そのコマンド行を再度実行させることができる。

bashのヒストリー機能の例 bashのヒストリー機能の例
bashのヒストリー機能を使えば、過去に実行したコマンドを指定して呼び出し、さらにその一部を変更して簡単に再実行できる。例えば!echoだと、echoで始まるコマンドのうち、最も新しいものを呼び出して再実行する。^を使って直前のコマンドの一部を置換することもできる。

 直前のコマンドをもう一回実行するなら「!!」と入力すればよい。このほかに、「^」を使って「^str1^str2^」として直前に実行したコマンドの一部を文字列置換して再度実行することも可能だ。

 ただし、簡単なコマンド名の置き換えだけなら、最初の例にあるように新しいコマンド名を打ち込んだあと、引数として「!*」を指定してもよい。

 さらに、「ヒストリー置換」では、コマンドライン中の単語を指定する「単語指示子」を組み合わせることで、指定した過去のコマンド(イベント)から特定の部分を取り出すこともできる。

 なお、bashでは、コマンドライン先頭のコマンド自体が「0番目」の単語である。「ls -l」なら「ls」が0番目、「-l」が1番目となる。なお、この単語指示子の後にさらに修飾子を付けて、単語の一部を抜き出すという指定もできる。

 詳しくはmanで調べてほしいが、1つだけ解説すると「:p」を最後に付けると履歴展開した結果のみを表示することができ、コマンドは実行されない。履歴展開になれないうちは、これを使うといいだろう。例えば、「ls /temp」のあと「cd !*:p」とすると、cdコマンドの引数として直前に実行したコマンドの引数全部を展開した結果「cd /temp」を表示するがコマンド自体は実行されない。ただし、展開の結果は、ヒストリーの最後に追加されるので、直後に「!!」で実行させることもできるし、後で参照しやすくなる。

パターン 意味
!:1 直前に実行したコマンドの1つ目の単語(最初の引数)
!:n 直前に実行したコマンドのn個目の単語(nは1以上の整数)
!:n-m 直前に実行したコマンドのn個目からm個目までの単語(n、mは1以上の整数)
!:$ 直前に実行したコマンドの最後の単語
!:* 直前に実行したコマンドの引数全て(!*と省略することも可能。!:1-$の省略形)
!:n* 直前に実行したコマンドのn個目から最後までの引数(!:n-$の省略形)
!:n- 直前に実行したコマンドのn個目から最後の1つ前までの単語(最後の単語は含まない)
ヒストリー置換の「単語指示子」(主なもの)

 「!echo:4-$」は、「echoで始まる直前のコマンドの4つ目から最後までの引数」という意味になる。前述の「!*」は、ヒストリー置換の単語指示子の省略表現で、「!:1-$」の省略表現である。この辺りを図解すると次のようになる。

bashのヒストリー置換機能の例 bashのヒストリー置換機能の例
赤字部分が入力したコマンドライン。!を使った記法で、過去に実行したコマンドやその引数の一部を取り出すことができる。

ヒストリーで呼び出してコマンドを編集することも可能

 さらにbashでは、ヒストリーで呼び出したり、ユーザーが入力している現在行の編集をテキストエディタのように行ったりすることもできる。左右カーソルキーで1文字ずつカーソルを移動させる、[Home]/[End]キーで行の先頭、末尾に移動も当然行えるが、bashでは、テキストエディタと同等のカーソル移動コマンドや編集機能がある。

 [Esc][Back Space]キーは1つ前の単語を「キル(削除)」し、[Ctrl]+[Y]キーはキルした単語をカーソル位置に貼り付ける。これを使うと、下図のような編集がコマンドライン行内で行える。こうした編集作業は、UNIX/Linux系では、全て端末の制御コード(エスケープシーケンス)で行われる。

bashの行編集機能の例 bashの行編集機能の例
カーソルをワード単位で動かして、ワードの削除や貼り付けが行える。

 コマンドラインでは、スペースを区切りとしているため、「ワード」の削除コマンドやワード単位の移動コマンドは非常に便利だ。さらにbashの組み込みコマンドのfcを使えば、ヒストリーにあるコマンド行をエディタに読み込み、編集した後で実行させることも可能だ。

 もっともここまで細かく編集することはまれで、実際には、前述のヒストリー呼び出しなどで大半が片付く。これら以外の割り当てについては、以下の記事も参照のこと。

 取りあえず、どのようなキー割り当てになっているのかは、bashの組み込みコマンドであるbindを使って「bind -p」で表示できる。このとき、各行の先頭のダブルクオートで囲まれた部分がキー、コロンの後が動作(bashの組み込み関数)である。「\C-」は[Ctrl]キーを押しながら、「\e」は、[Esc]キーを意味する。例えば、「"\C-a"」は[Ctrl]+[A]キーを意味し、「"\eb”」は「[Esc][B]キー」という意味である。

 このキー割り当ては、デフォルトではemacsエディタのキー割り当てに基づいている。他にvi系エディタに基づいたキー割り当ても利用できる。それぞれのキー割り当てと変更方法については、以下の記事も参照のこと。

 このコマンドの出力をファイルなどに保存してみれば分かるが、ほとんど全ての文字に対して関数が定義されている。「a」には「a」を入力するというコマンド(self-insert)が割り当てられている。逆にいうと、bashでは、全ての入力文字にコマンドを割り当てることが可能である。どうしても、bind -pの出力が見難いという場合にはawkやsedコマンドを使って文字列を置換し、分かりやすい表記に置き換えることもできる(コマンドの意味についてはおいおい解説する予定だ)。

bind -pの出力をawkコマンドで整形した出力 bind -pの出力をawkコマンドで整形した出力
bind -pの出力をawkで文字列置換すれば、見やすいコマンドラインのキー割り当てのリストを作ることができる。

bind -p | awk '{ gsub(/\\C/,"[Ctrl]"); gsub(/\\e/,"[Esc]"); print }' | sort


bind -pの出力を見やすくするコマンド例

 このようにWSLを使うと、コマンドの出力を加工でき、簡単な出力のコマンドを組み合わせて作業を行うことができる。こうした考えは「Software Tools」という書籍(邦訳は「ソフトウェア作法」、共立出版)で世の中に広まったが、著者の一人は、UNIXの開発者でもあるBrian W. Kernighan氏である。この本で述べられる基本的な考え方はいまでも有効なので、未読の方は一度、目を通すことをお勧めする。

WSLとWin32の関係

 本連載の立場は、コマンドラインの作業をWSLから行うことでこれまでより便利になるというものだ。WSLのWin32相互運用性を使えば、Win32側のコマンドは全てWSLから起動できる。そこで、WSLの中からWindows OSがどのように見えるのかについて解説しておこう。

 WSLの中で動作しているのは、ほぼ普通のLinuxでコマンドのバイナリコードもそのままである。コマンドに関しては特にWSL用に変更が加えられているわけではない。このため、WSL側には、Linuxのファイルシステムがそのまま見える。これを「VolFs(Volume File System)」と呼ぶ。これに対して、Windows OS側の普通のファイルシステム(Cドライブなど)は、WSL側からは、「/mnt/」以下にドライブ文字から始まるパスとして見ることができる。これを「DrvFs(Drive File System)」と呼ぶ。

VolFsとDrvFsの関係 VolFsとDrvFsの関係
WSL側は、ローカルファイルシステムとなるVolFsと、Win32側にアクセスするためのDrvFsが利用できる。Windows 10 October 2018 Update(バージョン1809)以前ではWin32からVolFs側にアクセスすることはできなかったが、次のバージョン1903は可能になる予定だ。

 WSLの中からであれば、LinuxのローカルファイルシステムもWindows OSのファイルシステムもそのまま扱うことができる。ただし、WSL側のLinuxコマンドで、Win32側のファイル(例えば、C:\Windows)を扱う場合、DrvFsに従ったパスを使う必要がある。例えば、WSL側でlsコマンドを使って、Win32側の「c:\Users\Shioda」にアクセスするには、以下のコマンドを実行する。

$ ls /mnt/c/Users/Shioda


WSL側のlsコマンドでWin32側のフォルダにアクセスする

 このとき、Windows OS側であっても大文字小文字が区別されることに注意されたい。しかし、WSL内からWin32側コマンドを使う場合、実行されるのはWin32側なので、引数として渡すパス表記は普通に行えばよい。

 メモ帳(notepad.exe)を使いC:\Users\Shioda\sample.txtを開くには、以下のコマンドを実行する(後述するが、「\\」はbash側での逆スラッシュの解釈を止めるためのもの)。

$ notepad.exe c:\\Users\\Shioda\\sample.txt


WSLからWindows OS側のコマンドを呼び出す例(1)

 または、以下のようにも書ける。WSL側でWindows OS側のファイルパスを表記する場合、「\」は「\\」にするか、パス全体を引用符(シングルクオートかダブルクオート)で囲むこと。パス区切りとして「/」を利用する場合は(詳細は後述)、引用符で囲んだり、「\」でエスケープしたりする必要はない。

$ notepad.exe c:/Users/Shioda/sample.txt


WSLからWindows OS側のコマンドを呼び出す例(2)

 上記のコマンドはWSL側で起動されるが、実行はWin32側となり、パスを解釈するのはnotepad.exeなので、普通にWindows OSの作法で(パス区切りは「\」か「/」)、パスを指定すればよい。

 原則としてWSL内でWin32側のファイルパスを扱うことは何の問題もない。Linux側のコマンドは、「DrvFs」の形式でパスを記述すればよい。またWin32コマンドならば、従来通りWindows OSのパス(c:\〜)を記述すればよい。WSL内で実行されるWin32コマンドでは、必ず「〜.exe」と記述する決まりなので、下表のように覚えておけばいいだろう。

覚え方 パスの指定法
「exeあるならC:」 Win32コマンドは普通にC:\のパス指定を行う
「exeなければ/mnt」 Linuxコマンドは「DrvFs」で/mnt/cから始まるパス指定
パス指定の覚え方
 

 なお、現在のWindows 10 October 2018 Update(バージョン1809)では、Win32側からWSL側のファイルシステム(VolFs)をアクセスすることはできない。このため、Win32側のコマンドにVolFs内のパスを渡さないように注意する必要がある。

 Win32相互運用性では、エラーを避けるため、VolFs上でWin32コマンドを起動すると、自動的にカレントディレクトリを「C:\Windows\System32」に設定し、その上でコマンドを実行する。これは、Win32アプリケーションがカレントディレクトリに作業ファイルなどを作ろうとしてエラーになるのを避けるためだ。

WSL内でのWin32側パスの表記

 bashなどUNIX/Linux系のコマンドラインでは、「\(逆スラッシュ)」は、特別な意味を持つ文字であり、後続の文字と組にして特殊な文字などを表す場合に使われる。例えば、「\t」はタブコードを表す。また、「\"」とすることで、ダブルクオート文字そのものとなり、bashなどがダブルクオート文字に持たせている特殊な意味を無効にすることができる。また、逆スラッシュ文字自体は「\\」で表す。

 このため、WSL内でWin32側のパスを記述する場合、そのままでは、「\」が特殊な文字として解釈されてしまうため、以下のような記述方法を使う必要がある。前述のnotepad.exeの例では、\が抜けたパスが渡されてしまうのを防ぐため、\\と記述していた。

パスの表記法 説明
'c:\Windows\System32' 引用符(シングルクオートかダブルクオート文字)で囲む
c:\\Windows\\System32 「\\」で逆スラッシュ文字そのものを表現する
c:/Windows/System32 「/(スラッシュ)」をパス区切りとして使う
WSL内でWin32側のパスを記述する方法

 実は、Win32側のプログラムやWindows OSが提供するネイティブなAPIなどでは、「/(スラッシュ)」をパス区切りとして受け付けることができる。例えばコマンドプロンプト上では、「notepad c:\Users\Shioda\sample.txt」だけでなく、「notepad c:/Users/Shioda/sample.txt」も利用できる。ただし「/」は[Tab]キーによる自動補完がきかないので、入力は面倒である。

 これらには、それぞれ一長一短ある。ダブルクオート文字で囲む方法では、パスの最後に逆スラッシュがあると、末尾のダブルクオートの意味が打ち消されてしまうため、末尾に逆スラッシュを付けないように注意する必要がある。

 ただ、WindowsでCドライブのルートフォルダを表すには、どうしても「C:\」と表記する必要がある(C:だとCドライブのカレントディレクトリとなるため)。このような場合には、ダブルクオートではなく、シングルクオート文字で囲むか、後にスペースを挟むなどの必要が出てくる(パスの最後に付いたスペースは無視されるため)。

 「\\」で逆スラッシュ文字を表現する方法は、全ての逆スラッシュが2文字となるためにパスによっては打鍵数が増えてしまう。ただし、前述のようにヒストリー機能などを使えば、打鍵数を抑制することは可能だ。

 最後の「/(スラッシュ)」をパス区切りとして使う方法だが、Windows OSのAPIレベルでは、「/」もパス区切り文字として扱えるのだが、コマンドライン(cmd.exe)に関しては、「/」は内部コマンドのオプションとして解釈されるため、混同しないように\を使う必要がある(オプションの開始記号が「/」でなく、ファイル名を直接指定できるコマンドでは使える可能性がある)。

 このときには、シングルクオートで囲むか、「\\」で逆スラッシュ文字を表現する記法を使わなければならない。原則、オプション文字が「/」になっているならパス区切り文字として「/」を受け付けないと思ったほうがいいだろう。

 notepad.exeのようなWin32のGUIコマンドはオプションを持っていないため、たいてい受け入れることができる。ただ、2つを区別してパスを入力するのも煩わしく感じる。

 bashを使う場合、Win32のパス以外でも、多かれ少なかれ、逆スラッシュによる文字のエスケープと、引用符による文字列指定に慣れる必要がある。こうした特殊な意味を持つ記号文字の使い方は、ある意味、シェル利用時の1つのヤマと言っていいかもしれない。

 特に、コマンドの中でさらにスクリプトを指定する場合には、逆スラッシュや引用符による保護を組み合わせる必要がある。そのため、これらの記法とも身につけておく必要がある。

 なお、Linux系のシェルでは、シングルクオートとダブルクオートは別の意味を持つので、シングルクオートの代わりにダブルクオートは使わない方がよい。単純な利用では両者の区別は分からないのだが、思わぬ所で奇妙な動作を体験することになる。Windows OSではスペースを含むパスなどをダブルクオートでくくるため、つい使ってしまいたくなるが、WSL内では意識してシングルクオートを使おう。

wslpathコマンドでパスの相互変換が可能

 WSL内で、DrvFsとNTFSのパスを相互に変換するには、wslpathというコマンド(WSL用に作られたLinuxのプログラム)が利用できる。マニュアルページはないが、引数なしで実行すれば、簡単なヘルプ(UNIX/Linux系ではこのような表示をusageと呼ぶ)が表示される。

wslpathコマンドによるパスの変換 wslpathコマンドによるパスの変換
wslpathコマンドを使えば、NTFSのパスとDrvFsのパスを相互に変換できる。また、シェルの特殊文字である逆クオートを使うと、コマンドの実行結果を文字列として、他のコマンドの引数にすることが可能だ。

 Windows OS側の「c:\Windows\System32」というパスは、以下のコマンドで「/mnt/c/windows/system32」に変換できる。

$ wslpath 'c:\windows\system32'


パスを変換するwslpathコマンドの使用例

 bashでは、こうしたコマンドの出力を引数に組み込むこともできる。コマンドの引数の中で、逆クオート「`」(バッククオートとも。109日本語キーボードなら、[P]キーの右側にある[@]キーを、[Shift]キーと一緒に押したときに入力される文字)によって囲まれた部分は、その中をコマンドとして実行した結果(出力)に置き換わるからだ。

 「C:\temp\test.txt」というパスをwslpathコマンドでDrvFsのパスに変換して、コマンドの引数にするには、以下のようにする。最初の「ls -l」を「echo」に置き換えると引数がどうなっているのかを見ることができるだろう。

$ ls -l `wslpath 'c:\temp\test.txt'`


wslpathでパスを変換して、別のコマンドの引数にする例

Windows 10 バージョン1903からの新機能

 2019年5月に配布予定のWindows 10 バージョン1903(May 2019 Update)では、$wslという仮想ファイルサーバにアクセスすることで、VolFsのファイルをWin32側で扱うことができるようになる。このとき/home/shioda/sample.txtといったWSL側のファイルをメモ帳で開くには、以下のようなコマンドを実行すればよい(下記コマンドラインはWin32側であることに注意)。

> notepad.exe "\\wsl$\Ubuntu\home\shioda\sample.txt"


パスを変換してコマンドの引数にするコマンド

 また、WSL内からWin32相互運用性などでWin32コマンドを実行する場合には、WSL側のパスが自動変換されてWin32コマンドに渡される。

 例えば、WSL側のディレクトリにいる場合に「explorer.exe .」としてカレントディレクトリでエクスプローラーを起動させたり、notepad.exeのファイル指定をVolFs側のLinux方式によるパス指定で行ったりすることが可能になる。これにより、ますます、WSL側でWin32コマンドを使った作業が行いやすくなる。

Windows 10 バージョン1903の変更点 Windows 10 バージョン1903の変更点
Windows 10 バージョン1903(May 2019 Update)からは、VolFs側のパスを自然な形でWin32コマンドに渡せるようになる。これにより、ユーザーは、VolFsやDrvFsの区別を行わなくてもいいようになる。

 また、前述のwslpathコマンドは、VolFs側のパスをWSL$側のパスに変換することができる。なお日本語関係などでも動作の違いが見られるので注意してほしい。



 次回は、bashの機能にもう少し触れて見ることにする。また、コマンドとしてはパッケージのインストール関係について解説する予定だ。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。