sd -- a drop-in replacement for `cd'

Check-in [eed377262a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:replace piping of `print' output into commands by `<<<' here strings whereever possible. some other minor edits.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: eed377262a73515fc2d13fd59b7c0cb6910c71c9
User & Date: vdh 2019-12-22 16:55:41
Context
2019-12-28 10:58
ensure sane input to `sdman'. remove (apparently redundant) use of `eval'. check-in: 5b18ec4ca0 user: vdh tags: trunk
2019-12-22 16:55
replace piping of `print' output into commands by `<<<' here strings whereever possible. some other minor edits. check-in: eed377262a user: vdh tags: trunk
2019-12-22 16:20
replace piping of `print' output into commands by `<<<' here strings whereever possible. some other minor edits. check-in: 630cfacdfe user: vdh tags: mksh
2019-12-13 15:01
manpage fix (bring back into sync with version on other branches). check-in: 13f16da74f user: vdh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to sd.ksh.

619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
...
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
...
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
...
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717

718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
...
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
...
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
...
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911

912
913
914
915
916
917
918
...
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
....
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
....
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
# count linefeed separated lines in string `buffer'.
# we append a trailing \n (implying `buffer' is assumed
# to not contain a trailing empty line) via `sdprint'
# since `wc -l' actually counts the number of \n.
# if buffer is empty we return 0
#-------------------------------------------------------
   typeset -i lc=0    #using `-i' suppresses spurious blanks in `wc -l' output.
   [[ -z $1 ]] || lc=$($sdprint "$1" | wc -l)
   $sdprint $lc
}

function sdlogread {
   sdlog=$(<$dirv)
   sdmagicline=${sdlog%%$'\n'*}
   sdlog=$($sdprint "$sdlog" | grep -v "^$sdmagicstring")
   sdlist=$(sdstack)
   sdnew=""
}

function sdlogwrite {
   $sdprint $sdmagicstring-- time stamp: $(date)$'\n'"$sdlog" >| $dirv
   sdlist=$(sdstack)
................................................................................
   lines=$(sdlinecount "$sdlog")

   if (( lines > sdmax )); then
      typeset -i limit

      ((limit = sdmax*9/10))
      ((limit = sdlines > limit ? sdlines:limit))
      sdlog=$($sdprint "$sdlog" | tail -$limit)
      mv -f $dirv ${dirv}.bak
      sdlogwrite
   fi
}

function sdremove {
#----------------------------------------
................................................................................
   typeset dname check
   typeset IFS=$'\n'

   sdl=$sdlines
   sdirs -l $sdmax #look at whole database
   sdlines=$sdl

   dname=$($sdprint "$sdlist" | $awk -F"\t" '$NF ~ /'"$pat"'/ {print $NF}')
   sdirs -l $sdlines  #important to reset before possibly returning...
   [[ -n $dname ]] || { $sdprint no match; return 1; }

   $sdprint "$dname"
   $sdprint -n "remove these entries from the logfile? [y/N] "
   read check
   check=${check:-"n"}
................................................................................
   fi

   # note to self: the '|' quoting '|' is not really required here.
   # its done just to silence `ksh -n'..
   pat="${dname//$IFS/\|}"
   pat="^(${pat//\//\\/})\$"

   sdlog=$($sdprint "$sdlog"|egrep -v "$pat")

   sdlogwrite
}

function sdclean {
#----------------------------------------------
#remove stale entries (and `/') from sdlog/dirv
#----------------------------------------------
   typeset IFS=$'\n'
   typeset -a entries stale
   typeset -i i j


   # note to self: mksh is really bad at `+=' string concatenation
   # when the string becomes large (as is the case here with `entries'
   # and, possibly, `stale'. for that reason we now use indexed
   # arrays for buffering and paste the result together in the end.
   # in ksh/bash it doesn't matter: both approaches are equally
   # fast (and both these shells remain faster than mksh).
   for dname in $sdlog; do
      [[ -d ${dname/#\~/$HOME} ]] && {
         entries[i++]=$dname
      } || stale[j++]=$dname
   done
   [[ -n $stale ]] && {
      stale=$($sdprint -n "${stale[*]}" | sort | uniq )
      $sdprint "$stale"
      $sdprint -n "eliminate these stale directories from the logfile? [y/N] "
      read check
      check=${check:-"n"}
      if [[ "$check" == "y" ]]; then
         sdlog=${entries[*]}
         sdlogwrite
................................................................................
      stacksize=$(sdlinecount "$sdlist")
      mostlen=${#most}
      i=0
      while ((i < indent0+mostlen)); do
         indent+=" "
         ((i++))
      done
      top=$($sdprint "$sdlist" | head -10 | $awk -F'\t' '{print $(NF-1) "\t" $NF}')
      top=${top//$'\n'/$'\n'$indent}
   } || stacksize=0

   vdir=${dirv/#$HOME/\~}


   report="
................................................................................
# given unique entry/directory is used as the resulting weight for
# that entry when sorting. note, that the visit frequencies are used
# as a secondary sort key since in extreme cases (very large `sdpower'
# values) weights can become exactly zero at the bottom of the sorted
# stack and the primary key does not suffice for unambiguous sorting.
# generally this will not happen, though.
#-----------------------------------------------------------------------------
   $sdprint "$sdlog" | tail -$sdlines |\
   $awk '
      BEGIN {
         OFS     = "\t"
         sdlines = '$sdlines'
         sdpower = '$sdpower'
      }
      {
................................................................................
      if [[ $pat == "$sdpattern" ]]; then
         ((sdcount++))
      else
         sdcount=1
      fi
      sdpattern=$pat
      ((sdcount > 1)) && {
         rank=$($sdprint "$sdlist" |\
            $awk -F"\t" "$awkpat"' {
               if (++count == '$sdcount') {
                  print $(NF-1)
                  exit
               }
            }'
         )
         [[ -n $rank ]] && awkpat="NR == $rank" || { sdcount=1; $sdprint starting over ... >&2 ;}
      }
   fi

   matches=$($sdprint "$sdlist" |\
      $awk -F"\t" "$awkpat"' {
         sub(/^~/, "'"$HOME"'", $NF)
         print $NF
      }')


   # select first actually existing directory
   typeset IFS=$'\n'
   for match in $matches; do
      [[ -d $match ]] && {
         dname=$match
         break
................................................................................
               break
            } || {
               [[ -d "$dname" ]] && {
                  # see comment at start of block for explanation of this:
                  command cd "$dname"
                  return $?
               } || {
                  echo "No matching directory"
                  return 1
               }
            }
         } || {
            # we already _know_ that the cd fails _and_ the stack is
            # already maximized. so just let the user see the error
            # message (should be "no such file or directory").
................................................................................
}

function sdchoose { ## dname
   # possibly called by `sdirs' to allow cd to directory selected from
   # list of matches. note to self: sort alphabetically?
   typeset dname="$1"
   typeset -i hits num
   $sdprint "$dname" | $awk -F"\t" '
      BEGIN {
         print "rank\tindex\tname"
      }
      {
         print $(NF-1) "\t" NR "\t" $NF
      }' >&2
   hits=$($sdprint "$dname"|wc -l)
   $sdprint -n "choose index (<CR> = 1; CTRL-C = abort): " >&2
   read -r num
   ((num == 0)) && num=1
   ((num > 0 && num <= hits)) && {
      dname=$($sdprint "$dname" | $awk -F"\t" 'NR == '$num' {sub(/^~/,"'$HOME'",$NF); print $NF}')
      cd "$dname"
      # care is taken that a call to `sdirs' does not send anything to stdout elswhere
      # and here only if we believe that we are operating under tcsh (indicated
      # by `sdistcsh == 1'). this output is then needed by the `sdirs' executable
      # with tcsh.
      ((sdistcsh == 1)) && $sdprint $dname
   }
................................................................................

   if [[ $# == 0 ]]; then
      [[ -n $sdlog ]] || return 1
      dname=$sdlist
   else
      pat="$@"
      pat="${pat//\//\\/}"
      dname=$($sdprint "$sdlist" | $awk -F"\t" '$NF ~ /'"$pat"'/')
      [[ -n $dname ]] || return 1
   fi

   dname=$($sdprint "$dname" | $dump)
   if ((sdselect == 0)); then
      $sdprint $'frec\tfreq\trank\tname'
      $sdprint "$dname"
   else
      sdchoose "$dname"
   fi
}







|






|







 







|







 







|







 







|











>













|







 







|







 







|







 







|





|





|



|
>







 







|







 







|





|
|




|







 







|



|







619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
...
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
...
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
...
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
...
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
...
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
...
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
...
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
....
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
....
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
# count linefeed separated lines in string `buffer'.
# we append a trailing \n (implying `buffer' is assumed
# to not contain a trailing empty line) via `sdprint'
# since `wc -l' actually counts the number of \n.
# if buffer is empty we return 0
#-------------------------------------------------------
   typeset -i lc=0    #using `-i' suppresses spurious blanks in `wc -l' output.
   [[ -z $1 ]] || lc=$(wc -l <<< "$1")
   $sdprint $lc
}

function sdlogread {
   sdlog=$(<$dirv)
   sdmagicline=${sdlog%%$'\n'*}
   sdlog=$(grep -v "^$sdmagicstring" <<< "$sdlog")
   sdlist=$(sdstack)
   sdnew=""
}

function sdlogwrite {
   $sdprint $sdmagicstring-- time stamp: $(date)$'\n'"$sdlog" >| $dirv
   sdlist=$(sdstack)
................................................................................
   lines=$(sdlinecount "$sdlog")

   if (( lines > sdmax )); then
      typeset -i limit

      ((limit = sdmax*9/10))
      ((limit = sdlines > limit ? sdlines:limit))
      sdlog=$(tail -$limit <<< "$sdlog")
      mv -f $dirv ${dirv}.bak
      sdlogwrite
   fi
}

function sdremove {
#----------------------------------------
................................................................................
   typeset dname check
   typeset IFS=$'\n'

   sdl=$sdlines
   sdirs -l $sdmax #look at whole database
   sdlines=$sdl

   dname=$($awk -F"\t" '$NF ~ /'"$pat"'/ {print $NF}' <<< "$sdlist")
   sdirs -l $sdlines  #important to reset before possibly returning...
   [[ -n $dname ]] || { $sdprint no match; return 1; }

   $sdprint "$dname"
   $sdprint -n "remove these entries from the logfile? [y/N] "
   read check
   check=${check:-"n"}
................................................................................
   fi

   # note to self: the '|' quoting '|' is not really required here.
   # its done just to silence `ksh -n'..
   pat="${dname//$IFS/\|}"
   pat="^(${pat//\//\\/})\$"

   sdlog=$(egrep -v "$pat" <<< "$sdlog")

   sdlogwrite
}

function sdclean {
#----------------------------------------------
#remove stale entries (and `/') from sdlog/dirv
#----------------------------------------------
   typeset IFS=$'\n'
   typeset -a entries stale
   typeset -i i j
   typeset dname check

   # note to self: mksh is really bad at `+=' string concatenation
   # when the string becomes large (as is the case here with `entries'
   # and, possibly, `stale'. for that reason we now use indexed
   # arrays for buffering and paste the result together in the end.
   # in ksh/bash it doesn't matter: both approaches are equally
   # fast (and both these shells remain faster than mksh).
   for dname in $sdlog; do
      [[ -d ${dname/#\~/$HOME} ]] && {
         entries[i++]=$dname
      } || stale[j++]=$dname
   done
   [[ -n $stale ]] && {
      stale=$(sort <<< "${stale[*]}" | uniq)
      $sdprint "$stale"
      $sdprint -n "eliminate these stale directories from the logfile? [y/N] "
      read check
      check=${check:-"n"}
      if [[ "$check" == "y" ]]; then
         sdlog=${entries[*]}
         sdlogwrite
................................................................................
      stacksize=$(sdlinecount "$sdlist")
      mostlen=${#most}
      i=0
      while ((i < indent0+mostlen)); do
         indent+=" "
         ((i++))
      done
      top=$($awk -F'\t' 'NR <= 10 {print $(NF-1) "\t" $NF}' <<< "$sdlist" )
      top=${top//$'\n'/$'\n'$indent}
   } || stacksize=0

   vdir=${dirv/#$HOME/\~}


   report="
................................................................................
# given unique entry/directory is used as the resulting weight for
# that entry when sorting. note, that the visit frequencies are used
# as a secondary sort key since in extreme cases (very large `sdpower'
# values) weights can become exactly zero at the bottom of the sorted
# stack and the primary key does not suffice for unambiguous sorting.
# generally this will not happen, though.
#-----------------------------------------------------------------------------
   tail -$sdlines <<< "$sdlog" |\
   $awk '
      BEGIN {
         OFS     = "\t"
         sdlines = '$sdlines'
         sdpower = '$sdpower'
      }
      {
................................................................................
      if [[ $pat == "$sdpattern" ]]; then
         ((sdcount++))
      else
         sdcount=1
      fi
      sdpattern=$pat
      ((sdcount > 1)) && {
         rank=$(
            $awk -F"\t" "$awkpat"' {
               if (++count == '$sdcount') {
                  print $(NF-1)
                  exit
               }
            }' <<< "$sdlist"  
         )
         [[ -n $rank ]] && awkpat="NR == $rank" || { sdcount=1; $sdprint starting over ... >&2 ;}
      }
   fi

   matches=$(
      $awk -F"\t" "$awkpat"' {
         sub(/^~/, "'"$HOME"'", $NF)
         print $NF
      }' <<< "$sdlist"
   )

   # select first actually existing directory
   typeset IFS=$'\n'
   for match in $matches; do
      [[ -d $match ]] && {
         dname=$match
         break
................................................................................
               break
            } || {
               [[ -d "$dname" ]] && {
                  # see comment at start of block for explanation of this:
                  command cd "$dname"
                  return $?
               } || {
                  $sdprint "No matching directory"
                  return 1
               }
            }
         } || {
            # we already _know_ that the cd fails _and_ the stack is
            # already maximized. so just let the user see the error
            # message (should be "no such file or directory").
................................................................................
}

function sdchoose { ## dname
   # possibly called by `sdirs' to allow cd to directory selected from
   # list of matches. note to self: sort alphabetically?
   typeset dname="$1"
   typeset -i hits num
   $awk -F"\t" '
      BEGIN {
         print "rank\tindex\tname"
      }
      {
         print $(NF-1) "\t" NR "\t" $NF
      }' <<< "$dname" >&2
   hits=$(wc -l <<< "$dname")
   $sdprint -n "choose index (<CR> = 1; CTRL-C = abort): " >&2
   read -r num
   ((num == 0)) && num=1
   ((num > 0 && num <= hits)) && {
      dname=$($awk -F"\t" 'NR == '$num' {sub(/^~/,"'$HOME'",$NF); print $NF}' <<< "$dname")
      cd "$dname"
      # care is taken that a call to `sdirs' does not send anything to stdout elswhere
      # and here only if we believe that we are operating under tcsh (indicated
      # by `sdistcsh == 1'). this output is then needed by the `sdirs' executable
      # with tcsh.
      ((sdistcsh == 1)) && $sdprint $dname
   }
................................................................................

   if [[ $# == 0 ]]; then
      [[ -n $sdlog ]] || return 1
      dname=$sdlist
   else
      pat="$@"
      pat="${pat//\//\\/}"
      dname=$($awk -F"\t" '$NF ~ /'"$pat"'/' <<< "$sdlist")
      [[ -n $dname ]] || return 1
   fi

   dname=$($dump <<< "$dname")
   if ((sdselect == 0)); then
      $sdprint $'frec\tfreq\trank\tname'
      $sdprint "$dname"
   else
      sdchoose "$dname"
   fi
}