Ryan
Ryan

Reputation: 15270

Is it possible to add an alias in bash that allows tab completion?

I have lots of log files in a deeper directory like so:

/my/deep/path/to/log/files/
                           foo-2016-10-10.log
                           foo-2016-10-11.log
                           bar-2016-10-10.log
                           bar-2016-10-11.log

Many times a day I find myself tailing different logs like so:

tail -fn 100 /my/deep/path/to/log/files/foo-2016-10-10.log

Is it possible to alias everything but the final file? And allow tab completion to get me the rest of the way?

For example, I'd love to type tl (short for tail log) and the specific log file I'm looking for like so:

$ tl foo
      [HIT TAB KEY]
$ tl foo-2016-10-1
      [HIT 1 and TAB KEY]
$ tl foo-2016-10-11.log

Is this possible in bash? How can I accomplish this?

Upvotes: 2

Views: 468

Answers (3)

gniourf_gniourf
gniourf_gniourf

Reputation: 46853

Pretty much a clone of Charles Duffy's answer (with the only difference that he uses globs and I'm using compgen — which is probably the way completions are (badly) designed to be written):

_tl_COMPDIR=/my/deep/path/to/log/files/
_comp_tl() {
   local IFS=$'\n'
   COMPREPLY=( $(compgen -f -- "$_tl_COMPDIR$2") )
   COMPREPLY=( "${COMPREPLY[@]#"$_tl_COMPDIR"}" )
}

tl() (
   cd -- "$_tl_COMPDIR" && tail -fn 100 "$@"
)

complete -o nospace -o filenames -F _comp_tl tl

Upvotes: 3

Charles Duffy
Charles Duffy

Reputation: 295619

It's a bit ugly, but this does the job:

_tl_log_path=/my/deep/path/to/log/files

_tl_completions() {
  COMPREPLY=( "${_tl_log_path}/$2"* )
  COMPREPLY=( "${COMPREPLY[@]#${_tl_log_path}/}" )
  if [[ ${COMPREPLY[0]} = "$2*" ]]; then
    COMPREPLY=( )
  fi
}
complete -F _tl_completions -o filenames tl

As for your tl, I'd suggest implementing it as such:

tl() {
  # generate an array of arguments to pass to tail
  local -a args=( )

  # iterate over each argument given to the function
  for arg; do
    if [[ -e "$_tl_log_path/$arg" ]]; then
      # if a like-name file exists under _tl_log_path, add that filename as an argument
      args+=( "$_tl_log_path/$arg" )
    else
      # otherwise, add that argument exactly as it already exists
      args+=( "$arg" )
    fi
  done
  # ...and, finally, execute the tail command.
  tail -f "${args[@]}"
}

...though you could also go cheap and easy:

tl() (
  cd "$_tl_log_path" || exit
  exec tail -f "$@"
)

To explain:

  • complete -F funcname cmdname indicates that the shell function funcname should be called to determine how to complete the command cmdname. -o filenames further indicates that the results should be treated as filenames.
  • COMPREPLY is a shell array which the completion function is expected to fill out with possible completions.
  • COMPREPLY=( "${_tl_log_path}/$2"* ) expands the given content as a glob (because the * is, intentionally, outside the quotes).
  • COMPREPLY=( "${COMPREPLY[@]#${_tl_log_path}/" ) then strips the log directory as a prefix from each result in the array.
  • Checking whether [[ ${COMPREPLY[0]} = "$2*" ]] (with the asterisk inside the quotes, on this occasion), detects the case where no possible glob expansion results existed, and where (thus) the glob expanded to itself (assuming that the shell's configuration via shopt is at defaults).

Upvotes: 2

Anh Huynh
Anh Huynh

Reputation: 204

Yes you can simply switch to that directory before executing the command.

$ cd /my/deep/path/to/log/files/
$ tl <tab>

Upvotes: 0

Related Questions