# Make a directory and change to it

A basic shell function to create a directory and change to it goes like this:

mkcd () { mkdir "$1" && cd "$1"; }


This works well in many cases but breaks in unusual cases (e.g. if the argument begins with -).

I'm writing a more sophisticated version. This version calls mkdir -p to create parent directories if needed and just change to the directory if it already exists. It has these design goals:

• Work in any POSIX compliant shell.
• Cope with any file name.
• If the shell has logical directory tracking, where foo/.. is the current directory even if foo is a symbolic link to a directory, then the function must follow that logical tracking: it must act as if the cd builtin was called and magically created the target directory.
• If a directory is created, it is guaranteed that the function changes into it, as long as there is no race condition (another process moving a parent directory, changing relevant permissions, …).

Here is my best current effort. Does it meet the goals above? Are there situations where the behavior is surprising?

mkcd () {
case "$1" in */..|*/../) cd -- "$1";; # that doesn't make any sense unless the directory already exists
/*/../*) (cd "${1%/../*}/.." && mkdir -p "./${1##*/../}") && cd -- "$1";; /*) mkdir -p "$1" && cd "$1";; */../*) (cd "./${1%/../*}/.." && mkdir -p "./${1##*/../}") && cd "./$1";;
../*) (cd .. && mkdir -p "${1#.}") && cd "$1";;
*) mkdir -p "./$1" && cd "./$1";;
esac
}

-

Some error messages may be confusing like:

$mkcd /foo/../bar mkcd:cd:3: no such file or directory: /foo/..$ mkcd /bin/../bar
mkdir: cannot create directory ./bar': Permission denied


Probably not much you can do about that.

-
I don't find the message from cd particularly bad: at least it's true. On the other hand the message from mkdir is wrong in that case. mkdir … 2>&1 | sed …? Or rather error=$(mkdir 2>&1 …) || … to keep the return status. – Gilles Jan 30 '13 at 23:46 You should be able to get away with handling only two error cases (empty or no parameter) and three path possibilities: Absolute path, relative path which starts with ./ and other ("dangerous") paths: mkcd() { if [ -z "${1:-}" ]
then
printf '%s\n' 'Usage: mkcd PATH'
return 2
fi

case "$1" in /*|./*) break;; *) set -- "./$1";;
esac

mkdir -p "$1" && cd "$1"
}


You won't need the -- separator. It might be surprising that mkcd foo/../bar would create both directories if they don't exist, but that's more to do with mkdir than the script.

Of course, this doesn't recursively simplify the path, which you'd need to do if you want to create the simplest absolute path defined (as printed by readlink -f, which is not in POSIX). But this would be surprising behavior, since cd foo/../.. fails even when ../ exists.

-
If it was only a matter of the initial -, then mkdir -p -- "$1" && cd -- "$1" would almost suffice (except for the oddball case of a directory named -). The real complexity is in simulating the shell's logical directory tracking, when the argument contains foo/.. where foo` is a symlink. –  Gilles Apr 3 '13 at 12:49