Adding Pathname#abspath()

 SUSv3のrealpath(3)ã®é …ã«ã¯ã€å­˜åœ¨ã—ãªã„ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒã‚ã£ãŸã‚‰
errno=ENOENT を返すべし(shall fail)とあるのですが、*BSDでは最後の
コンポーネントに限り存在しなくてもいい(All but the last component
must exist)å®Ÿè£…ã«ãªã£ã¦ã„ã¦ã€å®Ÿç”¨ä¸Šãã®æ–¹ãŒä¾¿åˆ©ãªå ´åˆãŒå°‘ãªã‹ã‚‰ãš
あります。

 Pathname#realpath()はSUSv3の仕様を踏襲した実装になっていますが、
Pathname自体は生成時点で存在しないパスも許しますし、これから生成
しようとするファイルまたはディレクトリのパス名を正規化するような
操作ができると便利です。

 といってPathname#realpath()の仕様を変えるのは好ましくないので、
#abspath()ã¨ã„ã†åˆ¥ãƒ¡ã‚½ãƒƒãƒ‰ã®è¿½åŠ ã‚’ææ¡ˆã—ã¾ã™ã€‚ã©ã†ã§ã—ã‚‡ã†ã‹ã€‚

Index: ChangeLog

— ChangeLog (revision 19332)
+++ ChangeLog (working copy)
@@ -1,3 +1,9 @@
+Sun Sep 14 05:51:02 2008 Akinori MUSHA [email protected]
+

    • lib/pathname.rb (Pathname#realpath_rec, Pathname#abspath): Add
  • Pathname#abspath, a variant of #realpath that allows the last
  • component of pathname to be nonexistent.

Sun Sep 14 03:20:03 2008 Tanaka A. [email protected]

  • include/ruby/oniguruma.h (onigenc_get_prev_char_head): add end
    Index: lib/pathname.rb
    ===================================================================
    — lib/pathname.rb (revision 19332)
    +++ lib/pathname.rb (working copy)
    @@ -76,9 +76,9 @@

=== Core methods

-# These methods are effectively manipulating a String, because that’s
all a path
-# is. Except for #mountpoint?, #children, and #realpath, they don’t
access the
-# filesystem.
+# These methods are effectively manipulating a String, because that’s
+# all a path is. Except for #mountpoint?, #children, #abspath and
+# #realpath, they don’t access the filesystem.

- +

- #join

@@ -89,6 +89,7 @@

- #relative_path_from

- #each_filename

- #cleanpath

+# - #abspath

- #realpath

- #children

- #mountpoint?

@@ -411,7 +412,7 @@ class Pathname
end
private :cleanpath_conservative

  • def realpath_rec(prefix, unresolved, h)
  • def realpath_rec(prefix, unresolved, h, strict, last = true)
    resolved = []
    until unresolved.empty?
    n = unresolved.shift
    @@ -428,14 +429,20 @@ class Pathname
    prefix, *resolved = h[path]
    end
    else
  •      s = File.lstat(path)
    
  •      begin
    
  •        s = File.lstat(path)
    
  •      rescue Errno::ENOENT => e
    
  •        raise e if strict || !last || !unresolved.empty?
    
  •        resolved << n
    
  •        break
    
  •      end
         if s.symlink?
           h[path] = :resolving
           link_prefix, link_names = split_names(File.readlink(path))
           if link_prefix == ''
    
  •          prefix, *resolved = h[path] = realpath_rec(prefix, 
    

resolved + link_names, h)

  •          prefix, *resolved = h[path] = realpath_rec(prefix, 
    

resolved + link_names, h, strict, unresolved.empty?)
else

  •          prefix, *resolved = h[path] = realpath_rec(link_prefix, 
    

link_names, h)

  •          prefix, *resolved = h[path] = realpath_rec(link_prefix, 
    

link_names, h, strict, unresolved.empty?)
end
else
resolved << n
@@ -449,19 +456,33 @@ class Pathname
private :realpath_rec

  • Returns a real (absolute) pathname of +self+ in the actual

filesystem.

  • The real pathname doesn’t contain symlinks or useless dots.

  • Returns the real (absolute) pathname of +self+ in the actual

  • filesystem not containing symlinks or useless dots.

  • All components of the pathname must exist when this method is

  • called.

    No arguments should be given; the old behaviour is obsoleted.

    def realpath
  • abspath(true)
  • end
  • Returns the real (absolute) pathname of +self+ in the actual

filesystem.

  • The real pathname doesn’t contain symlinks or useless dots.

  • The last component of the pathname can be nonexistent unless

  • +strict+ is set to true.

  • def abspath(strict = false)
    path = @path
    prefix, names = split_names(path)
    if prefix == ‘’
    prefix, names2 = split_names(Dir.pwd)
    names = names2 + names
    end
  • prefix, *names = realpath_rec(prefix, names, {})
  • prefix, *names = realpath_rec(prefix, names, {}, strict)
    self.class.new(prepend_prefix(prefix, File.join(*names)))
    end

Index: test/pathname/test_pathname.rb

— test/pathname/test_pathname.rb (revision 19332)
+++ test/pathname/test_pathname.rb (working copy)
@@ -281,13 +281,45 @@ class TestPathname < Test::Unit::TestCas
rescue TypeError
end
Dir.mktmpdir(‘rubytest-pathname’) {|dir|

  •  assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") }
     File.symlink("not-exist-target", "#{dir}/not-exist")
     assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") }
    
  •  File.symlink("../#{File.basename(dir)}/./not-exist-target", 
    

“#{dir}/not-exist2”)

  •  assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist2") }
    
  •  File.open("#{dir}/exist-target", "w") {}
    
  •  File.symlink("../#{File.basename(dir)}/./exist-target", 
    

“#{dir}/exist”)

  •  assert_nothing_raised { realpath("#{dir}/exist") }
     File.symlink("loop", "#{dir}/loop")
     assert_raise(Errno::ELOOP) { realpath("#{dir}/loop") }
    

    }
    end

  • def abspath(path)

  • Pathname.new(path).abspath.to_s

  • end

  • def test_abspath

  • begin

  •  File.symlink(nil, nil)
    
  • rescue NotImplementedError

  •  return
    
  • rescue TypeError

  • end

  • Dir.mktmpdir(‘rubytest-pathname’) {|dir|

  •  assert_nothing_raised { abspath("#{dir}/not-exist") }
    
  •  assert_raise(Errno::ENOENT) { 
    

realpath(“#{dir}/not-exist/not-exist-child”) }

  •  File.symlink("not-exist-target", "#{dir}/not-exist")
    
  •  assert_nothing_raised { abspath("#{dir}/not-exist") }
    
  •  File.symlink("../#{File.basename(dir)}/./not-exist-target", 
    

“#{dir}/not-exist2”)

  •  assert_nothing_raised { abspath("#{dir}/not-exist2") }
    
  •  File.open("#{dir}/exist-target", "w") {}
    
  •  File.symlink("../#{File.basename(dir)}/./exist-target", 
    

“#{dir}/exist”)

  •  assert_nothing_raised { abspath("#{dir}/exist") }
    
  •  File.symlink("loop", "#{dir}/loop")
    
  •  assert_raise(Errno::ELOOP) { abspath("#{dir}/loop") }
    
  • }
  • end
  • def descend(path)
    Pathname.new(path).enum_for(:descend).map {|v| v.to_s }
    end

 名前が良くなかったですね。Pathname#resolve()でどうでしょうか。

At Sun, 14 Sep 2008 06:11:55 +0900,

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B!!L>A0$,NI$/$J$+$C$?$G$9$M!#e(BPathname#resolve()e$B$G$I$&$G$7$g$&$+!#e(B

e$B!!$H$$$C$Fe(BPathname#realpath()e$B$N;EMM$rJQ$($k$N$O9%$^$7$/$J$$$N$G!"e(B
#abspath()e$B$H$$$&JL%a%=%C%I$NDI2C$rDs0F$7$^$9!#$I$&$G$7$g$&$+!#e(B

abspath e$B$be(B resolve e$B$b$I$C$A$bF0:n$rFI$_<h$l$k5$$,$7$^$;$s!#e(B

realdir e$B$H$$$&$N$r9M$(IU$$$?$s$G$9$,!“e(Bfilename e$B$bJQ$($k$3$He(B
e$B$,$”$k$N$G$=$l$b$^$@$&$^$/$J$$$J$!!"$H$+!#e(B

At Sun, 14 Sep 2008 23:20:56 +0900,
Tanaka A. wrote:

realdir というのを考え付いたんですが、filename も変えること
があるのでそれもまだうまくないなぁ、とか。

では、 expand_path があるので resolve_path ですかねえ。

resolved_path もないことはないと思いますが、動詞の方が実際に
ファイルシステムにアクセスするニュアンスが出ていると思います。

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B$G$O!“e(B expand_path e$B$,$”$k$N$Ge(B resolve_path e$B$G$9$+$M$(!#e(B

resolved_path e$B$b$J$$$3$H$O$J$$$H;W$$$^$9$,!“F0;l$NJ}$,<B:]$Ke(B
e$B%U%!%$%k%7%9%F%`$K%”%/%;%9$9$k%K%e%"%s%9$,=P$F$$$k$H;W$$$^$9!#e(B

realpath e$B$H;w$?F0:n$G$"$k46$8$,$7$J$$$s$G$9$h$M$'!#e(B

At Mon, 22 Sep 2008 12:43:18 +0900,
Tanaka A. wrote:

In article [email protected],
“Akinori MUSHA” [email protected] writes:

では、 expand_path があるので resolve_path ですかねえ。

resolved_path もないことはないと思いますが、動詞の方が実際に
ファイルシステムにアクセスするニュアンスが出ていると思います。

realpath と似た動作である感じがしないんですよねぇ。

 似た動作の想起を条件にするのは、 realpath を含む名前にするか、
あるいは realpath ã«å¼•æ•°ã‚’è¿½åŠ ã™ã‚‹ã‹ã®äºŒæŠžã¨è€ƒãˆã¦ã‚ˆã„ã§ã—ã‚‡ã†ã‹ã€‚

 であれば私は後者を推したいです。フラグ引数が廃止された過去が
あるので抵抗があったのですが、もう数年経っているのでトラブルは
ないでしょうね。

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B!!;w$?F0:n$NA[5/$r>r7o$K$9$k$N$O!"e(B realpath e$B$r4^$`L>A0$K$9$k$+!“e(B
e$B$”$k$$$Oe(B realpath e$B$K0z?t$rDI2C$9$k$+$NFsBr$H9M$($F$h$$$G$7$g$&$+!#e(B

real e$B$H$$$&C18l$,F~$C$F$l$P;w$F$$$k$h$&$K;W$$$^$9!#e(B

e$B!!$G$“$l$P;d$O8e<T$r?d$7$?$$$G$9!#%U%i%00z?t$,GQ;_$5$l$?2a5n$,e(B
e$B$”$k$N$GDq93$,$“$C$?$N$G$9$,!”$b$&?tG/7P$C$F$$$k$N$G%H%i%V%k$Oe(B
e$B$J$$$G$7$g$&$M!#e(B

e$B%U%i%0$h$j0c$&L>A0$N$[$&$,$$$$$s$8$c$J$$$+$H$$$&5$$,$7$^$9!#e(B

e$B$3$N7o$K$D$$$F$OF0E*$K$I$A$i$+$N5sF0$rA*$S$?$$$H$$$&$3$H$O$Je(B
e$B$5$=$&$J5$$,$7$^$9$7!"L>A0$,5sF0$rI=$9$H%W%m%0%i%`$,$o$+$j$de(B
e$B$9$/$J$k$N$G!#e(B

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B!!$7$+$7!“e(B real e$B$H$$$&C18l$,F~$k$N$K<B:]$KB8:_$7$J$$%Q%9$rJV$9e(B
e$B$N$O4qL/$G$O$J$$$G$7$g$&$+!#$”$/$^$Ge(B realpath e$B$H$$$&4{CN$NL>A0e(B
e$B$+$i$7$+5!G=$OA[5/$5$l$J$$$H;W$$$^$9!#e(Breal_path e$B$O5$;}$A0-$$$7!"e(B
realpath e$B$de(B realpath e$B$bE,Ev$J$b$N$,;W$$$D$-$^$;$s$G$7$?!#e(B
bsd_realpath, realpath_butlast, realpath_till_penult,
almost_realpath, not_quite_realpath, …

e$B4qL/$K$J$k$+$I$&$+$OL>A0$N:n$j$+$?<!Bh$H;W$$$^$9!#$?$H$($Pe(B
realdir e$B$H$9$l$P!"e(Breal e$B$J$N$Oe(B dir
e$B$H<u$1<h$l$k$G$7$g$&!#e(B
dir e$B$@$1$,e(B real e$B$H$b<u$1<h$l$k$N$,LdBj$J$N$G$9$,!#e(B

e$B!!F1$85!G=$O4{B8$N8x3+$*$h$SFbIt%a%=%C%I$r;H$C$F$b4JC1$K$O<BAue(B
e$B$G$-$J$+$C$?$N$G$J$s$H$+F~$l$?$$$H;W$&$N$G$9$,!#e(B

e$B$(!<$H!"e(B

if path.exist?
path.realname
elsif path.dirname.directory?
path.dirname.realpath + path.basename
else
raise Errno::ENOTDIR
end

e$B$/$i$$$G$O$&$^$/$$$-$^$;$s$+$M!#e(B

e$B!!e(B~ e$B$NE83+$HAjBP%Q%9$NE83+$r9T$&%a%=%C%I$,e(B expand_path e$B$J$N$G!“e(B
symlink e$B$N2r7h$HAjBP%Q%9$NE83+$r9T$&%a%=%C%I$,e(B resolve_path e$B$He(B
e$B$$$&$N$O0-$/$J$$$H;W$C$F$$$^$9!#e(Brealpath e$B$H$N4XO”$O$5$[$IBg;ve(B
e$B$G$7$g$&$+!#e(B

symlink e$B$N2r7h$HAjBP%Q%9$NE83+$r9T$&$H$$$&$N$O$^$5$Ke(B
realpath e$B$G!"K>$^$l$F$$$k5!G=$+$i$Oe(B realpath
e$B$K6a$$$G$7$g$&!#e(B

e$B$"$H!“e(Bexpand_path e$B$Oe(B ~
e$B$r07$&$N$,$$$d$i$7$$$H;W$C$F$$$F!”$Ge(B
e$B$-$l$PL5;k$7$?$$$H$3$m$G$9!#e(B

At Fri, 26 Sep 2008 02:29:43 +0900,
Tanaka A. wrote:

奇妙になるかどうかは名前の作りかた次第と思います。たとえば
realdir とすれば、real なのは dir と受け取れるでしょう。
dir だけが real とも受け取れるのが問題なのですが。

 realdirpath ã¨ã‹ï¼Ÿå‹æ‰‹ãªé€ èªžã®ã‚ˆã†ã§æ°—ãŒå¼•ã‘ã¾ã™ãŒâ€¦ã€‚

raise Errno::ENOTDIR

end

くらいではうまくいきませんかね。

最初の elsif の前に

elsif path.symlink?
  dir, base = path.readlink.split
  (path.dirname + dir).realpath + base

も必要です。これだとメソッドにしたくなります。

 ~ の展開と相対パスの展開を行うメソッドが expand_path なので、
symlink の解決と相対パスの展開を行うメソッドが resolve_path と
いうのは悪くないと思っています。realpath との関連はさほど大事
でしょうか。

symlink の解決と相対パスの展開を行うというのはまさに
realpath で、望まれている機能からは realpath に近いでしょう。

 *BSD ではそれが realpath ですしね。ただ、そのために混乱を引き
ずりそうなので新しい名前がいいかもしれないと思いました。

あと、expand_path は ~ を扱うのがいやらしいと思っていて、で
きれば無視したいところです。

 ~ の展開機能はどのように提供するのがいいんでしょう。手元では
Pathname.new(filespec).expand_path.realpath のような並びになる
ことが多いです。

 田中さん、この件はどうでしょう。

 機能の必要性について補足しますと、

mkdir dir
ln -s dir/file link

とあったときに open(“link”, “w”) などとすると dir/file に新規に
ファイルが作られるわけですが、そのパスを事前に知るために使えます。
(そして親ディレクトリのパーミッションをチェックするなど)

 名前については、改めて realdirpath を推します。realpath との
明らかな連関を想起させますし、 real-directory path と書き下して
みれば、 real-directory は最後の1つ前のコンポーネント、すなわち
親ディレクトリまで展開することを、 path はディレクトリに限らない
ことをよく表しています。

 いかがでしょうか。

(以下引用)
At Fri, 26 Sep 2008 08:12:41 +0900,

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B!!5!G=$NI,MW@-$K$D$$$FJdB-$7$^$9$H!"e(B

mkdir dir
ln -s dir/file link

e$B$H$“$C$?$H$-$Ke(B open(“link”, “w”) e$B$J$I$H$9$k$He(B dir/file e$B$K?75,$Ke(B
e$B%U%!%$%k$,:n$i$l$k$o$1$G$9$,!”$=$N%Q%9$r;vA0$KCN$k$?$a$K;H$($^$9!#e(B
(e$B$=$7$F?F%G%#%l%/%H%j$N%Q!<%_%C%7%g%s$r%A%'%C%/$9$k$J$Ie(B)

[ruby-dev:36585] e$B$rFI$_JV$7$F$$$F!"e(B

elsif path.symlink?
  dir, base = path.readlink.split
  (path.dirname + dir).realpath + base

e$B$H$$$&$N$,I,MW$@$H$$$&OC$,$“$j$^$7$?$,!”$3$l$C$F$J$<$G$7$?$C$1e(B?

basename e$B$N07$$$K4|BT$5$l$k$3$H$K$D$$$F9M$($F$$$k$N$G$9$,!#e(B

e$B!!L>A0$K$D$$$F$O!"2~$a$Fe(B realdirpath e$B$r?d$7$^$9!#e(Brealpath e$B$H$Ne(B
e$BL@$i$+$JO"4X$rA[5/$5$;$^$9$7!"e(B real-directory path e$B$H=q$-2<$7$Fe(B
e$B$_$l$P!“e(B real-directory e$B$O:G8e$Ne(B1e$B$DA0$N%3%s%]!<%M%s%H!”$9$J$o$Ae(B
e$B?F%G%#%l%/%H%j$^$GE83+$9$k$3$H$r!"e(B path e$B$O%G%#%l%/%H%j$K8B$i$J$$e(B
e$B$3$H$r$h$/I=$7$F$$$^$9!#e(B

e$B$3$NL>A0$O0-$/$J$$5$$,$7$F$-$^$7$?!#e(B

At Wed, 21 Jan 2009 21:54:34 +0900,
Tanaka A. wrote:

[ruby-dev:36585] を読み返していて、

elsif path.symlink?
  dir, base = path.readlink.split
  (path.dirname + dir).realpath + base

というのが必要だという話がありましたが、これってなぜでしたっけ?

basename の扱いに期待されることについて考えているのですが。

あ、これはまさに、

mkdir dir
ln -s dir/file link

のときに Pathname(“link”).realdirpath が /here/dir/file を導いて
ほしいからです。

if path.exist?
path.realname
elsif path.dirname.directory?
path.dirname.realpath + path.basename
else
raise Errno::ENOTDIR
end

このままの実装だと、 link が展開されず /here/link が返ります。

 realpath(3) ã¯ã€é ­ã‹ã‚‰ãƒ‘ã‚¹ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã‚’ lstat しながら辿り、
symlinkが現れると readlink し、その内容をさらに再帰的に処理して
いきます。lstat の結果が ENOENT ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒç¾ã‚ŒãŸå ´åˆ:

POSIX系の仕様では:
それが最後のコンポーネントなら ENOENT、最後でなければ
ENOTDIR を返す。

4.4BSD系の仕様では:
それが最後のコンポーネントならそのまま結合して返す。最後で
なければ ENOENT を返す。(ENOTDIR の方が適切な気がするのは
さておき)

という違いがありますが、いずれも最終的に symlink を返すことは
ありません。(“real” の意図に反します)

 名前については、改めて realdirpath を推します。realpath との
明らかな連関を想起させますし、 real-directory path と書き下して
みれば、 real-directory は最後の1つ前のコンポーネント、すなわち
親ディレクトリまで展開することを、 path はディレクトリに限らない
ことをよく表しています。

この名前は悪くない気がしてきました。

 ぜひ採用をお願いします。

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B$“!”$3$l$O$^$5$K!"e(B

mkdir dir
ln -s dir/file link

e$B$N$H$-$Ke(B Pathname(“link”).realdirpath e$B$,e(B /here/dir/file e$B$rF3$$$Fe(B
e$B$[$7$$$+$i$G$9!#e(B

e$B$&$%$`!#e(B

e$BB8:_$7$J$$%U%!%$%k$r;X$9%7%s%%j%C%/%j%s%/$r$“$i$+$8$a:n$C$Fe(B
e$B$*$-!”$=$3$K=q$-9~$^$;$k$H$$$&$o$1$G$9$+!#e(B

e$B8D?ME*$K$O$=$&$$$&$3$H$r$d$C$?3P$($,$J$/!“$^$?!”$d$k$K$7$F$be(B
e$B$+$J$j%H%j%C%-!<$J5$$,$9$k$s$G$9$,!"$=$l$C$F6qBNE*$K$O$I$&$$e(B
e$B$&>u67$G;H$&$s$G$7$g$&$+!#e(B

e$B%W%m%0%i%`$K=q$-9~$^$;$?$$%U%!%$%k$N0LCV$r65$($k$J$i!"0z?t$de(B
e$B@_Dj%U%!%$%k$G$d$k$N$,$U$D$&$@$H;W$&$s$G$9$,!#e(B

At Thu, 22 Jan 2009 15:54:57 +0900,
Tanaka A. wrote:

存在しないファイルを指すシンボリックリンクをあらかじめ作って
おき、そこに書き込ませるというわけですか。

個人的にはそういうことをやった覚えがなく、また、やるにしても
かなりトリッキーな気がするんですが、それって具体的にはどうい
う状況で使うんでしょうか。

でっちあげれば、たとえば…

  • NFSç­‰ã§å…±æœ‰ã•ã‚ŒãŸé ˜åŸŸã«ãƒ›ã‚¹ãƒˆãƒ­ãƒ¼ã‚«ãƒ«ãªãƒ•ã‚¡ã‚¤ãƒ«ã‚’æŒ‡ã™symlinkã‚’
    作り、別のホストではデッドリンクなんだけれども、なければ作る、
    という挙動に期待して各ホストでのリンク先の生成の手間をけちる

  • 新規生成ファイルのパス名がハードコードされた外部コマンドを呼び
    出す際に、symlinkã‚’ç½®ã„ã¦ãŠã„ã¦åˆ¥ã®å ´æ‰€ã«æ›¸ãå‡ºã•ã‚Œã‚‹ã‚ˆã†ç´°å·¥
    する

のようなケースが想定可能でしょうか。

プログラムに書き込ませたいファイルの位置を教えるなら、引数や
設定ファイルでやるのがふつうだと思うんですが。

はい、そう思います。たとえば ENOENT でエラーにするのも一つの方針
ではあります。

 まれなケースなので固執はしませんが、先の例の link を creat(2)
ないし O_CREAT 付きで open(2) すれば dir/file にファイルができる
というのは事実で、 realdirpath でわざわざsymlinkを意識した処理を
ã™ã‚‹ã‹ã‚‰ã«ã¯ãã‚Œã‚’ç¹”ã‚Šè¾¼ã¿ãŸã„å ´åˆã‚‚ã‚ã‚‹ã¯ãšã ã¨ã¯æ€ã„ã¾ã™ã€‚

At Wed, 24 Sep 2008 02:02:59 +0900,
Tanaka A. wrote:

In article [email protected],
“Akinori MUSHA” [email protected] writes:

 似た動作の想起を条件にするのは、 realpath を含む名前にするか、
あるいは realpath ã«å¼•æ•°ã‚’è¿½åŠ ã™ã‚‹ã‹ã®äºŒæŠžã¨è€ƒãˆã¦ã‚ˆã„ã§ã—ã‚‡ã†ã‹ã€‚

real という単語が入ってれば似ているように思います。

 しかし、 real という単語が入るのに実際に存在しないパスを返す
のは奇妙ではないでしょうか。あくまで realpath という既知の名前
からしか機能は想起されないと思います。real_path は気持ち悪いし、
realpath や realpath も適当なものが思いつきませんでした。
bsd_realpath, realpath_butlast, realpath_till_penult,
almost_realpath, not_quite_realpath, …

 同じ機能は既存の公開および内部メソッドを使っても簡単には実装
できなかったのでなんとか入れたいと思うのですが。

 ~ の展開と相対パスの展開を行うメソッドが expand_path なので、
symlink の解決と相対パスの展開を行うメソッドが resolve_path と
いうのは悪くないと思っています。realpath との関連はさほど大事
でしょうか。

 であれば私は後者を推したいです。フラグ引数が廃止された過去が
あるので抵抗があったのですが、もう数年経っているのでトラブルは
ないでしょうね。

フラグより違う名前のほうがいいんじゃないかという気がします。

この件については動的にどちらかの挙動を選びたいということはな
さそうな気がしますし、名前が挙動を表すとプログラムがわかりや
すくなるので。

 それはそうですね。先の実装はフラグを受け付けるようになって
いましたが、フラグを取るメソッドは private ã«éš ãã†ã¨æ€ã„ã¾ã™ã€‚


Akinori MUSHA / http://akinori.org/

  • lib/pathname.rb (Pathname#realpath_rec, Pathname#resolve_path):
    Add Pathname#resolve_path, a variant of #realpath that allows
    the last component of pathname to be nonexistent.

Index: lib/pathname.rb

— lib/pathname.rb (revision 19545)
+++ lib/pathname.rb (working copy)
@@ -76,9 +76,9 @@

=== Core methods

-# These methods are effectively manipulating a String, because that’s
all a path
-# is. Except for #mountpoint?, #children, and #realpath, they don’t
access the
-# filesystem.
+# These methods are effectively manipulating a String, because that’s
+# all a path is. Except for #mountpoint?, #children, #resolve_path
+# and #realpath, they don’t access the filesystem.

- +

- #join

@@ -90,6 +90,7 @@

- #each_filename

- #cleanpath

- #realpath

+# - #resolve_path

- #children

- #mountpoint?

@@ -411,7 +412,7 @@ class Pathname
end
private :cleanpath_conservative

  • def realpath_rec(prefix, unresolved, h)
  • def realpath_rec(prefix, unresolved, h, strict, last = true)
    resolved = []
    until unresolved.empty?
    n = unresolved.shift
    @@ -428,14 +429,20 @@ class Pathname
    prefix, *resolved = h[path]
    end
    else
  •      s = File.lstat(path)
    
  •      begin
    
  •        s = File.lstat(path)
    
  •      rescue Errno::ENOENT => e
    
  •        raise e if strict || !last || !unresolved.empty?
    
  •        resolved << n
    
  •        break
    
  •      end
         if s.symlink?
           h[path] = :resolving
           link_prefix, link_names = split_names(File.readlink(path))
           if link_prefix == ''
    
  •          prefix, *resolved = h[path] = realpath_rec(prefix, 
    

resolved + link_names, h)

  •          prefix, *resolved = h[path] = realpath_rec(prefix, 
    

resolved + link_names, h, strict, unresolved.empty?)
else

  •          prefix, *resolved = h[path] = realpath_rec(link_prefix, 
    

link_names, h)

  •          prefix, *resolved = h[path] = realpath_rec(link_prefix, 
    

link_names, h, strict, unresolved.empty?)
end
else
resolved << n
@@ -448,22 +455,39 @@ class Pathname
end
private :realpath_rec

  • Returns a real (absolute) pathname of +self+ in the actual

filesystem.

  • The real pathname doesn’t contain symlinks or useless dots.

  • No arguments should be given; the old behaviour is obsoleted.

  • def realpath
  • def resolve_path_internal(strict = false)
    path = @path
    prefix, names = split_names(path)
    if prefix == ‘’
    prefix, names2 = split_names(Dir.pwd)
    names = names2 + names
    end
  • prefix, *names = realpath_rec(prefix, names, {})
  • prefix, *names = realpath_rec(prefix, names, {}, strict)
    self.class.new(prepend_prefix(prefix, File.join(*names)))
    end
  • private :resolve_path_internal
  • Returns the real (absolute) pathname of +self+ in the actual

  • filesystem not containing symlinks or useless dots.

  • All components of the pathname must exist when this method is

  • called.

  • No arguments should be given; the old behaviour is obsoleted.

  • def realpath
  • resolve_path_internal(true)
  • end
  • Returns the real (absolute) pathname of +self+ in the actual

filesystem.

  • The real pathname doesn’t contain symlinks or useless dots.

  • The last component of the pathname can be nonexistent.

  • def resolve_path

  • resolve_path_internal(false)

  • end

    #parent returns the parent directory.

Index: test/pathname/test_pathname.rb

— test/pathname/test_pathname.rb (revision 19545)
+++ test/pathname/test_pathname.rb (working copy)
@@ -273,6 +273,8 @@ class TestPathname < Test::Unit::TestCas
Pathname.new(path).realpath.to_s
end

  • class NoException < Exception; end
  • def test_realpath
    begin
    File.symlink(nil, nil)
    @@ -281,10 +283,42 @@ class TestPathname < Test::Unit::TestCas
    rescue TypeError
    end
    Dir.mktmpdir(‘rubytest-pathname’) {|dir|
  •  assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist") }
     File.symlink("not-exist-target", "#{dir}/not-exist")
    
  •  assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") }
    
  •  assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist") }
    
  •  File.symlink("../#{File.basename(dir)}/./not-exist-target", 
    

“#{dir}/not-exist2”)

  •  assert_raises(Errno::ENOENT) { realpath("#{dir}/not-exist2") }
    
  •  File.open("#{dir}/exist-target", "w") {}
    
  •  File.symlink("../#{File.basename(dir)}/./exist-target", 
    

“#{dir}/exist”)

  •  assert_raises(NoException) { realpath("#{dir}/exist"); raise 
    

NoException }
File.symlink(“loop”, “#{dir}/loop”)

  •  assert_raise(Errno::ELOOP) { realpath("#{dir}/loop") }
    
  •  assert_raises(Errno::ELOOP) { realpath("#{dir}/loop") }
    
  • }
  • end
  • def resolve_path(path)
  • Pathname.new(path).resolve_path.to_s
  • end
  • def test_resolve_path
  • begin
  •  File.symlink(nil, nil)
    
  • rescue NotImplementedError
  •  return
    
  • rescue TypeError
  • end
  • Dir.mktmpdir(‘rubytest-pathname’) {|dir|
  •  assert_raises(NoException) { resolve_path("#{dir}/not-exist"); 
    

raise NoException }

  •  assert_raises(Errno::ENOENT) { 
    

realpath(“#{dir}/not-exist/not-exist-child”) }

  •  File.symlink("not-exist-target", "#{dir}/not-exist")
    
  •  assert_raises(NoException) { resolve_path("#{dir}/not-exist"); 
    

raise NoException }

  •  File.symlink("../#{File.basename(dir)}/./not-exist-target", 
    

“#{dir}/not-exist2”)

  •  assert_raises(NoException) { resolve_path("#{dir}/not-exist2"); 
    

raise NoException }

  •  File.open("#{dir}/exist-target", "w") {}
    
  •  File.symlink("../#{File.basename(dir)}/./exist-target", 
    

“#{dir}/exist”)

  •  assert_raises(NoException) { resolve_path("#{dir}/exist"); raise 
    

NoException }

  •  File.symlink("loop", "#{dir}/loop")
    
  •  assert_raises(Errno::ELOOP) { resolve_path("#{dir}/loop") }
    
    }
    end

In article [email protected],
“Akinori MUSHA” [email protected] writes:

e$B$N$h$&$J%1!<%9$,A[Dj2DG=$G$7$g$&$+!#e(B
e$B$&$%$`!#$=$&$$$&%1!<%9$C$FC1$Ke(B open e$B$9$k$@$1$G!"e(Brealdirpath
e$B$r;H$&$H$$$&OC$8$c$J$$$G$9$h$M!#e(B

e$B$O$$!“$=$&;W$$$^$9!#$?$H$($Pe(B ENOENT e$B$G%(%i!<$K$9$k$N$b0l$D$NJ}?Ke(B
e$B$G$O$”$j$^$9!#e(B

e$B!!$^$l$J%1!<%9$J$N$G8G<9$O$7$^$;$s$,!"@h$NNc$Ne(B link e$B$re(B creat(2)
e$B$J$$$7e(B O_CREAT e$BIU$-$Ge(B open(2) e$B$9$l$Pe(B dir/file e$B$K%U%!%$%k$,$G$-$ke(B
e$B$H$$$&$N$O;v<B$G!“e(B realdirpath e$B$G$o$6$o$6e(Bsymlinke$B$r0U<1$7$?=hM}$re(B
e$B$9$k$+$i$K$O$=$l$r?%$j9~$_$?$$>l9g$b$”$k$O$:$@$H$O;W$$$^$9!#e(B

e$B$^$@$^$8$a$K9M$($F$$$^$;$s$,e(B Apache e$B$Ge(B PUT e$B$Ge(B
e$B$He(B
e$B$$$&$N$r$A$g$C$H;W$$$D$$$?$s$G$9$,!"$I$&$+$J$!!#e(B

In article [email protected],
“Akinori MUSHA” [email protected] writes:

dstpath = Pathname(dst)
else

e$B$H$$$&%F%9%H$,<B:]$K$O@h$NM}M3$+$i@5$7$/$J$$$H$$$&$3$H$,$"$j!“e(B
e$B7k6IM_$7$$$N$Oe(B4.4BSDe$B$Ne(B realpath(3) e$B$8$c$J$$$+!”$H$$$&$N$,F05!e(B
e$B$G$7$?!#e(B

cross-device copy e$B$NH=CG$H$$$&I,MW@-$O6qBNE*$G$o$+$j$d$9$$$Ge(B
e$B$9$M!#$?$7$+$K!“%U%!%$%k$,B8:_$7$J$$CJ3,$G!”%U%!%$%k$r:n$C$?e(B
e$B$H$7$?$i$I$Ne(B directory/device e$B$K:n$i$l$k$N$+!"$H$$$&$N$re(B
realpath e$B$r;H$C$FH=CG$9$k$N$O$&$^$/$$$-$^$;$s!#e(B

e$BA0H>$N>iD9@-$He(B realdirpath e$B$N4X78$O$o$+$j$^$;$s!#e(B

e$B!!$b$7$+$9$k$H!"e(B Pathname#realpath e$B$,>JN,2DG=$N%V%m%C%/$r<u$1e(B
e$BIU$1$k$h$&$K$7$F!"e(B

path.realpath { |resolved, unresolved|
  unresolved.to_s.include?("/") ? raise ENOTDIR : resolved + unresolved
}

e$B$$?$$$J%O%s%I%i$r=q$1$k$H$$$$$N$+$J!#$=$&$9$k$He(B rsync e$B$N$h$&$Ke(B
e$B$^$@B8:
$7$J$$%G%#%l%/%H%j$r@8@.$7$?$j$9$k=hM}$r=q$-$d$9$/$J$ke(B
e$B$+$b!#e(B(.to_s.include?(“/”) e$B$H$$$&$N$,$$$^$$$A$G$9$,e(B)

e$B$I$&$G$9$+$M$'!#e(B

e$B$^$@$^$8$a$K9M$($F$$$^$;$s$,e(B Apache e$B$Ge(B PUT e$B$Ge(B e$B$He(B
e$B$$$&$N$r$A$g$C$H;W$$$D$$$?$s$G$9$,!"$I$&$+$J$!!#e(B

e$B!!$3$3$O$A$g$C$H0UL#$,J,$+$j$^$;$s$G$7$?!#e(B

e$B$N$h$&$J@)Ls$N<B8=$K$Oe(B realdirpath e$B$,I,MW$K$J$ke(B
e$B$N$G$O$J$$$+$H$$$&?dB,$G$9!#e(B

At Thu, 22 Jan 2009 20:22:13 +0900,
Tanaka A. wrote:

出す際に、symlinkã‚’ç½®ã„ã¦ãŠã„ã¦åˆ¥ã®å ´æ‰€ã«æ›¸ãå‡ºã•ã‚Œã‚‹ã‚ˆã†ç´°å·¥
 まれなケースなので固執はしませんが、先の例の link を creat(2)
ないし O_CREAT 付きで open(2) すれば dir/file にファイルができる
というのは事実で、 realdirpath でわざわざsymlinkを意識した処理を
ã™ã‚‹ã‹ã‚‰ã«ã¯ãã‚Œã‚’ç¹”ã‚Šè¾¼ã¿ãŸã„å ´åˆã‚‚ã‚ã‚‹ã¯ãšã ã¨ã¯æ€ã„ã¾ã™ã€‚

 実際にあった例では、パス名を生成する段階で

# copy src to dst
srcpath = Pathname(src).realpath

dstpath = Pathname(dst)
if dstpath.exist?
  dstpath = dstpath.realpath
else
  dstpath = dstpath.dirname.realpath + dstpath.basename
end

と存在チェック(の記述)が必要で、実際に処理を行うときにもまた

if dstpath.exist?
  # differential copy
else
  # full copy
end

とする必要があるのが冗長に感じました。さらに、

if srcpath.dirname.dev != dstpath.dirname.dev
  # cross-device copy
  ...
end

というテストが実際には先の理由から正しくないということがあり、
結局欲しいのは4.4BSDの realpath(3) じゃないか、というのが動機
でした。

 上記のような例がよくあるのかどうかと言われると何とも言えない
ですが、(まだ)存在しないパスを扱うことはそれなりにあると思い
ます。

 もしかすると、 Pathname#realpath が省略可能のブロックを受け
付けるようにして、

path.realpath { |resolved, unresolved|
  unresolved.to_s.include?("/") ? raise ENOTDIR : resolved + 

unresolved
}

みたいなハンドラを書けるといいのかな。そうすると rsync のように
まだ存在しないディレクトリを生成したりする処理を書きやすくなる
かも。(.to_s.include?("/") というのがいまいちですが)

まだまじめに考えていませんが Apache で PUT で と
いうのをちょっと思いついたんですが、どうかなぁ。

 ここはちょっと意味が分かりませんでした。