• ベストアンサー

シェルスクリプト中でシンボリックリンクのリンク先を比較したい

シェルスクリプト中で2つのシンボリックリンクのリンク先を確実に比較する方法を考えています。 当初ls -lの出力を比較してやろうと思ったのですが、リンク先の情報はls -lの最後のカラムにあるため、 万一ユーザー名やファイル名に空白が混じっていた場合、リンク先ではない他の情報を比較してしまうのです。 そこで、リンク先を確認する専用のコマンドがシェルに用意されているかも知れないと思い、質問に参りました。 もしくはls -lでなく、もっと確実にリンク先情報を確認するための方法を編み出した方は教えてください。

質問者が選んだベストアンサー

  • ベストアンサー
  • Lean
  • ベストアンサー率72% (435/603)
回答No.6

Red Hat Linux 8.0(2002年10月)が出たときには、GNU coreutils(2003年4月)はまだ存在していません。 この頃、fileutils, shellutils, textutilsというのがありましたが、その後一つとなってcoreutilsになっています。 ls、cp、mv等のファイル操作に関するコマンドは、fileutilsに入っていますが、この中にreadlinkは含まれていません。 で、readlinkが含まれるパッケージをRPM Searchで検索したらtetexというTeXのパッケージに含まれているようです。 ○RPM Search  http://rpm.pbone.net/index.php3/stat/4/idpl/2406513/com/tetex-1.0.7-57.i386.rpm.html

zyousuke
質問者

お礼

Leanさん、ご返信ありがとうございます。 tetexをインストールするには あらかじめdialogとtetex-fontsをインストールしておく必要があるようです。 tetexを含め、この3つのRPMをインストールしたら見事readlinkが使えるようになりました。 これで、簡単にリンク先を確認できるようになりました。 そして、便利なサイトのご紹介ありがとうございます。 日本語が無いのが少々残念ですが、 このサイトでは、コマンド名を入力すると、過去版を含めた各ディストリビューション用のRPMが検索できるのですね! しかも、それがそのまま入手先のアドレスになっていて、簡単にダウンロードできてしまうとは! トップページをお気に入りに登録しました。 これで、コマンド名さえ分かれば簡単にRPM名を確認することができます。 肝心なコマンド名が分からない場合は利用できませんが。。。

その他の回答 (5)

  • ham_kamo
  • ベストアンサー率55% (659/1197)
回答No.5

No.1です。 readlinkというコマンドは私も知りませんでした。一番スマートなお答えが出ていますが、一応レスします。 > 今回教えていただいたsedの書き方は、どのような意味でしょうか。 > 行頭から最初の"> "までを消すという意味でしょうか。 そうです。[^>]*というのが、">"以外の文字の0個以上の連続、という正規表現になります。したがって">"が複数ある場合、最初の"-> "までがマッチして、それを削除しています。 > ユーザー名に" -> "が含まれていた場合どうなるか試してみました。 ユーザ名に" -> "ですか。。。理論的には作れますが、実運用上あり得るのでしょうか? 一応回避する方法を書いておくと、lsに-nオプションをつけて、ユーザ名とグループ名をUID,GIDで表示させることができます。 $ ls -ln tmp1 lrwxrwxrwx 1 504 100 13 9月 24 18:23 tmp1 -> /tmp/file AAA $ ls -ln tmp1 | sed 's/^[^>]*> //' /tmp/file AAA しかし、No.2の補足に > 私が今作成しているシェルスクリプトはFILEという変数へフルパスのファイル名を代入してそれを次々評価する形式になっています。 とありますが、シェルで処理する以上、ファイル名にシェルに食われる特殊文字が含まれていると、どんな方法を用いても結局無理なケースがでてきてしまうような気がします。 「FILEという変数へのフルパス」へはどうやって代入しているのでしょうか。私が普通に処理すると、for FILE in `find / -type l` というfor文を使用するところですが、これもファイル名にスペースが含まれると誤作動します。それ以外の代入方法でも、' や " などが含まれていると、やはり正常に動作するとは限りません。 その辺りは大丈夫でしょうか?

zyousuke
質問者

お礼

ham_kamoさん、ご返信ありがとうございます。 No.4:Leanさんから決定的な回答をいただきましたが、引き続きご回答ありがとうございます。 ○以外を正規表現で表すと[^○]となるのですね! ワイルドカードの[]と混同してなかなか理解できませんでした。 しかも^は行頭を表す正規表現なので ^[^>]*は行頭から(行頭か>)の0個以上の連続??? のように解釈して意味不明でした。 しかし、もう理解出来ました。 $ echo abacad | sed s/[^a]//g aaa 例えば、こうやればa以外を全部消してくれるのですね。 ls -nを確認しました。 これを使えばどんなユーザー名をでも対応できますね。 さて、いいかんげんにしろ!と言われそうですがファイル名に" -> "が含まれていた場合、どうなるか試してみました。 しかも今度はファイル名中2か所に" -> "を含めています。 $ ls -l "l -> n -> k" lrwxrwxrwx 1 user user 13 9月 27 23:41 l -> n -> k -> /tmp/linktest $ ls -l "l -> n -> k" | sed 's/^[^>]*> //' n -> k -> /tmp/linktest このように行頭から最初の"> "までが消されて、残り全てが表示されました。 ところで、今回のテストで作成したような>や空白など特殊文字を含むユーザー名は実運用では、はっきり言ってありえないと思います。 しかし論理的には作成できてしまうため、この0.0000...1パーセントに対応するために、いつも頭を悩ませ大事な時間がどんどん経過してゆきます。 だから私は周りの誰よりもプログラミングが遅く、テスト仕様書の作成も遅いのです。 そして、これが能力査定にひびき、いつまでたっても昇進できないというわけです。。。 ちなみに今作成しているスクリプトは会社とは何の関係もなく、ただ私が趣味で作成しています。 スクリプト中でFILEという変数にどのようなファイル名が代入されても正しく評価させるため、評価時は$FILEを""でくくって渡しています。 自宅でプログラミングをやる場合、どんなに時間をかけても能力査定には一切ひびかないため、1個の機能を仕上げるのに究極に時間がかかっています! 何事もほどほどが大事なのですけどね~

  • Lean
  • ベストアンサー率72% (435/603)
回答No.4

No.2です。 No.2を書いた後、気がついたのですがGNU coreutilsの中にreadlinkコマンドがあるようなので、readlinkコマンド使用すれいいと思います。 $ ls -l 合計 4 lrwxrwxrwx 1 lean lean 16 9月 24 17:06 linktest -> /tmp/link-> test $ readlink --version readlink (GNU coreutils) 5.97 Copyright (C) 2006 Free Software Foundation, Inc. This is free software. You may redistribute copies of it under the terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>. There is NO WARRANTY, to the extent permitted by law. 作者 Dmitry V. Levin. $ readlink linktest /tmp/link-> test

zyousuke
質問者

お礼

Leanさん、ご返信ありがとうございます。 perlと同様にシェルにもreadlinkがあるのですね。 よって迷わず、このreadlinkを使用すべきですね。 しかし、私のリナックスには、なんとreadlinkがありませんでした。 coreutilsについて調べました。 coreutilsはechoなどシェルの基本的なコマンドを提供するとありました。 しかし、私のリナックスではechoはあるのですがreadlinkは無かったのです。 CD3枚組のRed Hat Linux 8.0をフルインストールしたところ、readlinkが使えました。 そこで、この3枚のCDのどこかにcoreutilsのrpmが格納されていると思い、検索したのですが、 見つかりませんでした。 どうも、Red Hat Linux 8.0では別のrpm名でreadlinkが提供されているような気がします。 1個1個rpmをインストールしてreadlinkが使えるか試していたのですが、 すさまじい量のrpmを前に気が遠くなってきました。 Leanさん、もしrpmをご存知でしたら、教えてもらえますか。

  • ham_kamo
  • ベストアンサー率55% (659/1197)
回答No.3

No.1です。ちょっと改良してみました。 $ ls -l tmp1 tmp2 lrwxrwxrwx 1 fcuser users 13 9月 24 18:23 tmp1 -> /tmp/file AAA lrwxrwxrwx 1 fcuser users 16 9月 24 18:25 tmp2 -> /tmp/link-> test とすると、 $ ls -l tmp1 | sed 's/^[^>]*> //' /tmp/file AAA $ ls -l tmp2 | sed 's/^[^>]*> //' /tmp/link-> test となりました。ただし、ls -l で指定したファイルがシンボリックリンクではない場合、上記は正常に動作しません。 No.2さんの方法の方がスマートかと思います。

zyousuke
質問者

お礼

ham_kamoさん、再返信ありがとうございます。 今回教えていただいたsedの書き方は、どのような意味でしょうか。 行頭から最初の"> "までを消すという意味でしょうか。 実際に試したところ、うまくリンク先を取得できました。 私が今作成しているスクリプトでは、ファイル形式がシンボリックリンクだった場合のみ当該処理を実行するようにしていますので、ham_kamoさんが懸念している問題も解決済みです。 しかし、この手法にも弱点があると思います。 ユーザー名に" -> "が含まれていた場合どうなるか試してみました。 $ ls -l "link -> test" lrwxrwxrwx 1 test -> user test -> user 17 9月 24 21:33 link -> test -> /tmp/link -> test $ ls -l "link -> test" | sed 's/^[^>]*> //' user test -> user 17 9月 24 21:33 link -> test -> /tmp/link -> test 見てください。 このように最初に登場する" -> "まで lrwxrwxrwx 1 test -> が消されて残りの user test -> user 17 9月 24 21:33 link -> test -> /tmp/link -> test が表示されてしまいました。 うーん、脳味噌が混乱してきますね~。 ham_kamoさん、他にも何か妙案はありますでしょうか。

  • Lean
  • ベストアンサー率72% (435/603)
回答No.2

No.1のお礼にあるようなシンボリックリンクのような場合だと下記のような感じでしょうか? $ ls --version ls (GNU coreutils) 5.97 Copyright (C) 2006 Free Software Foundation, Inc. This is free software. You may redistribute copies of it under the terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>. There is NO WARRANTY, to the extent permitted by law. Written by Richard Stallman and David MacKenzie. $ ls -lQ linktest lrwxrwxrwx 1 lean lean 16 9月 24 17:06 "linktest" -> "/tmp/link-> test" $ ls -lQ linktest | cut -d'"' -f4 /tmp/link-> test $ perl -e "print readlink './linktest' ;" /tmp/link-> test

zyousuke
質問者

補足

Leanさん、ご返信ありがとうございます。 lsに-Qオプションを指定するとファイル名を""で括って表示するのですね! この"を区切り文字としてcutに渡すとは、すごい巧妙な手法ですね! しかしLeanさんに考えていただいた、この手法にも弱点がありました。 それはユーザー名やファイル名に"が含まれていた場合です。 ユーザー名やファイル名に"が含まれていた場合、cutに渡すカラム数が変動し、 確実にリンク先情報を取得することは、できません。 もう1つ、考えていただいた手法は、私の要望にとても近いです。 すなわち、リンク先情報を取得する専用のコマンドを知りたいというものです。 readlinkがリンク先情報を取得するコマンドでしょうか。 しかし、このコマンドはシェルではなくperlが提供しているので、perlを呼び出して、その中で使用するということでしょうか。 perlの使い方はほとんど分からないのですが、次の認識で合っているでしょうか。 -eはperlをスクリプトとしてではなく1個のコマンドとして実行する場合に必要なオプションでしょうか。 -eを省略するとファイルがないというエラーになってしまいます。 そして実行するコマンド行は""で括らなければいけないのですね。 さらにreadlinkの引数は''で括らなければいけないのですね。 私が今作成しているシェルスクリプトはFILEという変数へフルパスのファイル名を代入してそれを次々評価する形式になっています。 お教えいただいたperlのコマンドを使ってこれをやろうとすると perl -e "print readlink '$FILE' :" となり、これを試したところうまくリンク情報を取得できました。 しかし、この手法も完璧とはいえず、ファイル名が'を含んでいた場合、エラーになってしまいます。 perlまたはreadlinkへ渡す前に$FILEが展開されてしまうのでしょうか。 ですのでperlを使用する場合はクォーティングを工夫する必要があり、すごい難しいと思いました。 そう言うわけで、今、私が選択肢として考えているのは No.1:ham_kamoさんのls -lとsedを組み合わせた手法を改良してみる No.2:Leanさんのls -lQとcutを組み合わせた手法を改良してみる No.3:当初私が考えたls -lとawkを組み合わせた手法を改良してみる です。 引き続き皆さんからの助言大募集です。 perlの情報も大募集です。 perlは学生時代に少し習ったのですが、うーん全然覚えてない。。。

  • ham_kamo
  • ベストアンサー率55% (659/1197)
回答No.1

たとえば、 $ ls -l tmp1 tmp2 lrwxrwxrwx 1 fcuser users 13 9月 24 09:19 tmp1 -> /tmp/file AAA lrwxrwxrwx 1 fcuser users 12 9月 24 09:19 tmp2 -> /tmp/fileBBB となっている場合(tmp1の方はスペースが入っている)、以下のようにしたらできました。 $ symlink1=`ls -l tmp1 | sed 's/^.*> //'` $ symlink2=`ls -l tmp2 | sed 's/^.*> //'` $ echo $symlink1 /tmp/file AAA $ echo $symlink2 /tmp/fileBBB

zyousuke
質問者

お礼

ham_kamoさん、ご返信ありがとうございます。 なるほど、シンボリックリンクの場合、ls -lの表示に>があることを利用して 行頭から>+空白までを消してしまうという技ですね。 しかし、この方法の場合リンク先に>という文字が混じっていた場合、正しいリンク先を取得できませんでした。 $ ls -l linktest lrwxrwxrwx 1 test test 16 9月 24 11:42 linktest -> /tmp/link-> test $ ls -l linktest | sed 's/^.*> //' test リンク先が"/tmp/link-> test"の場合"/tmp/link-> test"でなく"test"を取得してしまうのです。 これを考慮するとsedを使用して確実にリンク先情報を取得するのは、すごい難しいと思います。 ham_kamoさん、他にも何かひらめいたら教えてください。

関連するQ&A