#! /bin/bash shopt -s extglob # POSIX dirname(1) without fork/exec. # # "Step N" below refers to the steps in the POSIX specification for dirname: # http://www.opengroup.org/onlinepubs/009695399/utilities/dirname.html dirname () { # // is not treated specially by this implementation, so steps # 1 and 6 are ignored. # Ignore a first argument of '--' [[ $1 = -- ]] && shift if [[ $# -lt 1 ]]; then printf "%s: missing operand\n" "dirname" >&2 return 1 elif [[ $# -gt 1 ]]; then printf "%s: extra operand '%s'\n" "dirname" "$2" >&2 fi # Handle empty string immediately. The next step converts all-slash # strings to the empty string, but we need to be able to distinguish # between the two. if [[ ! "$1" ]]; then echo . return fi # Remove trailing slashes (step 3). If the string was all slashes, # this gives us "" and our ${fn:-/} yields /. Thus steps 1 and 2 are # handled as well. local fn=${1%%+(/)} if [[ "$fn" = +([!/]) ]]; then # No slashes, and the string hadn't been all-slashes. It's # in the current directory (step 4). fn=. else # Remove trailing non-slashes and the slashes that precede them # (steps 5, 7). fn=${fn%%+(/)*([!/])} fi # convert empty string to "/" (step 8). printf "%s\n" "${fn:-/}" } dirname "$@"