Regardless whether you use dirname
or use parameter expansion with substring removal (e.g. parent="${1%/*}"
) to incrementally lop off the final pathspec, you need to test for several conditions to handle both absolute and relative pathnames. Specifically you need to determine if:
- the path name is absolute, e.g.
"/path/to/file"
- the path name is relative, e.g.
"path/to/file"
- the path name references the parent dir using
".."
- the path name references the current dir using
"."
To do that you can use compound statements e.g. [ ... ] && [ ... ] ...
(or the older, less favored [ ... -a .... -a .... ]
). You also need to consider whether to use bashisms (non-POSIX, bash only functions and test clauses, like [[ ... ]]
), or to make your script portable limiting your syntax to POSIX compatible elements. (parameter expansion is POSIX compliant)
Putting all those pieces together, you can do something similar to the following:
#!/bin/bash
parent="$1"
tmp="$(dirname "$parent")"
## if only filename given, parent is "./"
if [ "$tmp" = "." ]; then
parent="./"
else
## find parent for absolute and relative paths
while [ "$parent" != "$tmp" ] && [ "$tmp" != "/" ] &&
[ "$tmp" != "." ] && [ "$tmp" != ".." ]; do
parent="$tmp"
tmp="$(dirname "$parent")"
done
fi
printf "%s\n%s\n" "$1" "$parent"
Example Use/Output
$ bash parent.sh ../a/b/c.d
../a/b/c.d
../a
$ bash parent.sh ./a/b/c.d
./a/b/c.d
./a
$ bash parent.sh /a/b/c.d
/a/b/c.d
/a
$ bash parent.sh a/b/c.d
a/b/c.d
a
$ bash parent.sh c.d
c.d
./
note: you can use tmp="${parent%/*}"
in place of tmp="$(dirname "$parent")"
, it is up to you. (you just need to adjust the first test for filename only to [ "$parent" = "$tmp" ]
and replace "$tmp" != "/"
with "$tmp" != ""
)
You can be as creative as you like isolating the parent. There isn't one right way and all others are wrong. Just try and use good script tests and validations to cover as many corner cases as you are likely to encounter.
If you wanted to use parameter expansion and use the old conditional -a
operator, you could do:
#!/bin/bash
parent="$1"
tmp="${parent%/*}"
## if only filename given, parent is "./"
if [ "$parent" = "$tmp" ]; then
parent="./"
else
## find parent for absolute and relative paths
while [ "$parent" != "$tmp" -a "$tmp" != "" -a \
"$tmp" != "." -a "$tmp" != ".." ]; do
parent="$tmp"
tmp="${parent%/*}"
done
fi
printf "%s\n%s\n" "$1" "$parent"
Look things over and let me know if you have further questions.