Re: equivalent of Linux readlink -f in pure bash?
However, this also loops endlessly. The reason is most likely that bash maintains an additional internal variable holding the index of the current character, relative to the current word. While this variable is not directly accessible by the user, it is set to 0 whenever OPTIND is assigned a value. [...] That would be a bug in bash in my opinion. If OPTIND is marked local to the function, it shouldn't affect the behavior of parent contexts. Note that that bug is also in ksh93, pdksh, mksh and posh (though slightly different in that one), but not in ash nor zsh. Seems like ksh93 (tested with version 93u 2011-02-08) implicitly declares OPTIND and OPTARG in functions defined in the `function NAME {}' syntax and everything works fine. But if OPTIND or OPTARG are explicitly declared as local it may not work as expected. Wish Chet would consider fixing this problem in future bash releases. :) Bash doesn't have the `posix' and `non-posix' distinction between function syntax that ksh93 makes. All variables are global unless explicitly declared as local. The other thing that Posix requires is that setting OPTIND to 1 resets getopts' internal state. I'll take another look and see if I can work around the issue: resetting getopts when returning from the shell function restores the value `1' (since we're still processing the first argument), which causes getopts' internal state to reset. Chet -- ``The lyf so short, the craft so long to lerne.'' - Chaucer ``Ars longa, vita brevis'' - Hippocrates Chet Ramey, ITS, CWRUc...@case.eduhttp://cnswww.cns.cwru.edu/~chet/
Re: equivalent of Linux readlink -f in pure bash?
On Wed, Aug 10, 2011 at 18:47, Stephane CHAZELAS stephane_chaze...@yahoo.fr wrote: 2011-08-10, 12:00(+02), Bernd Eggink: [...] function f { local OPTIND=1 echo \$1=$1 } while getopts abcdefg opt do echo opt=$opt f $opt done Calling the sript like this works fine: script -a -b -c But calling it like this leads to an endless loop: script -abc [...] However, this also loops endlessly. The reason is most likely that bash maintains an additional internal variable holding the index of the current character, relative to the current word. While this variable is not directly accessible by the user, it is set to 0 whenever OPTIND is assigned a value. [...] That would be a bug in bash in my opinion. If OPTIND is marked local to the function, it shouldn't affect the behavior of parent contexts. Note that that bug is also in ksh93, pdksh, mksh and posh (though slightly different in that one), but not in ash nor zsh. Seems like ksh93 (tested with version 93u 2011-02-08) implicitly declares OPTIND and OPTARG in functions defined in the `function NAME {}' syntax and everything works fine. But if OPTIND or OPTARG are explicitly declared as local it may not work as expected. Wish Chet would consider fixing this problem in future bash releases. :) Note that if you set OPTIND local, you probably want to do the same for OPTARG (and maybe OPTERR). -- Stephane
Re: equivalent of Linux readlink -f in pure bash?
On Wed, Aug 10, 2011 at 6:00 PM, Bernd Eggink mono...@sudrala.de wrote: On 09.08.2011 15:50, Steven W. Orr wrote: *) You reset OPTIND to 1 but you didn't declare it local. This will cause any caller of getlink which uses getopts to reset its variable to 1. (I mention this because it cost me a couple of hours a while back.) The reason I didn't declare OPTIND local is that OPTIND is handled specially by the shell; there is always exactly _one_ instance of this variable. In other words, OPTIND is always global, even if declared local (which is indeed pretty weird). Try this: I always declare OPTIND as local. I didn't know it does not work at all. Bug? --**- function f { local OPTIND=1 echo \$1=$1 } while getopts abcdefg opt do echo opt=$opt f $opt done --**-- Calling the sript like this works fine: script -a -b -c But calling it like this leads to an endless loop: script -abc One could of course save and restore the original: --**- function f { local oldind=$OPTIND OPTIND=1 echo \$1=$1 OPTIND=$oldind } --**- However, this also loops endlessly. The reason is most likely that bash maintains an additional internal variable holding the index of the current character, relative to the current word. While this variable is not directly accessible by the user, it is set to 0 whenever OPTIND is assigned a value. So the only safe way is to _never_ use getopts within another getopts block, but always wait until the first one has finished.
Re: equivalent of Linux readlink -f in pure bash?
2011-08-9, 09:24(+00), Stephane CHAZELAS: 2011-08-9, 11:44(+10), Jon Seymour: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? (I need readlink's function on AIX where it doesn't seem to be available). [...] What about: readlink_f() ( link=$1 max_iterations=40 while [ $max_iterations -gt 0 ]; do max_iterations=$(($max_iterations - 1)) dir=$(dirname -- $link) || exit base=$(basename -- $link) || exit dir=$(cd -P -- $dir pwd -P) || exit link=${dir%/}/$base if [ ! -L $link ]; then printf '%s\n' $link exit fi link=$(ls -ld -- $link) || exit link=${link#* - } done printf 2 'Loop detected\n' exit 1 ) Sorry, it's wrong if there are relative paths in symlinks (or trailing newlines). fixed_cmd_subst() { eval ' '$1'=$('$2'; ret=$?; echo .; exit $ret) set -- $1 $? '$1'=${'$1'%??} ' return $2 } readlink_f() ( link=$1 max_iterations=40 while [ $max_iterations -gt 0 ]; do max_iterations=$(($max_iterations - 1)) fixed_cmd_subst dir 'dirname -- $link' || exit fixed_cmd_subst base 'basename -- $link' || exit cd -P -- $dir || exit link=${PWD%/}/$base if [ ! -L $link ]; then printf '%s\n' $link exit fi fixed_cmd_subst link 'ls -ld -- $link' || exit link=${link#* - } done printf 2 'Loop detected\n' exit 1 ) -- Stephane
Re: equivalent of Linux readlink -f in pure bash?
2011-08-09, 11:29(+02), Bernd Eggink: On 09.08.2011 03:44, Jon Seymour wrote: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? You can find my version here: http://sudrala.de/en_d/shell-getlink.html As it contains some corrections from Greg Wooledge, it should handle even pathological situations. ;) [...] function getlink # ([-l] path) Why use the ksh syntax instead of the standard one? { # Path of the file a symbolic link is pointing to. # -l: follow link chain, print last target # no option: print 1st target unchanged typeset dir file last link opt oldPWD=$PWD ret=0 (( OPTIND = 1 )) while getopts l opt do case $opt in (l) last=1 ;; esac done shift $(( OPTIND - 1 )) file=$1 if [[ $last ]] # last link then while true do dir=$(dirname $file) What if $file starts with -? [[ ! -d $dir ]] { ret=1 break } # remove slashes at end while [[ $file == */ ]] do file=${file%/} done What if $file is / file=${file##*/}# file name command cd -P $dir What if that command fails? [[ ! -h $file ]] break What if there are symlinks in the path components? link=$(command ls -l -- $file; printf x) Good point about command substitution discarding trailing newlines. I forgot that in the solution I gave. link=${link%$'\nx'} remove=$file - file=${link#*$remove} done printf %s\n $PWD/$file command cd $oldPWD What if $oldPWD has blanks or wildcards? elif [[ ! -h $file ]] # 1st link then printf %s\n $file else link=$(ls -l $file) printf %s\n ${link##*- } What about trailing newlines here and links with - in their path. fi return $ret What if $IFS contains 0 or 1? } declare -fc getlink What's the -c about? -- Stephane
Re: equivalent of Linux readlink -f in pure bash?
On 09.08.2011 16:54, Stephane CHAZELAS wrote: 2011-08-09, 09:50(-04), Steven W. Orr: [...] *) To remove the trailing slashes, instead of while [[ $file == */ ]] do file=${file%/} done file=${file##*/}# file name just say file=${file%${file##*[!/]}} [...] file=${file%${file##*[!/]}} Same problem with / being changed to though. This has been fixed. Also getlink() now outputs paths always in normalized form. Any feedback appreciated! Bernd -- http://sudrala.de
Re: equivalent of Linux readlink -f in pure bash?
On 09.08.2011 15:50, Steven W. Orr wrote: On 8/9/2011 5:29 AM, Bernd Eggink wrote: On 09.08.2011 03:44, Jon Seymour wrote: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? You can find my version here: http://sudrala.de/en_d/shell-getlink.html As it contains some corrections from Greg Wooledge, it should handle even pathological situations. ;) Bernd I'd just like to make a couple of suggestions for your script (I hope these are welcome): You are welcome! *) You reset OPTIND to 1 but you didn't declare it local. This will cause any caller of getlink which uses getopts to reset its variable to 1. (I mention this because it cost me a couple of hours a while back.) The reason I didn't declare OPTIND local is that OPTIND is handled specially by the shell; there is always exactly _one_ instance of this variable. In other words, OPTIND is always global, even if declared local (which is indeed pretty weird). Try this: --- function f { local OPTIND=1 echo \$1=$1 } while getopts abcdefg opt do echo opt=$opt f $opt done Calling the sript like this works fine: script -a -b -c But calling it like this leads to an endless loop: script -abc One could of course save and restore the original: --- function f { local oldind=$OPTIND OPTIND=1 echo \$1=$1 OPTIND=$oldind } --- However, this also loops endlessly. The reason is most likely that bash maintains an additional internal variable holding the index of the current character, relative to the current word. While this variable is not directly accessible by the user, it is set to 0 whenever OPTIND is assigned a value. So the only safe way is to _never_ use getopts within another getopts block, but always wait until the first one has finished. When calling getopts, especially from a function that is intended to not be used at a top level for processing command line options, you should declare local copies of OPTIND, OPTARG and OPTERR. *) To remove the trailing slashes, instead of while [[ $file == */ ]] do file=${file%/} done file=${file##*/} # file name just say file=${file%${file##*[!/]}} Yes, you can do that, but I find my version a bit more legible. Also, for file=/ it returns a single slash, while yours returns an empty string. (Hmm... the next statement in my script also creates an empty string, but this is a bug and will be fixed). *) Instead of [[ ! -d $dir ]] { ret=1 break } how about this for slightly cleaner? [[ -d $dir ]] || { ret=1 break } I think that's just a matter of taste. Greetings, Bernd -- http://sudrala.de
Re: equivalent of Linux readlink -f in pure bash?
Bob Proulx b...@proulx.com writes: Same comment here about over-quoting. If nothing else it means that syntax highlighting is different. dir=$(cd $(dirname $path); pwd -P) You are missing a pair of quotes here. :-) Andreas. -- Andreas Schwab, sch...@linux-m68k.org GPG Key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 And now for something completely different.
Re: equivalent of Linux readlink -f in pure bash?
On 09.08.2011 03:44, Jon Seymour wrote: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? You can find my version here: http://sudrala.de/en_d/shell-getlink.html As it contains some corrections from Greg Wooledge, it should handle even pathological situations. ;) Bernd -- http://sudrala.de
Re: equivalent of Linux readlink -f in pure bash?
On 8/9/2011 5:29 AM, Bernd Eggink wrote: On 09.08.2011 03:44, Jon Seymour wrote: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? You can find my version here: http://sudrala.de/en_d/shell-getlink.html As it contains some corrections from Greg Wooledge, it should handle even pathological situations. ;) Bernd I'd just like to make a couple of suggestions for your script (I hope these are welcome): *) You reset OPTIND to 1 but you didn't declare it local. This will cause any caller of getlink which uses getopts to reset its variable to 1. (I mention this because it cost me a couple of hours a while back.) When calling getopts, especially from a function that is intended to not be used at a top level for processing command line options, you should declare local copies of OPTIND, OPTARG and OPTERR. *) To remove the trailing slashes, instead of while [[ $file == */ ]] do file=${file%/} done file=${file##*/}# file name just say file=${file%${file##*[!/]}} *) Instead of [[ ! -d $dir ]] { ret=1 break } how about this for slightly cleaner? [[ -d $dir ]] || { ret=1 break } -- Time flies like the wind. Fruit flies like a banana. Stranger things have .0. happened but none stranger than this. Does your driver's license say Organ ..0 Donor?Black holes are where God divided by zero. Listen to me! We are all- 000 individuals! What if this weren't a hypothetical question? steveo at syslang.net
Re: equivalent of Linux readlink -f in pure bash?
On Tue, Aug 9, 2011 at 7:29 PM, Bernd Eggink mono...@sudrala.de wrote: On 09.08.2011 03:44, Jon Seymour wrote: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? You can find my version here: http://sudrala.de/en_d/shell-getlink.html As it contains some corrections from Greg Wooledge, it should handle even pathological situations. ;) Bernd Thanks for that. ${link##*- } is a neater way to extract the link. It does seem that a link create like so: ln -sf a - b c is going to create problems for both your script and mine [ not that I actually care about such a perverse case :-) ] jon.
Re: equivalent of Linux readlink -f in pure bash?
On Tue, Aug 9, 2011 at 12:49 PM, Bob Proulx b...@proulx.com wrote: Jon Seymour wrote: Has anyone ever come across an equivalent to Linux's readlink -f that is implemented purely in bash? (I need readlink's function on AIX where it doesn't seem to be available). Try this: ls -l /path/to/some/link | awk '{print$NF}' Sure it doesn't handle whitespace in filenames but what classic AIX Unix symlink would have whitespace in it? :-) readlink -f will fully resolve links in the path itself (rather than link at the end of the path), which was the behaviour I needed. It seems cd -P does most of what I need for directories and so handling things other than directories is a small tweak on that. Anyway, thanks for that! jon.
Re: equivalent of Linux readlink -f in pure bash?
Jon Seymour wrote: readlink -f will fully resolve links in the path itself (rather than link at the end of the path), which was the behaviour I needed. Ah, yes, well, as you could tell that was just a partial solution anyway. It seems cd -P does most of what I need for directories and so handling things other than directories is a small tweak on that. You might try cd'ing there and then using pwd -P to get the canonical directory name. I am thinking something like this: #!/bin/sh p=$1 dir=$(dirname $p) base=$(basename $p) physdir=$(cd $dir; pwd -P) realpath=$(cd $dir; ls -l $base | awk '{print$NF}') echo $physdir/$realpath | sed 's|//*|/|g' exit 0 Again, another very quick and partial solution. But perhaps something good enough just the same. Bob
Re: equivalent of Linux readlink -f in pure bash?
On Tue, Aug 9, 2011 at 1:36 PM, Bob Proulx b...@proulx.com wrote: Jon Seymour wrote: readlink -f will fully resolve links in the path itself (rather than link at the end of the path), which was the behaviour I needed. Ah, yes, well, as you could tell that was just a partial solution anyway. It seems cd -P does most of what I need for directories and so handling things other than directories is a small tweak on that. You might try cd'ing there and then using pwd -P to get the canonical directory name. I am thinking something like this: #!/bin/sh p=$1 dir=$(dirname $p) base=$(basename $p) physdir=$(cd $dir; pwd -P) realpath=$(cd $dir; ls -l $base | awk '{print$NF}') echo $physdir/$realpath | sed 's|//*|/|g' exit 0 Again, another very quick and partial solution. But perhaps something good enough just the same. realpath=$(cd $dir; ls -l $base | awk '{print$NF}') I always use sed for this purpose, so: $(cd $dir; ls -l $base | sed s/.*-//) But, with pathological linking structures, this isn't quite enough - particularly if the target of the link itself contains paths, some of which may contain links :-) jon.
Re: equivalent of Linux readlink -f in pure bash?
Jon Seymour wrote: I always use sed for this purpose, so: $(cd $dir; ls -l $base | sed s/.*-//) But, with pathological linking structures, this isn't quite enough - particularly if the target of the link itself contains paths, some of which may contain links :-) Agreed! Symlinks with arbitrary data, such as holding small shopping lists in the target value, are so much fun. I am more concerned that arbitrary data such as - might exist in there more so than whitespace. That is why I usually don't use a pattern expression. But I agree it is another way to go. But it is easier to say whitespace is bad in filenames than to say whitespace is bad and oh yes you can't have - in there either. :-) Bob
Re: equivalent of Linux readlink -f in pure bash?
On Tue, Aug 9, 2011 at 2:14 PM, Bob Proulx b...@proulx.com wrote: Jon Seymour wrote: I always use sed for this purpose, so: $(cd $dir; ls -l $base | sed s/.*-//) But, with pathological linking structures, this isn't quite enough - particularly if the target of the link itself contains paths, some of which may contain links :-) Agreed! Symlinks with arbitrary data, such as holding small shopping lists in the target value, are so much fun. I am more concerned that arbitrary data such as - might exist in there more so than whitespace. That is why I usually don't use a pattern expression. But I agree it is another way to go. But it is easier to say whitespace is bad in filenames than to say whitespace is bad and oh yes you can't have - in there either. :-) Ok, I think this does it... readlink_f() { local path=$1 test -z $path echo usage: readlink_f path 12 exit 1; local dir if test -L $path then local link=$(ls -l $path | sed s/.*- //) if test $link = ${link#/} then # relative link dir=$(dirname $path) readlink_f ${dir%/}/$link else # absolute link readlink_f $link fi elif test -d $path then (cd $path; pwd -P) # normalize it else dir=$(cd $(dirname $path); pwd -P) base=$(basename $path) echo ${dir%/}/${base} fi }
Re: equivalent of Linux readlink -f in pure bash?
On Tue, Aug 9, 2011 at 2:36 PM, Jon Seymour jon.seym...@gmail.com wrote: On Tue, Aug 9, 2011 at 2:14 PM, Bob Proulx b...@proulx.com wrote: Jon Seymour wrote: I always use sed for this purpose, so: $(cd $dir; ls -l $base | sed s/.*-//) But, with pathological linking structures, this isn't quite enough - particularly if the target of the link itself contains paths, some of which may contain links :-) Agreed! Symlinks with arbitrary data, such as holding small shopping lists in the target value, are so much fun. I am more concerned that arbitrary data such as - might exist in there more so than whitespace. That is why I usually don't use a pattern expression. But I agree it is another way to go. But it is easier to say whitespace is bad in filenames than to say whitespace is bad and oh yes you can't have - in there either. :-) Ok, I think this does it... readlink_f() { ... } And I make no claims whatsoever about whether this is vulnerable to infinite recursion! jon.
Re: equivalent of Linux readlink -f in pure bash?
Jon Seymour wrote: readlink_f() { local path=$1 test -z $path echo usage: readlink_f path 12 exit 1; An extra ';' there that doesn't hurt but isn't needed. local dir if test -L $path then local link=$(ls -l $path | sed s/.*- //) I would be inclined to also look for a space before the - too. Because it just is slightly more paranoid. local link=$(ls -l $path | sed s/.* - //) if test $link = ${link#/} then # relative link dir=$(dirname $path) As an aside you don't need to quote assignments. They exist inside the shell and no word splitting will occur. It is okay to assign without quotes here and I think it reads slightly better without. dir=$(dirname $path) readlink_f ${dir%/}/$link else # absolute link readlink_f $link fi elif test -d $path then (cd $path; pwd -P) # normalize it else dir=$(cd $(dirname $path); pwd -P) base=$(basename $path) Same comment here about over-quoting. If nothing else it means that syntax highlighting is different. dir=$(cd $(dirname $path); pwd -P) base=$(basename $path) echo ${dir%/}/${base} fi } And of course those are just suggestions and nothing more. Feel free to ignore. Note that there is a recent movement to change that dash greater-than combination into a true unicode arrow graphic emited by 'ls'. I think things paused when there were several different bike shed suggestions about which unicode arrow symbol people wanted there. I haven't seen any actual movement for a while and I think that is a good thing. Bob
Re: equivalent of Linux readlink -f in pure bash?
On Tue, Aug 9, 2011 at 2:51 PM, Bob Proulx b...@proulx.com wrote: Jon Seymour wrote: readlink_f() { local path=$1 test -z $path echo usage: readlink_f path 12 exit 1; An extra ';' there that doesn't hurt but isn't needed. local dir if test -L $path then local link=$(ls -l $path | sed s/.*- //) I would be inclined to also look for a space before the - too. Because it just is slightly more paranoid. local link=$(ls -l $path | sed s/.* - //) if test $link = ${link#/} then # relative link dir=$(dirname $path) As an aside you don't need to quote assignments. They exist inside the shell and no word splitting will occur. It is okay to assign without quotes here and I think it reads slightly better without. dir=$(dirname $path) readlink_f ${dir%/}/$link else # absolute link readlink_f $link fi elif test -d $path then (cd $path; pwd -P) # normalize it else dir=$(cd $(dirname $path); pwd -P) base=$(basename $path) Same comment here about over-quoting. If nothing else it means that syntax highlighting is different. dir=$(cd $(dirname $path); pwd -P) base=$(basename $path) echo ${dir%/}/${base} fi } And of course those are just suggestions and nothing more. Feel free to ignore. Tips appreciated, thanks. jon.