C shell pushd/popd on steroids as Korn Shell functions

Blogfinger inspires me to post some of my crazy KSH function code. The code below is a heavily hacked version of a similar functions that I got from Will several years ago. Someday I should post my partial ASN.1 DER encoder/decoder written in KSH :)

Syntax highlighting courtesy of VIM and its :TOhtml feature.

typeset|grep '\^integer cdx$' > /dev/null || integer cdx=0
typeset|grep '\^integer cdsx$' > /dev/null || integer cdsx=0

function cdhelp
{
    cat <<-"EOF"
        Usage: cdinit  [<file>]
        Usage: cdcl
            Initialize directory list and stack.
            Clear directory list and stack.

        Usage: cdfind  <path> [quiet]
        Usage: cdgrep  <partial path> [first]
            Find a directory, by exact match in the cd list.
            Find matching directories in the cd list.

        Usage: cdls
        Usage: cdrf    [-a|--append] [<files>|-]
            Show the cd list. Use this to save your cd list.
            Read in a cd list and replace the current cd list.

        Usage: cdsv    [<directory paths>] (if none given then CWD)
            Save the given or current directory to the cd list.

        Usage: cdow    <index>
            Overwrite the given entry in the cd list with the CWD.

        Usage: cdto    <index>|<path>|[+]<partial path>
            Change the current directory to an entry from the cd list,
            or, if a path is given, then change to the given path and
            save to the cd list.

        Usage: cdrm [<path>|<index>]
            Remove a directory, or the current directory from the cdlist.

        Usage: cdsort
            Sort the cd list (alphanumerically)

        Usage: pushd   [<directory>]
        Usage: popd    [<number>]
        Usage: rightd  [<number>] (reverse of popd)
        Usage: dirs    (shows dirs in pushd/popd stack)
            Directory stack, similar to the C-Shell built-ins of similar names.
EOF
}

function cdfind
{
    typeset p dir
    integer i
    if [[ $# -lt 1 || -z "$1" ]]
    then
        print "Usage: cdfind <path> [quiet]"
        return 5
    fi
    i=0
    p="$1"
    [[ "$1" != /\* ]] && p="$PWD/$1"
    for dir in "${cdlist[@]}"
    do
        if [[ "$p" = "$dir" ]]
        then
            if [[ "$2" != quiet ]]
            then
                print "Found at index $i"
                return 0
            fi
            return $i
        fi
        i=i+1
    done
    return 255
}

function cdgrep
{
    typeset i s
    integer i=0 s=1
    if [[ $# -lt 1 || $# -gt 2 || -z "$1" ]]
    then
        print "Usage: cdgrep <partial path> [first]"
    fi
    while [[ i -lt cdx ]]
    do
        if eval "[[ \\"${cdlist[i]}\\" = \*${1}\* ]]"
        then
            print "$i ${cdlist[i]}"
            s=0
            [[ "$2" = first ]] && return 0
        fi
        i=i+1
    done
    return $s
}

function cdshow
{
    typeset found_at
    cdfind "$PWD" quiet
    found_at=$?
    [[ $found_at -eq 255 ]] && found_at='[unsaved]'
    print "$found_at $PWD"
    return 0
}

function cdto
{
    typeset i j dir
    integer i
    if [[ $# -ne 1 || -z "$1" ]]
    then
        print "Usage: cdto <index>|<path>|[+]<partial path>"
    fi
    if [[ "$1" = +([0-9]) ]]
    then
        if cd "${cdlist[$1]}"
        then
            print "$1 ${cdlist[$1]}"
            return 0
        fi
    else
        if [[ "$1" != \\+\* && -d "$1" ]]
        then
            if cd "$1"
            then
                cdsv > /dev/null
                cdshow
                return 0
            fi
        fi
        cdgrep "${1#\\+}" first|read j dir
        if [[ -d "$dir" ]]
        then
            if cd "$dir"
            then
                cdsv > /dev/null
                cdshow
                return 0
            fi
        fi
    fi
    print "Could not cd to $1"
    return 1
}

function cdls
{
    integer i=0

    while [[ i -lt cdx ]]
    do
        print $i ${cdlist[i]}
        i=i+1
    done
}

cdcl ()
{
        cdx=0
        cdsx=0
        unset cdlist
        unset cdstack
        set -A cdlist
        set -A cdstack
}

function cdsv
{
    typeset dir current
    integer i
    if [[ "$1" = -h || "$1" = --help ]]
    then
        print "Usage: cdsv [<directory paths>] (if none given then CWD)"
        return 1
    fi
    # Look for $PWD in cdlist[]
    current=""
    if [[ $# -eq 0 ]]
    then
        current="current "
        set -- "$PWD"
    fi
    for dir in "$@"
    do
        cdfind "$dir" quiet
        i=$?
        if [[ $i -ne 255 ]]
        then
            print "The ${current}directory $dir is already in the cdlist ($i)"
            continue
        fi
        #cdlist[${#cdlist[@]}]="$PWD"
        cdlist[cdx]=$PWD
        cdx=cdx+1
    done
}

# overwrite entry
function cdow
{
    integer i
    if [[ $# -ne 1 || "$1" != +([0-9]) ]];
    then
        print "Usage: cdow index#(see cdls)"
        return 1
    fi
    cdfind "$PWD" quiet
    i=$?
    if [[ "$1" -gt ${#cdlist[@]} ]]
    then
        print "Index is beyond cdlist end ($1 > ${#cdlist[@]})"
        return 1
    fi
    if [[ $i -ne 255 ]]
    then
        print "The current directory is already in the cdlist ($i)"
        return 1
    fi
    cdlist[$1]=$PWD
    return 0
}

function cdrf
{
    typeset f status dir spath
    typeset cdx_copy cdlist_copy usage
    integer cdx_copy=0

    usage="Usage: cdrf [-a|--append] [<files>|-]"
    status=1

    set -A cdlist_copy --

    # Options
    while [[ $# -gt 0 && "$1" = -?\* ]]
    do
        case "$1" in
            -s|--strip)
                spath=$2
                shift
                ;;
            -a|--append)
                cdx_copy=$cdx
                set -A cdlist_copy -- "${cdlist[@]}"
                ;;
            \*)  print "$usage"
                return 1
                ;;
        esac
        shift
    done

    # Default to reading stdin
    [[ $# -eq 0 ]] && set -- -

    # Process cdlist files
    for f in "$@"
    do
        if [[ "$f" = - ]]
        then
            # STDIN
            sed -e 's/\^[0-9]\* //' | while read dir
            do
                    dir=${dir#$spath}
                    [[ "$dir" != /\* ]] && dir="$PWD/$dir"
                    cdlist_copy[cdx_copy]=${dir}
                    cdx_copy=cdx_copy+1
            done
            status=0
            continue
        fi

        # Find the cdlist file
        if [[ ! -f "$f" && ! -f "$HOME/.cdpath.$f" ]]
        then
            print "No such cdpath file $p or $HOME/.cdpath.$f"
            continue
        fi

        [[ ! -f "$f" && -f "$HOME/.cdpath.$f" ]] && f="$HOME/.cdpath.$f"

        # Read the cdlist file
        sed -e 's/\^[0-9]\* //' "$f" | while read dir
        do
                dir=${dir#$spath}
                [[ "$dir" != /\* ]] && dir="$PWD/$dir"
                cdlist_copy[cdx_copy]=${dir}
                cdx_copy=cdx_copy+1
        done
        status=0
    done

    [[ $status -ne 0 ]] && return $status

    # Install new cdlist
    cdcl
    cdx=$cdx_copy
    set -A cdlist -- "${cdlist_copy[@]}"
    return 0
}

function cdsort
{
    cdls | sed -e 's/\^[0-9]\* //' | sort -u | cdrf -
    cdls
}

function cdrm
{
    integer i
    if [[ -n "$1" && "$1" != +([0-9]) ]]
    then
        cdfind "${1:-$PWD}" quiet
        i=$?
    elif [[ -n "$1" && "$1" = +([0-9]) ]]
    then
        i=$1
    elif [[ $# -ne 0 ]]
    then
        print "Usage: cdrm <path>|<index>"
        return 1
    else
        cdfind "${1:-$PWD}" quiet
        i=$?
    fi
    if [[ "$i" -eq 255 ]] && return 1
    then
        cdfind "${1:-$PWD}"
    fi

    cdls|grep -v "\^${i} "|sed -e 's/\^[0-9]\* //'|cdrf
    i=$?
    cdls
    return $i
}

function pushd
{
    if [[ $# -gt 0 && ! -d "$1" ]]
    then
        print "Can't cd to: $1"
        return 1
    fi
    cdstack[$cdsx]="$PWD"
    if cd "${1:-.}"
    then
        cdsx=cdsx+1
        cdstack[cdsx]="$PWD"
        [[ "$2" = sv || "$2" = save ]] && cdsv
        cdshow
    else
        print "Could not cd to $1"
        return 1
    fi
    return 0
}

function pushdsv
{
    pushd "$1" save
}

function popd
{
    if [[ $((cdsx-${1:-1})) -lt 0 || $((${#cdstack[@]} - ${1:-1})) -lt 0 ]]
    then
        print "Empty stack or popping too much"
        return 1
    fi
    if [[ ! -d "${cdstack[cdsx-${1:-1}]}" ]]
    then
        print "Can't cd to: ${cdstack[cdsx-${1:-1}]}"
        return 2
    fi
    if cd "${cdstack[cdsx-1]}"
    then
        cdsx=cdsx-1
        cdshow
    else
        print "Could not popd to ${cdstack[cdsx-1]}"
        return 1
    fi
    return 0
}

function dirs
{
    integer i
    if [[ ${#cdstack[@]} -eq 0 ]]
    then
        print "Empty stack"
        return 1
    fi
    i=1
    print -n "${cdstack[0]}"
    while [[ $i -le $((cdsx)) && $i -le ${#cdstack[@]} ]]
    do
        print -n " ${cdstack[i]}"
        i=i+1
    done
    if [[ ${#cdstack[@]} -gt $cdsx && $i -lt ${#cdstack[@]} ]]
    then
        print -n " <-> "
        while [[ $i -lt ${#cdstack[@]} ]]
        do
            print -n " ${cdstack[i]}"
            i=i+1
        done
    fi
    print
    return 1
}

function rightd
{
    typeset i
    integer i
    if [[ -n "$1" && "$1" != +([0-9]) ]]
    then
        print "Usage: rightd [<number>]"
        return 1
    fi
    i=${1:-1}
    if [[ ${#cdstack[@]} -le $((cdsx+i)) ]]
    then
        print "No directories to the right on the stack"
        return 1
    fi
    if [[ ! -d "${cdstack[cdsx+i]}" ]]
    then
        print "Can't cd to: ${cdstack[cdsx+i]}"
        return 2
    fi
    if cd "${cdstack[cdsx+i]}"
    then
        cdsx=cdsx+i
        cdshow
    else
        print "Could not cd to ${cdstack[cdsx+i]}"
        return 1
    fi
    return 0
}

cdinit ()
{
    [[ -n "${recd}" ]] && return 0
    cdcl
    if [[ $# -eq 0 && -f ~/lib/cdpaths ]]
    then
        cdrf < ~/lib/cdpaths
    elif [[ $# -eq 1 ]]
    then
        cdrf "$1"
    fi
    cdls
}

vicd ()
{
    ${EDITOR:-vi} ~/.kshcd
}

recd ()
{
    typeset recd
    recd=inprogress
    . ~/.kshcd
    recd=""
}

cdinit

Comments:

Nicolas,
great stuff! Thanks!
It inspired me to a nice "mkdir && cd" function I wanted to have since ages, but never searched for or programmed - in my blog entry now.

cu,
Bernd

Posted by blogfinger on April 05, 2007 at 07:24 AM CDT #

Post a Comment:
Comments are closed for this entry.
About

I'm an engineer at Oracle (erstwhile Sun), where I've been since 2002, working on Sun_SSH, Solaris Kerberos, Active Directory interoperability, Lustre, and misc. other things.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today