bash tricks

The bash directory utilities CDPATH, pushd and popd are not really usefull, they are too circumstantial for effective usage.

We want:

To use it, put the code below in your $HOME/.bashrc or /etc/bash.bashrc and start a new bash to use it.

Bold blue text in the examples is user input.


cd without cd

Entering a directory name should be sufficent to change the directory, an extra cd command should not be necessary. With
shopt -s autocd

you can omit the cd command to change a directory.


cdh: cd with directory history

With cd - you can go back to your last directory, but there is no command to go to the last but one or even older directories.

With

declare -a DIRS

savedir() {
  local i
  for ((i=1;i<=9;i++)); do 
    test "$1" = "${DIRS[$i]}" && return
  done
  for ((i=9;i>1;i--)); do 
    DIRS[$i]="${DIRS[((i-1))]}"
  done
  DIRS[1]="$1"
}

showdirs() {
  local i=1
  while [ "${DIRS[$i]}" ]; do
    echo "$i: ${DIRS[$i]}"
    ((i++))
  done
}

gotodir() {
  local d
  showdirs
  printf "goto: "
  read -n 1 d
  echo
  cd "${DIRS[$d]}"
}

PROMPT_COMMAND=prompt_command
prompt_command() { savedir "$OLDPWD"; }

alias cdh=gotodir
you have a directory history, which you can access with the cdh command. Example:
framstag@fex:~: cd /sw/share/fstools-0.0/bin
framstag@fex:/sw/share/fstools-0.0/bin: cd /tmp
framstag@fex:/tmp: cd /etc/X11
framstag@fex:/etc/X11: cd
framstag@fex:~: cdh
1: /etc/X11
2: /tmp
3: /sw/share/fstools-0.0/bin
4: /home/framstag
goto: 3
framstag@fex:/sw/share/fstools-0.0/bin:


cdb: cd with persistant bookmarks

The directory history is volatile and gone when you terminate your bash.
If you want persistant directory bookmarks, then extract cdbm in your $PATH and run source $(cdbm -S)

Afterwards you can use cdb to save, change, remove or go to a directory with just one key.
Example:

root@fex:~# cdb
[+] add bookmark
[-] del bookmark
[>] move bookmark
[?] toggle help
[a] /etc/amavis/conf.d
[p] /etc/postfix
[u] /export/backup/U10
[v] /var/lib/amavis/virusmails
[w] /export/home/httpd/virtual/flupp.belwue.de
[v]
root@fex:/var/lib/amavis/virusmails# 

See also mklink: create symlink in $HOME/Links


cdw: cd to directory where program is

With
cdw() {
  if [ -z "$1" ]; then
    echo >&2 "usage: cdw program_or_file"
  elif [[ "$1" =~ / ]]; then
    cd $(dirname "$1")
  else
    ap=$(type -P "$1")
    if [ "$ap" ]; then
      cdw $(readlink -f "$ap")
    else
      echo >&2 "$1 not found"
    fi
  fi
}
you can call cdw PROGRAM to change to the directory where PROGRAM is, for example:
framstag@fex:~: cdw dsmc
framstag@fex:/opt/tivoli/tsm/bin: ll dsmc
-rwxr-xr-x root     root     202 2015-08-14 15:52:27 dsmc
framstag@fex:/opt/tivoli/tsm/bin: cdw fexsend
framstag@fex:/sw/share/fstools-0.0/bin: ll fexsend
-rwxr-xr-x framstag root     95,081 2016-08-29 09:06:03 fexsend

You can also call cdw with any path (e.g. by mouse copy&paste) and it will change to the pertaining directory, example:
cdw /boot/grub/grub.cfg


cdu: cd upwards several levels

With
cdu() {
  declare -a x
  declare -A D
  local n=$1
  local i=0
  local dir=$(realpath .)
  x=({0..9} {a..z})
  
  if [[ $1 =~ [a-z] ]]; then
    echo "cdu: cd upwards"
    echo "usage: cdu [NUMBER_OF_PARENT_DIRECTORIES]"
    return
  fi
  
  while [[ $dir =~ /.+/ ]]; do
    ((i++))
    dir=$(dirname "$dir")
    D[${x[i]}]="$dir"
  done
  
  if [ -z "$n" ]; then
    for (( n=1; n<=i; n++ )); do
      echo "[${x[n]}] ${D[${x[n]}]}"
    done
    printf "[ ]\r["
    read -n 1 n
    test -z "$n" && return
    echo
  fi

  if [ "${D[$n]}" ]; then
    cd "${D[$n]}"
  else
    echo >&2 "cdu: no such directory"
  fi
}
you can go up any number of directories, for example:
framstag@fex:/sw/share/jedlib-0.99-20_116/lib: cdu
[1] /nfs/rusnas/sw/share/jedlib-0.99-20_116
[2] /nfs/rusnas/sw/share
[3] /nfs/rusnas/sw
[4] /nfs/rusnas
[5] /nfs
[4]
framstag@fex:/nfs/rusnas:


cdd: cd downwards

With
cdd() {
  local i=0
  local n=1
  local find='find . -type d'
  local dir dot
  local m nn nf ii
  declare -a D
  
  [ "$1" = '.' -o "$1" = ':.' ] && dot=true
  [ "$1" = ':' -o "$1" = ':.' ] && find="$find -maxdepth 1"
  [ "$1" = '.' -o "$1" = ':' -o "$1" = ':.' ] && shift
  
  if [[ $1 =~ ^- ]]; then
    echo "cdd: cd downwards (subdir)"
    echo "usage: cdd [SUBSTRING]"
    return
  fi
  
  m=$1
  
  while read dir; do
    [ "$dir" = . ] && continue
    [ -z "$dot" ] && [[ "/$dir" =~ /\. ]] && continue
    for i in $CD_IGNORE; do
      [ "$dir" = "$i" ] && continue 2
    done
    D[n]=$dir
    ((n++))
  done < <(
    $find 2>/dev/null|
    sed 's/..//'|
    fgrep -i "$m"|
    while read d; do 
      test -x "$d" -a -r "$d" && echo "$d"
    done|
    LC_ALL=C sort -f
  )

  nn="${#D[@]}"
  nf="${#nn}"
  if [ $nn = 0 ]; then
    return
  elif [ $nn = 1 ]; then
    i=1
  else
    for (( i=1; i<=nn; i++ )); do
      printf "[%0${nf}d] %s\n" $i "${D[i]}"
    done
    while :; do
      i=
      printf "\r[%${nf}s]     \r[" ''
      while (( "${#i}" < $nf )); do
        read -n 1 ii
        test -z "$ii" && return
        [[ $ii =~ ^[0-9]$ ]] || continue 2
        i=$i$ii
      done
      i="$((10#$i))" # remove leading 0s
      test $i = 0 -o -n "${D[i]}" && break 
    done
    echo
  fi
  test -n "${D[i]}" && cd "${D[i]}"
}

you can go downwards to any subdirectory matching substring, for example:
root@fex:/sw/share# cdd bin
[1] s3cmd-2.0.2/bin
[2] s3cmd-2.0.2/bin/S3
[3] aggis-1.0/bin
[4] linuxtools-0.0/bin
[5] linuxtools-0.0/sbin
[6] fstools-0.0/bin
[7] fstools-0.0/sbin
[5]
root@fex:/sw/share/linuxtools-0.0/sbin# 


goto new directory

With
newdir() { mkdir -p "$1" && cd "$1"; }
you can call newdir schwupp/di/wupp which creates this directory and let you enter it, with just one command.


unique PATH

With
pathuniq() {
  if [ -n "$1" ]; then
    perl -le 'print join(":",(grep !$_{$_}++,split(":",shift)))' "$1"
  else
    PATH=$(pathuniq $PATH:)
  fi
}
you can call pathuniq which removes double elements from the PATH environment variable. Example:
framstag@fex:~: echo $PATH
/home/framstag/bin:/usr/local/bin:/bin:/sbin:/usr/bin:/bin:/usr/sbin:/bin:/home/framstag/bin
framstag@fex:~: pathuniq
framstag@fex:~: echo $PATH
/home/framstag/bin:/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin

You can also call pathuniq with:any:other:argument:any


original command line

export BASHCMDLINE=

bind -x '"\e$":BASHCMDLINE="$READLINE_LINE"'
bind '"\e;":accept-line'
bind '"\r":"\e$\e;"'
This sets the environment variable BASHCMDLINE which contains the original command line before shell expansion.
You can access BASHCMDLINE from any programm to query the original invoke command. In contrast, the classic ARGV variable is the invoke command after shell expansion!

Example:

framstag@fex:/tmp: cat showargv
echo "BASHCMDLINE = [ $BASHCMDLINE ]"
echo "ARGV = [ $* ]"

framstag@fex:/tmp: bash showargv $PWD   *
BASHCMDLINE = [ bash showargv $PWD   * ]
ARGV = [ /tmp framstag root showargv zz ]


search for command substring

With
search() {
  local status=1
  if [ -z "$1" ]; then
    echo "usage: search substring"
    return 2
  fi

  alias | grep "$1[^ ]*=" && status=0
  
  declare -F | awk '$3 !~ /^_/ {print $3"()"}' | fgrep "$1" && status=0

  for d in $(sed 's/:/ /g' <<<$PATH); do
    for f in $d/*"$1"*; do
      if [ -x "$f" -a -f "$f" ]; then
        echo "$f"
        status=0
      fi
    done
  done
  return $status
}
you can search for commands (alias, functions, programs) by substring. Example:
framstag@fex: search dir
alias copydir='cp -av --parents'
newdir()
savedir()
showdirs()
/bin/dir
/bin/mkdir
/bin/rmdir
/usr/bin/dircolors
/usr/bin/dirname


all-in-one

You can use all bash tricks together.

For the cd tools you have to extract cdbm in your $PATH (eg /usr/local/bin or $HOME/bin)

With source $(cdbm -S) you will get:

framstag@fex: cdbm -h
cdb [BOOKMARK]  : cd bookmarks
cdh             : cd history
cdl [LINK]      : cd link (see mklink)
cdu [NUMBER]    : cd upwards
cdd [SUBSTRING] : cd downwards (no .directories)
cdd. [SUBSTRING]: cd downwards (with .directories)
cds [SUBSTRING] : cd subdirectory (no .directories)
cds. [SUBSTRING]: cd subdirectory (with .directories)
cda             : cd absolute path
cdw /PATH/FILE  : cd /PATH
cdw PROGRAM     : cd where PROGRAM is in $PATH
cdx PROGRAM     : cd where PROGRAM is in $PATH and list it
cde PROGRAM     : cd where PROGRAM is in $PATH and edit it
..              : cd one directory up
...             : cd two directories up
.. NUMBER       : cd NUMBER directories up (".. 2" == "...")
-               : cd previous directory
and also mklink.


Author: Ulli Horlacher