## improved CD commands: _QCD_FIND=find if [[ "$OSTYPE" = "darwin"* ]]; then alias tac=gtac _QCD_FIND=gfind fi mkdir -p $HOME/.config/qcd/ _QCD_HISTORY=$HOME/.config/qcd/cdhistory _QCD_BOOKMARKS=$HOME/.config/qcd/bookmarks if which sqlite3 &>/dev/null; then _QCD_DB=$HOME/.config/qcd/db sqlite3 "$_QCD_DB" "CREATE TABLE IF NOT EXISTS history (path TEXT PRIMARY KEY UNIQUE, key TEXT NOT NULL, time INT NOT NULL); CREATE TABLE IF NOT EXISTS bookmarks (key TEXT PRIMARY KEY UNIQUE, path TEXT NOT NULL, time INT NOT NULL);" fi if [[ -e "$HOME/.qcd" ]]; then cat "$HOME/.qcd" >> "$_QCD_BOOKMARKS" mv "$HOME/.qcd" "$HOME/.qcd.bak" fi function gcd() { # guess cd, find first match in folder, or ask if multiple hits local cdto QCDPATH case $OSTYPE in darwin*) QCDPATH=$( dirname $( realpath ${BASH_SOURCE[0]} ) ) ;; *) QCDPATH=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) ;; esac cdto=$( python3 "$QCDPATH"/files/gcd-findmatch.py "$@" ) [[ -z "$cdto" ]] && return \cd "$cdto" } # gcd ends function hcd() { if [[ -n "Q_CD_DB" ]]; then hcd_sqlite "$@" return $? fi # History cd. Run without arguments to see list of entries, number as arg. to change directory. [[ "$1" = "-h" ]] && { echo History cd. Run without arguments to see list of entries, number as arg. to change directory. return } local d if [ -z "$1" ] then tail -n 20 "$_QCD_HISTORY" | head -n 19 | cut -d: -f2 | tac | cat -n | sed 's,^ \+,,' | tac return fi d=$( tail -n 20 "$_QCD_HISTORY" | head -n 19 | cut -d: -f2 | tac | cat -n | sed 's,^ \+,,' | grep -h "^$1 " ) d=${d/* /} if [ ! -z "$d" ] then \cd "$d" fi } function hcd_sqlite() { # History cd. Run without arguments to see list of entries, number as arg. to change directory. [[ "$1" = "-h" ]] && { echo History cd. Run without arguments to see list of entries, number as arg. to change directory. return } local d if [ -z "$1" ]; then sqlite3 -column "$_QCD_DB" "SELECT ROW_NUMBER() OVER (ORDER BY time DESC) AS row, path FROM history ORDER BY row LIMIT 20" return fi d=$( sqlite3 -column "$_QCD_DB" "SELECT path FROM (SELECT ROW_NUMBER() OVER (ORDER BY time DESC) AS row, path FROM history) WHERE row = $1" ) if [ ! -z "$d" ] then \cd "$d" fi } function cd_history () { # Function that replaces "cd". It stores visited folder in ~/.bash_cdhistory local old local b if [ -z "$1" ] then \cd "$HOME" return fi \cd "$1" b=$( basename "$PWD" ) b=${b:0:42} if [[ -n "$_QCD_DB" ]]; then local cd_time printf -v cd_time "%(%s)T000" -1 sqlite3 "$_QCD_DB" "INSERT OR REPLACE INTO history (path,key,time) VALUES ('$PWD','$b',$cd_time);" &>/dev/null || true else touch "$_QCD_HISTORY" old=$( tail -n 499 "$_QCD_HISTORY" ) echo "$old" > "$_QCD_HISTORY" b=$( basename "$PWD" ) echo "$b:$PWD" >> "$_QCD_HISTORY" fi } alias cd=cd_history function qcd_sqlite() { # cd command, that jumps to folders visited in the near history, or user bookmarked folders local OPTIND local OPTARG local opt local case local d while getopts ae:hiILl:m opt do case "$opt" in a) # Adding local name=${!OPTIND} # Remove / chars in name name=${name//\//} name=${name//:/} if [ -z "$name" ] then name=$( basename $( pwd )) fi echo "$name":$PWD local cd_time printf -v cd_time "%(%s)T000" -1 sqlite3 "$_QCD_DB" "INSERT OR REPLACE INTO bookmarks (path,key,time) VALUES ('$PWD','$name',$cd_time);" &>/dev/null || true return ;; i) case="-i" ;; I) sqlite3 "$_QCD_DB" "CREATE TABLE IF NOT EXISTS history (path TEXT PRIMARY KEY UNIQUE, key TEXT NOT NULL, time INT NOT NULL); CREATE TABLE IF NOT EXISTS bookmarks (key TEXT PRIMARY KEY UNIQUE, path TEXT NOT NULL, time INT NOT NULL);" return ;; L) echo "## History ##" sqlite3 -column "$_QCD_DB" "SELECT key,path FROM (SELECT key, path, time FROM history ORDER BY time DESC LIMIT 500) ORDER BY time" echo echo "## Bookmarks ##" sqlite3 -column "$_QCD_DB" "SELECT key, path FROM bookmarks ORDER BY key" return ;; l) local d=$( sqlite3 "$_QCD_DB" "SELECT path FROM bookmarks WHERE key LIKE '$OPTARG%' LIMIT 1" ) if [[ -z "$d" ]]; then d=$( sqlite3 "$_QCD_DB" "SELECT path FROM (SELECT key, path, time FROM history ORDER BY time DESC) WHERE key LIKE '$OPTARG%' LIMIT 1" ) fi echo $d return ;; e) local d=$( sqlite3 "$_QCD_DB" "SELECT path FROM bookmarks WHERE key LIKE '$OPTARG%' LIMIT 1" ) if [[ -z "$d" ]]; then d=$( sqlite3 "$_QCD_DB" "SELECT path FROM (SELECT key, path, time FROM history ORDER BY time DESC) WHERE key LIKE '$OPTARG%' LIMIT 1" ) fi echo QCD=$d QCD="$d" return ;; m) sqlite3 -column "$_QCD_DB" "DROP TABLE IF EXISTS tmp_history" sqlite3 -column "$_QCD_DB" "CREATE TABLE tmp_history AS SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY time DESC) AS row,key,path,time FROM history ORDER BY row) WHERE row < 10000" sqlite3 -column "$_QCD_DB" "DELETE FROM history" sqlite3 -column "$_QCD_DB" "INSERT INTO history SELECT path,key,time FROM tmp_history" sqlite3 -column "$_QCD_DB" "DROP TABLE IF EXISTS tmp_history" sqlite3 -column "$_QCD_DB" "CREATE TABLE IF NOT EXISTS tmp_notexists (path TEXT); DELETE FROM tmp_notexists;" local db_dir while read db_dir; do if [[ ! -d "$db_dir" ]]; then sqlite3 "$_QCD_DB" "INSERT INTO tmp_notexists (path) VALUES ('$db_dir');" &>/dev/null || true fi done < <( sqlite3 "$_QCD_DB" "SELECT path FROM history" ) sqlite3 -column "$_QCD_DB" "DELETE FROM history WHERE path IN ( SELECT path FROM tmp_notexists );" return ;; h) echo 'qcd [-hiLm]|[-al] [name] Version: 2024-10-14 Change current working path based on the sqlite3 db '"$_QCD_DB"' -a [name] Adds the path to the list You may add the name of the path, but when omitted the basename will be used -e [name] Show the match, store in variable QCD -i Case insensitive search, must come first. -I Install (use only once) -l [name] Show the match, but do not change -L Lists the paths -m Maintain the list, deleting non-existing entries' return ;; esac done shift $(($OPTIND - 1)) if [ -z "$1" ] then [[ $OPTIND -gt 1 ]] && return \cd; return fi unset OPTSTRING unset OPTIND if [ "$1" = "-" ] then hcd_sqlite 1 return fi local d=$( sqlite3 "$_QCD_DB" "SELECT path FROM bookmarks WHERE key LIKE '$1%' LIMIT 1" ) if [[ -z "$d" ]]; then d=$( sqlite3 "$_QCD_DB" "SELECT path FROM (SELECT key, path, time FROM history ORDER BY time DESC) WHERE key LIKE '$1%' LIMIT 1" ) fi if [ ! -z "$d" ] then \cd "$d" fi } function qcd() { if [[ -n "$_QCD_DB" ]]; then qcd_sqlite "$@" return $? fi # cd command, that jumps to folders visited in the near history, or user bookmarked folders local OPTIND local OPTARG local opt local case local d [ -e "$_QCD_BOOKMARKS" ] || touch "$_QCD_BOOKMARKS" [ -e "$_QCD_HISTORY" ] || touch "$_QCD_HISTORY" while getopts ae:hiILl:m opt do case "$opt" in a) # Adding local name=${!OPTIND} # Remove / chars in name name=${name//\//} name=${name//:/} if [ -z "$name" ] then name=$( basename $( pwd )) fi echo "$name":$( pwd ) echo "$name":$( pwd ) >> "$_QCD_BOOKMARKS" return ;; i) case="-i" ;; I) touch "$_QCD_BOOKMARKS" "$_QCD_HISTORY" return ;; L) echo "## History $_QCD_HISTORY ##" cat "$_QCD_HISTORY" echo echo "## Saved $_QCD_BOOKMARKS ##" cat "$_QCD_BOOKMARKS" return ;; l) local d=$( grep $case -h ^"$OPTARG" "$_QCD_BOOKMARKS" <( tac "$_QCD_HISTORY" ) | head -n 1 ) d=${d/*:/} echo $d return ;; e) local d=$( grep $case -h ^"$OPTARG" "$_QCD_BOOKMARKS" <( tac "$_QCD_HISTORY" ) | head -n 1 ) d="${d/*:/}" echo QCD=$d QCD="$d" return ;; m) local IFS=$'\n' touch "$_QCD_BOOKMARKS" "$_QCD_HISTORY" touch "$_QCD_BOOKMARKS".tmp "$_QCD_HISTORY".tmp for line in $( cat "$_QCD_HISTORY" ); do if [ -d "${line/*:/}" ]; then echo "$line" >> "$_QCD_HISTORY".tmp fi done mv "$_QCD_HISTORY".tmp "$_QCD_HISTORY" for line in $( cat "$_QCD_BOOKMARKS" ); do if [ -d "${line/*:/}" ]; then echo "$line" >> "$_QCD_BOOKMARKS".tmp fi done mv "$_QCD_BOOKMARKS".tmp "$_QCD_BOOKMARKS" return ;; h) echo 'qcd [-hiLm]|[-al] [name] Version: 2022-10-14 Change current working path based on the list '"$_QCD_BOOKMARKS"' Keeps a history of folders visited in '"$_QCD_HISTORY"' -a [name] Adds the path to the list You may add the name of the path, but when omitted the basename will be used -e [name] Show the match, store in variable QCD -i Case insensitive search, must come first. -I Install (use only once) -l [name] Show the match, but do not change -L Lists the paths -m Maintain the list, deleting non-existing entries' return ;; esac done shift $(($OPTIND - 1)) if [ -z "$1" ] then [[ $OPTIND -gt 1 ]] && return \cd; return fi unset OPTSTRING unset OPTIND if [ "$1" = "-" ] then d=$( tail -n 1 "$_QCD_HISTORY" ) d=${d/*:/} \cd "$d" return fi d=$( grep $case -h ^"$1" "$_QCD_BOOKMARKS" <( tac "$_QCD_HISTORY" ) | head -n 1 ) d=${d/*:/} if [ ! -z "$d" ] then \cd "$d" fi } _qcd() { local cur local d cur=${COMP_WORDS[COMP_CWORD]} if [[ "$cur" == -* ]]; then COMPREPLY=( $( compgen -W "-a -i -h -l -L -m" -- $cur ) ) return 0 fi COMPREPLY=( $( compgen -W "$( grep -h ^"$cur" "$_QCD_BOOKMARKS" <( tac "$_QCD_HISTORY" ) | cut -d: -f1 )" ) ) } complete -F _qcd qcd # qcd ends function ncd() { # echo Next sibling cd [[ "$1" = "-h" ]] && { echo -e "Next sibling cd: change directory to [../next_folder]. \nncd - to go to previous folder. \nncd . to descend to first subfolder." return } local _current_pwd _iter_d _iter_prev _current_found _current_pwd=$( basename "$( pwd )" ) if [ "$1" = "." ]; then for _iter_d in *; do if [ ! -d "$_iter_d" ]; then continue; fi \cd "$_iter_d" return done return fi \cd .. for _iter_d in *; do if [ ! -d "$_iter_d" ]; then continue; fi if [ -n "$_current_found" ]; then \cd "$_iter_d" return fi if [ "$_iter_d" = "$_current_pwd" ]; then _current_found=true if [ "$1" = "-" ]; then if [ -z "$_iter_prev" ]; then echo $_current_pwd was the first folder. else \cd "$_iter_prev"; return fi fi fi _iter_prev="$_iter_d" done if [ -n "$_current_found" ]; then echo "$_current_pwd" was the last folder. else echo "$_current_pwd" was not found in ../ fi } function scd() { # search cd, find first recursively match in a folder [[ "$1" = "-h" ]] && { echo Search cd. First argument path to search, optionally second argument where to search from. return } local cdto cdfrom [[ -z "$2" ]] && { cdfrom="." } || { cdfrom="$2" } cdto=$( $_QCD_FIND "$cdfrom" -mindepth 1 -type d -iname "*$1*" | sort -V | head -n 1 ) echo $cdto [[ -z "$cdto" ]] && return \cd "$cdto" } # scd ends function wcd() { # cd to folder that matches "which" dirname if [[ "$1" = "-h" ]] || [[ -z "$1" ]]; then echo which-cd. Give executable name to cd to the path of the executable return fi \cd $( dirname $( which "$1" ) ) } ## File processing functions function foldermenu_prompt { # Function to add in PS prompt variable, enabling foldermenu support [ -f .foldermenu ] && { foldermenu -lf 10 $@ || echo -n "*" } || { true } } function igrep () { # Interactive grep, read grep arguments [[ "$1" = "-h" ]] && [[ "$#" -eq 1 ]] && { echo Interactive grep, read grep arguments return } local args args="-i -e " while true do read -e -i "$args" args echo $@ grep --color=auto $args "$@" if [ "$?" -ne 0 ] then echo _no_matches_ | grep --color=auto ".*" fi done } function ised () { # Interactive sed [[ "$1" = "-h" ]] && [[ "$#" -eq 1 ]] && { echo Interactive sed, read sed arguments return } local args if [ ! -f "$1" ]; then echo must give atleast one file; return 1;fi; args="-e s/string//g" while true do read -e -i "$args" args echo input files: "$@" eval sed $args "$@" if [ "$?" -ne 0 ] then echo Error output | grep --color=auto ".*" fi done } function rmv () { # mv files/folders with rsync [[ "$1" = "-h" ]] && { echo 'mv files/folders with rsync (shows speed and progress)' echo 'Usage: rmv source [source2] [source3] target' return } local sources sources=() # remove / from ends, if target is an existing folder for (( i=1; i<=$(($#-1)); i++ )) do if [ -d "${@: -1}" ] then sources+=("${!i%/}") else sources+=("${!i}") fi done rsync -vaP --remove-source-files "${sources[@]}" "${@: -1}" # remove empty folders from sources (not last argument) for (( i=1; i<=$(($#-1)); i++ )) do if [ -d "${!i}" ] then $_QCD_FIND "${!i}" -depth -type d -exec rmdir \{\} \; fi done } function whenfilechanges() { # Run a command when file(s) time stamp changes echo "Version: 2015-10-13" [ -z "$2" ] && { echo 'Usage: whenfilechanges "file" "some" "command" Follows the modification time of the "file" and runs the command at every change. The "file" may also be a glob e.g. "*tex" ' return } local fname local forced local otime local ntime local i fname=($1) echo Waiting for ${#fname[@]} files to change: ${fname[@]} shift 1 echo "Command: \"$@\"" otime=() for ((i=0;i<${#fname[@]};i++)) do [ -e "${fname[$i]}" ] || { echo "File: ${fname[$i]} not found!" return } otime[$i]=$( stat -c %Z "${fname[$i]}" ) done while :; do ntime=() for ((i=0;i<${#fname[@]};i++)); do ntime[$i]=$( stat -c %Z "${fname[$i]}" ) [[ "${ntime[$i]}" -ne "${otime[$i]}" ]] && { echo "${fname[$i]} changed:" otime[$i]=$( stat -c %Z "${fname[$i]}" ) eval "$@" echo -n "Waiting for file changes... " date } done echo -ne Waiting.. $( date )\\r read -t 2 forced [ $? -eq 0 ] && eval "$@" done } # Shell enhancement function qbg { # Run a command quiet, and backgrounded [[ "$1" = "-h" ]] && { echo 'Run a command quiet, and backgrounded ( typically X11 executable )' return } "$@" &> /dev/null & } _qbg() { local cur cur=${COMP_WORDS[COMP_CWORD]} if [[ "$COMP_CWORD" == 1 ]]; then local executables executables=$({ compgen -c; compgen -abkA function; } | sort | uniq -u ) COMPREPLY=( $( compgen -W "$executables" -- "$cur" ) ) return 0 fi COMPREPLY=( $( compgen -fd -- "$cur" ) ) } complete -F _qbg qbg function set_term_title { # set term in byobu/screen xterm etc.. [ -z "$1" ] && { echo -ne '\033k'$HOSTNAME'\033\\\r' } || { echo -ne '\033k'$1'\033\\\r' } } function path_add_current { # Add current or given folder to PATH [[ "$1" = "-h" ]] && { echo 'Add the current folder in PATH, or pass a folder name to be added' return } local p=$(pwd) [[ -z "$1" ]] || { case $OSTYPE in darwin*) p=$( realpath "$1" ) ;; *) p=$( realpath "$1" ) ;; esac } [[ -z "$p" ]] && { echo Path "$1" not found >&2 return } export PATH="${p}:$PATH" path_remove_duplicates echo PATH=$PATH } function path_remove_duplicates { # Remove duplicates in PATH PATH=$( echo $PATH | awk -F: '{for (i=1;i<=NF;i++) { if ( !x[$i]++ ) printf("%s:",$i); }}' | sed 's,:\+$,,g' ) export PATH } function whichcat { which "$1" | xargs cat }