I have written bash scripts that accept a directory name as an argument. A single dot ('.') is a valid directory name, but I sometimes need to know where '.' is. The readlink
and realpath
commands provide a resolved path, which does not help because I need to allow for symbolic links.
For example, the resolved path to the given directory might be something like /mnt/vol_01/and/then/some
, whereas the script is called with '.' where '.' is /app/then/some
(a sym link which would resolve to the first path I gave).
What I have done to solve my problem is use cd
and pwd
in combination to provide the full path I want, and it seems to have worked OK so far.
A simplified example of a script:
DEST_DIR=$1
# Convert the given destination directory to a full path, ALLOWING
# for symbolic links. This is necessary in cases where '.' is
# given as the destination directory.
DEST_DIR=$(cd $DEST_DIR && pwd -L)
# Do stuff in $DEST_DIR
My question is: is my use of cd
and pwd
the best way to get what I want? Or is there a better way?
If all you want to do is to make an absolute path that has minimal changes from a relative path then a simple, safe, and fast way to to it is:
[[ $dest_dir == /* ]] || dest_dir=$PWD/$dest_dir
(See Correct Bash and shell script variable capitalization for an explanation of why dest_dir
is preferable to DEST_DIR
.)
The code above will work even if the directory doesn't exist (yet) or if it's not possible to cd
to it (e.g. because its permissions don't allow it). It may produce paths with redundant '.' components, '..' components, and redundant slashes (`/a//b', '//a/b/', ...).
If you want a minimally cleaned path (leaving symlinks unresolved), then a modified version of your original code may be a reasonable option:
dest_dir=$(cd -- "$dest_dir"/ && pwd)
--
is necessary to handle directory names that begin with '-'."$dest_dir"
are necessary to handle names that contain whitespace (actually $IFS
characters) or glob characters."$dest_dir"/
is necessary to handle a directory whose relative name is simply -
.pwd
is sufficient because it behaves as if -L
was specified by default.Note that the code will set dest_dir
to the empty string if the cd
fails. You probably want to check for that before doing anything else with the variable.
Note also that $(cd ...)
will create a subshell with Bash. That's good in one way because there's no need to cd
back to the starting directory afterwards (which may not be possible), but it could cause a performance problem if you do it a lot (e.g. in a loop).
Finally, note that the code won't work if the directory name contains one or more trailing newlines (e.g. as created by mkdir $'dir\n'
). It's possible to fix the problem (in case you really care about it), but it's messy. See How to avoid bash command substitution to remove the newline character? and shell: keep trailing newlines ('\n') in command substitution. One possible way to do it is:
dest_dir=$(cd -- "$dest_dir"/ && printf '%s.' "$PWD") # Add a trailing '.'
dest_dir=${dest_dir%.} # Remove the trailing '.'