readlinkコマンドはシンボリックリンクを辿り、リンク先を表示する。しかしこのままではファイルへのパスとして妥当なものにならない。
こういうときはdirnameと組み合わせればよい。
# 実際のファイル名を出力する D=$(dirname $i) L=$(readlink $i) if [ -z "$L" ] ; then echo $i elif [ -z "$D" -o "$D" = "." -o $(echo $L | cut -c 1) = "/" ] ; then echo $L else echo $D/$L fi
しかし、これでは「../」などが残ってしまう。そこで、こういう感じのを噛ませる。sedでパターンマッチだけでやろうとしたがなかなかいまいちになったのでPerlを登場させてしまった(不覚)。
echo $1 | \ perl -ne ' @a=split("/"); for($i=0; $i<=$#a; $i++){ if($a[$i] eq ".." & $#b!=-1 & $b[$#b] ne ".."){ pop(@b); }elsif($a[$i] ne "."){ push(@b, $a[$i]); } } print join("/", @b)."\n";'
だいたい実際のファイル名が得られる。厳密ではないと思うけど。
組み合わせると、以下のようになる。しばらくこれで使ってみようと思う。なんかでもバグってるかも。
#! /bin/sh
delete_parent(){
echo $1 | \
perl -ne '
@a=split("/");
for($i=0; $i<=$#a; $i++){
if($a[$i] eq ".." & $#b!=-1 & $b[$#b] ne ".."){
pop(@b);
}elsif($a[$i] ne "."){
push(@b, $a[$i]);
}
}
print join("/", @b)."\n";'
}
delete_parent_old(){
O=$1
while [ "$S" != "$O" ]; do
S=$O
O=$(echo -n $S | perl -pe 's|/([^\./][^/]+)/../|/|g;s|/./|/|g;s|^[^\./][^/]+/../||g;')
done
echo $O
}
for i; do
D=$(dirname $i)
L=$(readlink $i)
if [ -z "$L" ] ; then
echo $(delete_parent $i)
elif [ -z "$D" -o "$D" = "." -o $(echo $L | cut -c 1) = "/" ] ; then
echo $(delete_parent $L)
else
echo $(delete_parent $D/$L)
fi
done
/bin/sh
とは書いたものの、素のBourne Shellでは無理だろうな。bashなら動くようだ。
勿論これだけの操作では1つしかリンクを辿らない。また途中のディレクトリにシンボリックリンクがあってもそれは辿らない。指定されたファイル名のみについて1つ辿ってそれらしいファイル名を返す、というものだ。
絶対パスにしたければ、先頭文字が/でない場合に先頭に$PWD
をつければよい。いじょう。