Using extdebug to display where a function was defined I wrote a tool similar to which that would display the file and line number where a function was defined. Is there a way I can do the same for aliases? Also, any comments or critiques on the code?

function where() {
  cmd="$1"
  info=$(command -V "$cmd" | head -n 1)
  #rc="$PIPESTATUS[0]"

  case $info in
    *function*)
      shopt | grep -q extdebug
      extdebug=$?
      ! ((extdebug)) &amp;&amp; shopt -s extdebug
      declare -F "$cmd"
      ! ((extdebug)) &amp;&amp; shopt -u extdebug
      ;;
    *) echo "$info" ;;
  esac
}


Output looks like:

$ where ls ; where l ; where bad ; where where
ls is /bin/ls
l is aliased to `ls -Al --color=auto'
bash: command: bad: not found

where 6 /home/harleypig/projects/dotfiles/.bash_sources.d/tools


I realized, even though it was working, I was capturing the output of the shopt|grep command, not the exit status.
function where() is not the right syntax and only works in some versions of bash and zsh (and a few others). It is better to write either function where or where().

shopt | grep -q extdebug is expected to always return 0 for the exit code, since extdebug is always in the output of shopt whether it's turned on or off, which is not really your intention when you test for its status. Instead, you should write shopt -q extdebug.

Looking at ! ((extdebug)) &amp;&amp; shopt -s extdebug, I don't know if ! ((extdebug)) evaluates to true or false. It turns out it is true when extdebug equals 0 and false otherwise. This runs in counter to how the exit status of a shell command works: 0 for success and non-zero for failure. Both shopt -q extdebug and shopt | grep -q extdebug return 0 (success) if extdebug is, respectively, set and present in the output of shopt, and 1 otherwise. So you'd be setting extdebug when it is already set, and unsetting it when it is already unset. It is better to be explicit and write ! shopt -q extdebug &amp;&amp; shopt -s extdebug instead.

It's not clear to me why info=$(command -V "$cmd") does not suffice. You should document cases where this messes up and piping to head -n 1 is needed. Though probably a tiny bit less portable, info=$(type "$cmd") works fine, too, and is shorter to type (no pun intended) and conveys the intent better.

You don't have to limit to processing only the first argument passed to this function, just as command does not process only its first argument. The change required is trivial. I include the whole modified function below, with improved formatting:

function where
{
    while (( $# &gt; 0 ))
    do
        cmd=$1
        info=$(type "$cmd")
        case $info in
            *function*)
                #! shopt -q extdebug &amp;&amp; shopt -s extdebug
                shopt -s extdebug
                declare -F "$cmd"
                shopt -u extdebug
                #shopt -q extdebug &amp;&amp; shopt -u extdebug
                ;;
            *)
                echo "$info"
                ;;
        esac
        shift
    done
}


The shell option check is superfluous, so I've commented that out. Now the output would look like:

$ where ls l bad where
ls is /bin/ls
l is aliased to `ls -Al --color=auto'
bash: type: bad: not found

where 6 /home/harleypig/projects/dotfiles/.bash_sources.d/tools


The same could probably be done for alias, but this is a much more involving process. You probably need to define a function named alias that calls the builtin alias in a dotfile that is sourced first, utilize caller, do some arithmetic to get the right line number and save the result in an associative array. I'll post an update when I could get it to work.

Addendum

I have a proof of concept to do the same for alias that roughly works though not always 100% accurate:

declare -A alias_info

function alias
{   
    builtin alias "$@"
    while (( $# &gt; 0 ))
    do  
        case $1 in
            *=*)
                IFS='=' read alias_name alias_command &lt;&lt;&lt; "$1" 
                read line_no_in_sub caller_name source_file &lt;&lt;&lt; "$(caller 0)"
                if [[ -n "${source_file}" ]] # alias definition in sourced file
                then
                    # line_no_in_sub is the line number relative to the function/script block, 
                    # so we have to add to it the line number of the function.
                    shopt -s extdebug
                    func_line_no=$(awk '{print $2}' &lt;&lt;&lt; "$(declare -F "${func_name}")")
                    shopt -u extdebug
                    alias_info[$alias_name]="$(( line_no_in_sub + func_line_no )) ${source_file} ${alias_name} is aliased to \`${alias_command}'"
                else # alias definition from terminal
                    alias_info[$alias_name]="$(caller) $0 ${alias_name} is aliased to \`${alias_command}'"
                fi
                ;;
            *)  
                ;;
        esac
        shift
    done
}