Re: equivalent of Linux readlink -f in pure bash?

2012-01-10 Thread Chet Ramey
   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?

2012-01-09 Thread Clark J. Wang
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?

2011-08-11 Thread Clark J. Wang
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-11 Thread Stephane CHAZELAS
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-11 Thread Stephane CHAZELAS
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?

2011-08-11 Thread Bernd Eggink

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?

2011-08-10 Thread Bernd Eggink

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?

2011-08-09 Thread Andreas Schwab
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?

2011-08-09 Thread 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. ;)


Bernd

--
http://sudrala.de



Re: equivalent of Linux readlink -f in pure bash?

2011-08-09 Thread Steven W. Orr

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?

2011-08-09 Thread Jon Seymour
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?

2011-08-08 Thread Jon Seymour
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?

2011-08-08 Thread Bob Proulx
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?

2011-08-08 Thread Jon Seymour
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?

2011-08-08 Thread Bob Proulx
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?

2011-08-08 Thread Jon Seymour
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?

2011-08-08 Thread Jon Seymour
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?

2011-08-08 Thread Bob Proulx
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?

2011-08-08 Thread Jon Seymour
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.