diff --git a/README b/README index 77a62ae..ba4a2da 100644 --- a/README +++ b/README @@ -1,31 +1,23 @@ -******** -G (GOTO) -******** - A faster way to cd to commonly used directories. Inspired by commacd. Tested on -Bash 5.1, but should work on most versions. +Bash and Dash. Installation -************ +============ -On most operatin systems with Bash run:: - - $ curl -o ~/.goto.sh \ - -sSL https://gitea.gch.icu/ge/goto/raw/branch/master/goto.sh - $ echo '[ -f ~/.goto.sh ] && . ~/.goto.sh' >> ~/.bashrc - -Reread ~/.bashrc by command:: +On most operatin systems with Bash run: + $ curl -o ~/.g.sh -sSL https://git.nxhs.cloud/ge/g/raw/branch/master/g.sh + $ echo '[ -f ~/.g.sh ] && . ~/.g.sh' >> ~/.bashrc $ source ~/.bashrc Usage -***** +===== -Available commands (actually is aliases to functions):: +Available commands (actually is aliases to functions): g [] goto directory. If you have single bookmark dir will be changed without prompt. Type dir number in prompt to cd. - g-save [] bookmark $PWD or (save into ~/.goto_saved). + g_save [] bookmark $PWD or (save into ~/.g). Run `g` to show the entire list of bookmarks and select the one you need. @@ -34,10 +26,10 @@ the bookmark number. The exception is when you have a two-digit number (or more) and you select a bookmark whose number is less than 10. Then you have to press `Enter`. -You can pass the Perl regular expression (processed by grep -Pi) as an for 'g'. +You can pass the Perl regular expression (processed by grep -Pi) as an for `g`. At the same time, if only one bookmark is found, then the directory will be changed directly to it. If there are several bookmarks, you will be prompted to -choose the appropriate one. Also you can use autocompletion by pressing `Tab`. +choose the appropriate one. Also you can use autocompletion by pressing Tab. It works in a similar way. Examples @@ -45,60 +37,65 @@ Examples Previously set up your commonly used dirs. Specify dir as argument:: - $ g-save ~/Documents - $ g-save ~/Downloads + $ g_save ~/Documents + $ g_save ~/Downloads or cd into dir and simply run:: - $ g-save + $ g_save -Go to directory by number:: +Go to directory by number: $ g 0 ~/Documents/ 1 ~/Downloads/ : - => cd /home/user/Downloads + => cd /home/ge/Downloads -Goto '~/Downloads' directory by regex:: +Goto '~/Downloads' directory by regex: $ g w - => cd /home/user/Downloads + => cd /home/ge/Downloads -Another way :):: +Another way: $ g 'do[^c]' - => cd /home/gd/Downloads + => cd /home/ge/Downloads and etc. Changelog -********* +========= -06 Feb 2022 - - `~/.gotosave` renamed to `~/.goto_saved` - - Alias `s` changed to `g-save` +v0.3 (05 Oct 2022) -07 Jan 2022 - Initial release. +* Refactored, added POSIX-compatibility +* Script renamed to `g.sh` +* ~/.goto_saved renamed to ~/.g +* Alias `g-save` changed to `g_save` +* Functions renamed + +v0.2 (06 Feb 2022) + +* ~/.gotosave renamed to ~/.goto_saved +* Alias `s` changed to `g-save` + +v0.1 (07 Jan 2022) + +Initial release. Tips -**** +==== -Combining `commacd`, `g` and Bash ``autocd`` option gives a great experience! - -Add to you ~/.bashrc following line:: - - shopt -s autocd - -Now you can type just directory name without 'cd' to change directory. +Combining `commacd`, `g` and Bash ``shopt -s autocd`` option gives a great +experience! Get `commacd` here: https://github.com/shyiko/commacd Alternatives? -************* +============= Some `g` alternatives: - - aliases (yep, Bash builtin aliases) - - https://github.com/huyng/bashmarks +* aliases (yep, Bash builtin aliases) +* https://github.com/huyng/bashmarks diff --git a/goto.sh b/g.sh similarity index 51% rename from goto.sh rename to g.sh index bb52c0a..a277c5f 100644 --- a/goto.sh +++ b/g.sh @@ -1,5 +1,5 @@ -# * g (goto directory) - bookmark directories in Bash. -# * versuion: 0.2 +# * g (goto directory) - bookmark directories in shell. +# * version: 0.3 # This is free and unencumbered software released into the public domain. # @@ -26,52 +26,63 @@ # # For more information, please refer to -_goto_cd() { - ##### cd into directory ##### +_g_file=~/.g +_read_char() { + # Read number of chars into variable + # Usage: _read_char VAR NUM + stty -icanon + eval "$1=\$(dd bs=1 count="$2" 2>/dev/null)" + stty icanon echo +} + +_g_cd() { + # cd into directory if [ ! -d "$1" ]; then - echo "goto: no such directory $1" >&2 + printf 'g: no such directory %s\n' "$1" >&2 return 1 fi - if cd -- "$1" > /dev/null; then echo "$1" else - echo -e "\bgoto: cannot cd into $1" >&2 + printf '\bg: cannot cd into %s\n' "$1" >&2 fi return "$?" } -_goto_prompt() { - ##### Prompt and cd ##### +_g_prompt() { + # Prompt and cd - # Exit if no dirs in ~/.goto_saved - if [ "${#dirs[@]}" -eq 0 ] || [[ "${dirs[@]}" == '' ]]; then + # Exit if no dirs in $_g_file + if [ "$#" -eq 0 ] || [ "$*" = '' ]; then echo goto: nothing to do; return 1 fi # cd into dir without prompt if you have single directory - # in ~/.goto_saved - if [ "${#dirs[@]}" -eq 1 ]; then - _goto_cd "${dirs[@]}" && return 0 + # in $_g_file + if [ "$(echo "$*" | wc -l)" -eq 1 ]; then + _g_cd "$*" && return 0 fi # Print directory list - for i in "${!dirs[@]}"; do - echo -e "$i\t${dirs[$i]%%+(/)}/" | sed "s%${HOME}%~%" + i=0 + echo "$*" | while read -r dir; do + dir="$(echo "$dir" | sed "s%$HOME%~%")" + printf '%s\t%s\n' "$i" "$dir" + i=$((i+1)) done - # Count digits in number of dirs for 'read' - local dirscount="$((${#dirs[@]}-1))" - local numlen="${#dirscount}" - - # Read input to 'num' and set 'target' directory - read -r -n "$numlen" -p ': ' num - if [[ "$num" =~ [0-9]+ ]]; then - target="${dirs[$num]}"; + # Input target dir number + dirs_num="$(echo "$*" | wc -l)" + num_len="${#dirs_num}" + printf '%s' ": " + _read_char _num "$num_len" + if echo "$_num" | grep -E '[0-9]+' >/dev/null; then + _target_dir="$(echo "$*" | + awk -v line="$((_num+1))" 'NR==line {print}')" # Print new line to prevent line concatenation - [ "${#num}" -eq "$numlen" ] && echo - elif [[ "$num" == '' ]]; then + [ "${#_num}" -eq "$num_len" ] && echo + elif [ "$_num" = '' ]; then return 1 # Just exit if you press Enter. else # Print new line and exit if printable chars passed. @@ -80,63 +91,60 @@ _goto_prompt() { fi # cd into directory - _goto_cd "$target" + _g_cd "$_target_dir" } -_goto_search() { - grep -iP "$1" ~/.goto_saved +_g_search() { + grep -iP "$1" "$_g_file" } -_goto() { - ##### Main function ##### - +_g() { + # Main function # Get directory list ('dirs' array) - dirs=() - if [ -f ~/.goto_saved ]; then + if [ -f "$_g_file" ]; then if [ "$1" ]; then # Search dirs with Perl regex - _source="$(_goto_search "$1")" + _dirs="$(_g_search "$1")" else # Load all directories - _source="$(<~/.goto_saved)" + _dirs="$(cat "$_g_file")" fi - while read -r dir; do - dirs+=("$dir") - done <<< "$_source" fi - _goto_prompt # prompt and cd + _g_prompt "$_dirs" # prompt and cd } -_goto_save() { - ##### Save PWD or 1 to ~/.goto_saved file ##### +_g_save() { + # Save dir in $_g_file - local dir - if [ "$1" ]; then - dir="$1" + if [ -n "$1" ]; then + _dir="$1" else - dir="$PWD" + _dir="$PWD" fi - # Exit if directory is already in ~/.goto_saved - if grep "^${dir%%+(/)}\$" ~/.goto_saved > /dev/null; then + # Exit if directory is already in $_g_file + if grep "^${_dir%%+(/)}\$" "$_g_file" >/dev/null 2>&1; then return 1 fi # Save directory - if echo "${dir%%+(/)}" >> ~/.goto_saved; then - echo "$dir" + if echo "${_dir%%+(/)}" >> "$_g_file"; then + echo "$_dir" fi } -_goto_complete() { - # Autocomplete directory by pattern - local pattern=${COMP_WORDS[COMP_CWORD]} - local IFS=$'\n' - COMPREPLY=($(compgen -W "$(printf "%s\n" "$(_goto_search "$pattern")")" -- '')) -} +if type complete >/dev/null 2>&1; then + # Bash completion + _g_complete_bash() { + _pattern=${COMP_WORDS[COMP_CWORD]} + COMPREPLY=($(compgen -W \ + "$(printf "%s\n" "$(_g_search "$_pattern")")" -- '')) + } + complete -F _g_complete_bash g +else + : +fi -complete -F _goto_complete g - -alias g='_goto' -alias g-save='_goto_save' +alias g='_g' +alias g_save='_g_save'