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

Check-in [630cfacdfe]
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 | mksh
Files: files | file ages | folders
SHA1: 630cfacdfea88717a3bfa1950470a969bf148b0d
User & Date: vdh 2019-12-22 16:20:22
Context
2019-12-22 17:08
tidy up: start to remove stuff from branches that should (or need) only be present on trunk. check-in: 6468d8b10d user: vdh tags: mksh
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:42
replace piping of `print' output into commands by `<<<' here strings whereever possible. some other minor edits. check-in: 78020ab235 user: vdh tags: ksh
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 14:09
minor adjustments to quickstart.md. check-in: 77dcf74ab6 user: vdh tags: mksh
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to sd.ksh.

614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
...
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
...
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
...
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
...
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
...
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
...
885
886
887
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
...
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
....
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
....
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
# 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
................................................................................
               ((sdsilent == 1)) || {
                  $sdprint "using first valid match from:"
                  $sdprint "$matches"
               }
               break
            } || {
               [[ -d "$dname" ]] && {
                  print $errmsg
                  return $status
               } || {
                  print "No matching directory"
                  return 1
               }
            }
         } || { print $errmsg; return $status; }  # should be the "not in logfile and no such file or directory" case
      }
   done

   # avoid logging `cd' to one of (home|current|root)-dir, since otherwise
   #
   # -- $HOME would always be at the top of the stack (really undesirable),
   # -- an accidental/reaffirmative `cd .' would artificially increase
................................................................................
}

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
}







|






|







 







|







 







|







 







|











>













|







 







|







 







|







 







|





|





|



|
>







 







|


|



|







 







|





|
|




|







 







|



|







614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
...
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
...
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
...
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
...
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
...
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
...
886
887
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
...
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
....
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
....
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
# 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
................................................................................
               ((sdsilent == 1)) || {
                  $sdprint "using first valid match from:"
                  $sdprint "$matches"
               }
               break
            } || {
               [[ -d "$dname" ]] && {
                  $sdprint $errmsg
                  return $status
               } || {
                  $sdprint "No matching directory"
                  return 1
               }
            }
         } || { $sdprint $errmsg; return $status; }  # should be the "not in logfile and no such file or directory" case
      }
   done

   # avoid logging `cd' to one of (home|current|root)-dir, since otherwise
   #
   # -- $HOME would always be at the top of the stack (really undesirable),
   # -- an accidental/reaffirmative `cd .' would artificially increase
................................................................................
}

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
}