Last active
July 18, 2025 06:50
-
-
Save vrdhn/1ca6bb16cb3ca388703ab7b0c9cb4cb4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Created by ChatGPT, prompted by Vardhan | |
| # | |
| # Enhanced cd wrapper for Bash with stack, bookmarks, and pattern matching | |
| # | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # β Features β | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # π Directory Stack (max 10 entries) | |
| # - cd (no args) : print the directory stack | |
| # - cd - : rotate top 2 entries (swap top and next) | |
| # - cd -- : rotate top 3 entries (cycle top 3) | |
| # - cd --- : rotate top 4 entries, etc. | |
| # - cd -N (N = digit) : rotate top N+1 entries left by N to bring index N to top | |
| # - cd +N : swap top and entry N in stack | |
| # | |
| # π Bookmarks | |
| # - cd + : list bookmarks | |
| # - cd +name /path : save bookmark 'name' with given path (path optional, defaults to current) | |
| # - cd +name : set bookmark 'name' to current directory | |
| # - cd -name : jump to bookmark 'name' | |
| # | |
| # π Directory shortcuts | |
| # - cd ... : cd ../.. | |
| # - cd .... : cd ../../.. | |
| # | |
| # π§ Pattern Matching (set by match_define) | |
| # - match_define name pattern (e.g. "/data/@_user/@_proj") | |
| # - On cd, sets _name=name, and variables like _user, _proj, _rest | |
| # | |
| # π¬ Example PS1 usage: | |
| # export PS1='\u@\h:\w$([[ "$_name" != "nomatch" ]] && echo " ($_name $_user $_proj)")\$ ' | |
| # | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Stack and bookmarks initialization | |
| [[ -z "${CD_STACK+x}" ]] && declare -a CD_STACK=() | |
| [[ -z "${CD_BOOKMARKS+x}" ]] && declare -A CD_BOOKMARKS=() | |
| [[ -z "${CD_PATTERNS+x}" ]] && declare -A CD_PATTERNS=() | |
| # Clear all _* variables from pattern matching | |
| clear_pattern_vars() { | |
| for var in $(compgen -v); do | |
| [[ "$var" == _* ]] && unset "$var" | |
| done | |
| } | |
| # Define a pattern for matching in form: match_define name pattern | |
| match_define() { | |
| local name="$1" pattern="$2" | |
| if [[ -z "$name" || -z "$pattern" ]]; then | |
| echo "Usage: match_define name pattern" >&2 | |
| return 1 | |
| fi | |
| CD_PATTERNS["$name"]="$pattern" | |
| } | |
| # Match $PWD against patterns and set variables accordingly | |
| match_patterns() { | |
| local pwd_path="$PWD" | |
| clear_pattern_vars | |
| _name="nomatch" | |
| for pname in "${!CD_PATTERNS[@]}"; do | |
| local pattern="${CD_PATTERNS[$pname]}" | |
| IFS='/' read -ra pat_parts <<< "$pattern" | |
| IFS='/' read -ra pwd_parts <<< "$pwd_path" | |
| declare -A captures=() | |
| local i=0 j=0 match=1 | |
| while (( i < ${#pat_parts[@]} && j < ${#pwd_parts[@]} )); do | |
| local p="${pat_parts[i]}" | |
| local w="${pwd_parts[j]}" | |
| if [[ "$p" =~ ^@_([a-zA-Z_][a-zA-Z0-9_]*)$ ]]; then | |
| captures["${BASH_REMATCH[1]}"]="$w" | |
| ((i++, j++)) | |
| elif [[ "$p" == "$w" ]]; then | |
| ((i++, j++)) | |
| else | |
| match=0 | |
| break | |
| fi | |
| done | |
| if (( match )) && (( i == ${#pat_parts[@]} )); then | |
| _name="$pname" | |
| for k in "${!captures[@]}"; do | |
| export "_$k"="${captures[$k]}" | |
| done | |
| # Join remainder parts with / for _rest or empty string if none | |
| if (( j < ${#pwd_parts[@]} )); then | |
| _rest="${pwd_parts[@]:j}" | |
| # Convert array to slash-separated string | |
| _rest="${_rest// /\/}" | |
| else | |
| _rest="" | |
| fi | |
| export _rest | |
| export _name | |
| return 0 | |
| fi | |
| done | |
| # No match found | |
| _name="nomatch" | |
| export _name | |
| unset _rest | |
| return 1 | |
| } | |
| # Update directory stack, keeping max 10 entries, no duplicates (top is $PWD) | |
| update_stack() { | |
| local current="$PWD" | |
| local -a newstack=("$current") | |
| for d in "${CD_STACK[@]}"; do | |
| [[ "$d" != "$current" ]] && newstack+=("$d") | |
| done | |
| CD_STACK=("${newstack[@]:0:10}") | |
| } | |
| # Print the directory stack nicely | |
| print_stack() { | |
| echo "π Directory stack (top is current):" | |
| local i=0 | |
| for d in "${CD_STACK[@]}"; do | |
| echo " [$i] $d" | |
| ((i++)) | |
| done | |
| } | |
| # Rotate left the top n entries by k places (default k=1) | |
| rotate_stack_left_n() { | |
| local n="$1" | |
| local k="${2:-1}" | |
| (( n > ${#CD_STACK[@]} )) && n=${#CD_STACK[@]} | |
| (( n < 2 )) && return 0 | |
| k=$((k % n)) | |
| local -a slice=("${CD_STACK[@]:0:n}") | |
| local -a rotated=("${slice[@]:k}" "${slice[@]:0:k}") | |
| CD_STACK=("${rotated[@]}" "${CD_STACK[@]:n}") | |
| builtin cd "${CD_STACK[0]}" || return 1 | |
| update_stack | |
| match_patterns | |
| } | |
| # Rotate top n entries to bring index to top (rotate left by index) | |
| rotate_stack_to_index() { | |
| local index="$1" | |
| (( index < 0 || index >= ${#CD_STACK[@]} )) && { | |
| echo "β Invalid stack index $index" >&2 | |
| return 1 | |
| } | |
| local n=$((index + 1)) | |
| rotate_stack_left_n "$n" "$index" | |
| } | |
| # Swap top and entry at given index in stack | |
| swap_stack_top_with() { | |
| local index="$1" | |
| (( index >= ${#CD_STACK[@]} )) && { | |
| echo "β Invalid stack index $index" >&2 | |
| return 1 | |
| } | |
| local tmp="${CD_STACK[0]}" | |
| CD_STACK[0]="${CD_STACK[$index]}" | |
| CD_STACK[$index]="$tmp" | |
| builtin cd "${CD_STACK[0]}" || return 1 | |
| update_stack | |
| match_patterns | |
| } | |
| # Resolve relative path to absolute path safely | |
| resolve_path() { | |
| local path="$1" | |
| if [[ "$path" == /* ]]; then | |
| echo "$path" | |
| else | |
| echo "$(cd "$path" 2>/dev/null && pwd)" | |
| fi | |
| } | |
| # The main cd wrapper function | |
| cd_wrapper() { | |
| local arg1="$1" | |
| local arg2="$2" | |
| # No args: print stack | |
| [[ -z "$arg1" ]] && { print_stack; return 0; } | |
| # List bookmarks: cd + | |
| if [[ "$arg1" == "+" ]]; then | |
| echo "π Bookmarks:" | |
| if (( ${#CD_BOOKMARKS[@]} == 0 )); then | |
| echo " (none)" | |
| else | |
| for k in "${!CD_BOOKMARKS[@]}"; do | |
| echo " +$k β ${CD_BOOKMARKS[$k]}" | |
| done | |
| fi | |
| return 0 | |
| fi | |
| # Save bookmark: cd +name [path] | |
| if [[ "$arg1" =~ ^\+([A-Za-z_][A-Za-z0-9_]*)$ ]]; then | |
| local key="${BASH_REMATCH[1]}" | |
| local path="${arg2:-$PWD}" | |
| local fullpath | |
| fullpath=$(resolve_path "$path") | |
| if [[ -z "$fullpath" ]]; then | |
| echo "β Invalid path: $path" >&2 | |
| return 1 | |
| fi | |
| CD_BOOKMARKS["$key"]="$fullpath" | |
| echo "β Bookmark +$key saved as $fullpath" | |
| return 0 | |
| fi | |
| # Jump to bookmark: cd -name | |
| if [[ "$arg1" =~ ^-([A-Za-z_][A-Za-z0-9_]*)$ ]]; then | |
| local key="${BASH_REMATCH[1]}" | |
| if [[ -z "${CD_BOOKMARKS[$key]}" ]]; then | |
| echo "β Bookmark not found: $key" >&2 | |
| return 1 | |
| fi | |
| builtin cd "${CD_BOOKMARKS[$key]}" || return 1 | |
| update_stack | |
| match_patterns | |
| return 0 | |
| fi | |
| # Rotate stack: cd - , --, --- | |
| if [[ "$arg1" =~ ^-+$ ]]; then | |
| local count=$(( ${#arg1} + 1 )) # cd - means rotate top 2, -- means 3 etc | |
| rotate_stack_left_n "$count" 1 | |
| return 0 | |
| fi | |
| # Rotate to index: cd -N (N digit) | |
| if [[ "$arg1" =~ ^-([0-9]+)$ ]]; then | |
| local idx="${BASH_REMATCH[1]}" | |
| rotate_stack_to_index "$idx" | |
| return $? | |
| fi | |
| # Swap top with stack index: cd +N | |
| if [[ "$arg1" =~ ^\+([0-9]+)$ ]]; then | |
| local idx="${BASH_REMATCH[1]}" | |
| swap_stack_top_with "$idx" | |
| return $? | |
| fi | |
| # Handle ... , .... etc as ../.. , ../../.. | |
| if [[ "$arg1" =~ ^\.{2,}$ ]]; then | |
| local ups=$(( ${#arg1} - 1 )) | |
| local path="" | |
| for ((i=0; i<ups; i++)); do | |
| path+="../" | |
| done | |
| builtin cd "$path" || return 1 | |
| update_stack | |
| match_patterns | |
| return 0 | |
| fi | |
| # Normal cd | |
| builtin cd "$arg1" || return 1 | |
| update_stack | |
| match_patterns | |
| } | |
| # Override built-in cd | |
| cd() { | |
| cd_wrapper "$@" | |
| } | |
| # Initialize stack with current directory on script load | |
| update_stack | |
| match_patterns | |
| # Export PS1 example usage: | |
| # | |
| # export PS1='\u@\h:\w$([[ "$_name" != "nomatch" ]] && echo " ($_name $_user $_proj $_rest)")\$ ' | |
| # | |
| # This will display pattern matching variables automatically in prompt when you cd. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment