はじめに
Linuxで一般ユーザが参照できないディレクトリをlsコマンド等から参照するときに苦労したことはないでしょうか。
sudoを併用しても上手くいかない、というパターン。
具体的には下記のようなパターンです。
「/unreadabledir」は一般ユーザが参照できないディレクトリとします。
$ sudo ls -la /unreadabledir/*
$ sudo su -
# ls -la /unreadabledir/*
最初はパターン1を実行して「あれー、出来ない、おかしいな」と思って、
パターン2のようにsuコマンドでrootユーザになってからlsすると参照できる、そんな経験はないでしょうか。
他にも、ワイルドカードが意図したとおりに動かないケースがあるかと思います。
この記事では、なぜパターン1が上手くいかないか、その仕組みから説明します。
TL;DR
- ワイルドカードを展開する主体は何か、理解しておくこと(シェルが展開する)
- どのタイミングでワイルドカードが展開されるのか理解しておくこと(コマンド実行前に展開され、コマンドに渡される)
前提知識
glob
シェルから複数ファイルを指定するコマンドを実行するときには、「*」や「?」を利用します。
これらのメタ文字を利用してファイル一覧を展開する仕組みを「glob」と言います。
これは多くのUNIX like/Linuxシステムのライブラリルーチンとして実装されています。
Linuxのドキュメントは下記をご覧ください。
glob(3)
http://linuxjm.osdn.jp/html/LDP_man-pages/man3/glob.3.html
誰がそのワイルドカードを展開するのか
シェルから何らかのコマンドを実行してファイルを処理する場合を考えましょう。
その実行主体は大きく以下の2つになります。
- シェル
- 実行コマンド
lsとfindでファイル一覧を取得するケースを考えてみます。
# "-1"は、1行1ファイル表示にするオプションです。
$ ls -1 /var/log/*log
/var/log/boot.log
/var/log/vboxadd-install.log
/var/log/vboxadd-install-x11.log
/var/log/VBoxGuestAdditions.log
/var/log/wpa_supplicant.log
/var/log/yum.log
# "-maxdepth n"は、探索する深さを制御するオプションです。
$ find /var/log -maxdepth 1 -name "*log"
/var/log/yum.log
/var/log/wpa_supplicant.log
/var/log/VBoxGuestAdditions.log
/var/log/vboxadd-install.log
/var/log/vboxadd-install-x11.log
/var/log/boot.log
上記のコマンドはいずれも同じ結果になります。(ソートの有無を除いて)
しかしながら、ワイルドカードを展開する主体が違います。
これが具体的にどのような違いになるのか、以下から見ていきます。
ケース1: 一般ユーザが参照できないディレクトリをsudoを併用して参照する
下記の場合を考えてみましょう。
Apacheのログディレクトリなど、一般ユーザが参照できないディレクトリに対し、sudoを併用して参照する場合です。
まず対象のディレクトリは下記のように、オーナがroot:root、パーミッションが700となっています。
このディレクトリ内のファイルのうち、「*log」にマッチするものを取得する方法を考えましょう。
$ ls -dl /var/log/httpd
drwx------. 2 root root 39 Oct 27 18:19 /var/log/httpd
「簡単簡単、sudoをつけてlsを実行するだけじゃないか」と思うでしょう。
しかし下記コマンドは上手くいきません。
$ sudo ls -1 /var/log/httpd/*log
ls: cannot access /var/log/httpd/*log: No such file or directory
しかし下記コマンドは上手くいきます。
$ sudo ls -1 /var/log/httpd
access_log
error_log
findも試してみましょう。
こちらも対象のファイルを取得することができたようです。
$ sudo find /var/log/httpd -maxdepth 1 -name "*log"
/var/log/httpd/error_log
/var/log/httpd/access_log
ls(ワイルドカード有り)が上手くいかなかったのはなぜでしょうか。
それはシェルが下記のような挙動になるからです。
- コマンドラインにワイルドカード「*」が存在しているため、シェルはglob展開を試みる。
- 「/var/log/httpd/*log」のglob展開は、「/var/log/httpd」ディレクトリの読み取り権限が必要。
- glob展開は、当該シェルのプロセスのuid/gidの権限(つまり、ログインして操作しているユーザ)で実行される。
- この操作を行ったユーザでは「/var/log/httpd」を参照する権限がないため、ファイル一覧を取得できない。
- この場合、シェル(※)は「*」を展開せず、そのままの文字列をlsコマンドに渡す。
- 「/var/log/httpd/*log」というファイルは存在しないため、lsは「No such file or directory」を標準エラー出力に出力する。
※項番5について、OS・シェルによって挙動が違う可能性があります。
マッチするファイルを取得しようがないため、空文字列に置き換えられてしまう場合もあるかもしれません。
少なくともCentOS 7にインストールされていたbash 4.2.46は、デフォルトでは上記のような挙動となりました。
なお、この挙動を確認するために下記のようにechoコマンドを実行してみます。
挙動の違いが明確にわかります。
# 対象ディレクトリを参照できる場合はglob展開する
$ echo /var/log/*log
/var/log/boot.log /var/log/lastlog /var/log/maillog /var/log/tallylog /var/log/vboxadd-install.log /var/log/vboxadd-install-x11.log /var/log/VBoxGuestAdditions.log /var/log/wpa_supplicant.log /var/log/yum.log
# 対象ディレクトリを参照できない場合はglob展開しない。
$ echo /var/log/httpd/*log
/var/log/httpd/*log
一方、findが上手くいったのは何故でしょうか。
それは、引数「-name "*log"」で指定されたワイルドカード「*」はシェルによってglob展開されず、findコマンド自身で展開されたからです。
# 実際にfindコマンド内でglob(3)関数を呼び出しているかどうかは不明ですが。
ケース2: scpでワイルドカードを利用する
リモートのサーバからログファイルを持ってくるときに、パターンを指定したい場合はよくあると思います。
たとえば、「message-201710*」にマッチするファイルを取得したい場合を考えましょう。
なにも考えなければ、下記のように指定すると思います。
$ scp remote-host:/var/log/message-201710* ~/dir_to_store_logs/
これまでの説明を理解していれば、上記コマンドが上手く動かない可能性があることは分かりますね。
上記のワイルドカードは、ローカルのホスト上の「remote-host:/var/log/message-201710*」にマッチするかどうか、glob展開が行われます。
マッチするファイルがなければ、そのままの文字列がscpコマンドの引数として渡されます。
ほとんどの場合、このようなディレクトリは存在していないため、恐らく意図したとおりに動作すると思います。
しかしながらこの場合、ローカルのホストでのglob解釈は不要であるため、エスケープするかシングルクォート/ダブルクォートで括るようにした方が良いでしょう。
ちなみにこの場合、scpコマンドは、第一引数として渡された「remote-host:/var/log/message-201710*」をもとに、リモートのホスト上でマッチするファイルを検索します。
マッチするファイルが見つかり次第、それらを全てローカルの「~/dir_to_store_logs/」にダウンロードしてきます。
これはfindコマンドのように、コマンド自体がワイルドカードを展開するパターンといえます。
おわりに
UNIX/Linuxのシェルに慣れていないと、パッと見不可解な状況に直面したりすることはあるでしょう。
大抵の場合、別の回避策があるため、分からないままにしていることも少なくないかと思います。
しかしながら、その仕組み、動きをきちんと理解することによって、他のケースにも対応できるようになります。
漠然とした曖昧な理解のままで良しとせず、きちんと調べて理解するようにしましょう。