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
# 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)







|






|







619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# 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)
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
   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 {
#----------------------------------------







|







658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
   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 {
#----------------------------------------
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
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
   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"}
   if [[ "$check" != "y" ]]; then
      return
   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







|
















|











>













|







682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
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
   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"}
   if [[ "$check" != "y" ]]; then
      return
   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
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
      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="







|







759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
      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="
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
# 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'
      }
      {







|







816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
# 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'
      }
      {
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
      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







|





|





|



|
>







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
      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
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
               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").







|







992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
               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").
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
}

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
   }







|





|
|




|







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
}

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
   }
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132

   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
}







|



|







1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134

   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
}