I usually just use which when I want to know about where a command comes from. Sometimes, I expect the command to be a symlink, or I want to know the filesize or last modified date, so I then routinely ls -l the output of which to check.

Why not make that automatic? Let's automatically run ls -l on all files in which output. This is possible with the Function Command Extension Trick.

Unfortunately, zsh and bash have some of those occasional implementation differences requiring specific versions of the which function:

  • zsh has a builtin which; bash does not
  • both zsh and bash have builtin type
  • zsh type -f displays function definitions; bash type -f suppresses function definitions
  • busybox / dash does not have builtin

It is cumbersome and error-prone to add code to handle all these differences in the same function, so I have 2 different versions:

Zsh version of augmented which

unalias which &>/dev/null  # Prevent system-wide which alias from breaking the which function
# When which output is a file, ls -l the file (zsh-specific version)
which() {
    { [[ -t 0 ]] && [[ -t 1 ]]; } || { builtin which "$@"; return; }
    local which_out which_exit
    which_out="$(builtin which -x4 "$@")"
    which_exit="$?"
    [[ -z "$which_out" ]] && return "$which_exit"
    printf '%s\n' "$which_out" | while IFS=$'\n' read -r line; do
        if [[ "$line" = "/"* ]] && [[ -x "$line" ]]; then
            # eval to use ls alias with color flag. Aliases only work if set before function definition
            eval "ls -la $(printf %q "$line")"
        else
            printf '%s\n' "$line"
        fi
    done
    return "$which_exit"
}

Bash version of augmented which

unalias which &>/dev/null  # Prevent system-wide which alias from breaking the which function
# When which output is a file, ls -l the file (bash-specific version)
which() {
    { [[ -t 0 ]] && [[ -t 1 ]]; } || { command which "$@"; return; }
    local which_out which_exit
    which_out="$(builtin type "$@")"
    which_exit="$?"
    [[ -z "$which_out" ]] && return "$which_exit"
    printf '%s\n' "$which_out" | while IFS=$'\n' read -r line; do
        if [[ "$line" = *" is /"* ]] && [[ -x "${line/#* is /}" ]]; then
            printf '%s' "${line%% is *} is "
            # eval to use ls alias with color flag. Aliases only work if set before function definition
            eval "ls -la $(printf %q "${line/#* is /}")"
        else
            printf '%s\n' "$line"
        fi
    done
    return "$which_exit"
}