12
\$\begingroup\$

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
}
\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

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.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ Commented Jan 30, 2013 at 23:46
2
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Apr 3, 2013 at 12:49
0
\$\begingroup\$

I try this command mkcd --help results in a new folder named --help and it cd to this folder, but if I try mkdir --help gives below output that I believe the correct behavior:

Usage: mkdir [OPTION]... DIRECTORY...
Create the DIRECTORY(ies), if they do not already exist.

Mandatory arguments to long options are mandatory for short options too.
  -m, --mode=MODE   set file mode (as in chmod), not a=rwx - umask
  -p, --parents     no error if existing, make parent directories as needed
  -v, --verbose     print a message for each created directory
  -Z                   set SELinux security context of each created directory
                         to the default type
      --context[=CTX]  like -Z, or if CTX is specified then set the SELinux
                         or SMACK security context to CTX
      --help     display this help and exit
      --version  output version information and exit

GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Full documentation at: <https://www.gnu.org/software/coreutils/mkdir>
or available locally via: info '(coreutils) mkdir invocation'

I can't remove --help folder after it created, zsh-5.7.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.