41

I can't tell how many times I have wished for a command that would both create a directory and move to that directory. Basically, I would like the equivalent of the following:

mkdir -p /arbitrarily/long/path; cd /arbitrarily/long/path

but only having to type the /arbitrarily/long/path once, something like:

mk-cd /arbitrarily/long/path

I tried creating a script to do this, but it only changes the directory within the script. I'd like the directory in the shell to have changed as well.

#!/bin/bash
mkdir $1
cd $1
export PWD=$PWD

How might I be able to make this work?

  • 12
    With cd, you picked a special case right from the start. :D – Daniel B May 6 '16 at 13:11
  • 2
    Not exactly what I was looking for, but super cool cd-related info (return to the previous directory using cd -, use pushd and popd to maintain a "stack" of directories): superuser.com/questions/324512/… – Christopher Bottoms May 6 '16 at 13:18
  • 8
    You can type mkdir -p /very/long/path, then use cd, space, and then press Alt+. to repeat the last argument, i.e. the dir name. – choroba May 6 '16 at 13:23
  • 2
    Not an answer, just a comment similar to @choroba's: You can also use mkdir -p /very/long/path; cd !#:2. The string !#:2 will expand to argument nr. 2 (that is, the third argument, /very/long/path, as counting starts with zero). – Ingo Blechschmidt May 6 '16 at 22:56
  • 3
    @IngoBlechschmidt, even easier, since it's the last argument of the last command, you can just use !$. I use this particular trick all the time, though there is a lot more you can do with history expansion. – Wildcard May 7 '16 at 8:28
92

The simplest way is to use a shell function:

mkcd() {
    mkdir -p -- "$1" && cd -- "$1"
}

Place it in your .bashrc file to make it be available to you just like another shell command.

The reason why it doesn't work as an external script is cd changes the current directory of the running script but doesn't affect the calling one. This is by design! Each process has its own working directory which is inherited by its children, but the opposite is not possible.

Unless part of a pipeline, run in the background or explicitly in a subshell, a shell function doesn't run in a separate process but in the same one, just like if the command has been sourced. The current directory shell can then be changed by a function.

The && used here to separate both commands used means, if the first command succeeds (mkdir), run the second one (cd). Consequently, if mkdir fails to create the requested directory, there is no point trying to go into it. An error message is printed by mkdir and that's it.

The -p option used with mkdir is there to tell this utility to create any missing directory that is part of the full path of the directory name passed as argument. One side effect is that if you ask to create an already existing directory, the mkcd function won't fail and you'll end up in that directory. That might be considered an issue or a feature. In the former case, the function can be modified for example that way which simply warns the user:

mkcd() {
    if [ -d "$1" ]; then
        printf "mkcd: warning, \"%s\" already exists\n" "$1"
    else
        mkdir -p "$1" 
    fi && cd "$1"
}

Without the -p option, the behavior of the initial function would have been very different.

If the directory containing the directory to create doesn't already exists, mkdir fails an so does the function.

If the directory to be create already exists, mkdir fails too and cd isn't called.

Finally, note that setting/exporting PWD is pointless, as the shell already does it internally.

Edit: I added the -- option to both commands for the function to allow a directory name starting with a dash.

  • 5
    You should also add that this command will not become permanently available unless it is added to ~/.bashrc or one of the other initialisation scripts. – AFH May 6 '16 at 13:25
  • Is there no way in bash to do the equivalent of tcsh's alias mk-cd 'mkdir -p \!:1; cd \!:1' without a function? Or maybe I should start thinking of bash functions more like extended aliases? – Thomas Padron-McCarthy May 8 '16 at 10:07
  • @ThomasPadron-McCarthy This csh argument passing mechanism is not implemented with bourne style aliases. Functions are indeed the way to go, being much more flexible. – jlliagre May 8 '16 at 12:16
  • @ThomasPadron-McCarthy - You might like to look at this answer, which does provide a rather abstruse mechanism for accessing parameters passed to an alias (in the definition of bar). Another option would be to use history 1, as in alias mk-cd='args="$(history 1)" && args="${args#*mk-cd\ }" && mkdir -p "$args" && cd' - this works fine for the questioner's example, but is not a general solution, as any other commands on the same line (eg piping the output) will cause it to fail. – AFH May 8 '16 at 16:09
  • 3
    @ThomasPadron-McCarthy You should start thinking of tcsh's aliases more like restricted functions. – Gilles May 9 '16 at 11:21
32

I would like to add an alternative solution.

Your script will work if you invoke it with the . or source command:

. mk-cd /arbitrarily/long/path

If you do this the export in the script is unnecessary. You can bypass typing the . by using an alias:

alias mk-cd='. mk-cd'

The reason this works is that a script normally runs in a sub-shell so its environment is lost on completion, but the . (or source) command forces it to run in the current shell.

This technique can be useful for command sequences which are too complex for a function or which are under development and are frequently edited: once the alias has been entered into .bash_aliases, you can edit the script at will without reinitialising.

Note the following:-

If you write a script which must be called with the ./source command, there are two ways to make sure of this:-

  1. For some reason, a script invoked with ./source need not be executable, so simply remove this permission and the script either will not be found in "$PATH" or will give a permission error if invoked with an explicit path.

  2. Alternatively, the following trick can be used at the head of the script:

bind |& read && { echo 'Must be run from "." or "source" command'; exit 1; }

This works because in a sub-shell bind issues a warning, though no error status: hence read is used to give an error on no input.

  • Thanks for the info on .. I have done . .bashrc countless times, but didn't know what exactly the . does. Is it similar to export? – cst1992 May 8 '16 at 19:42
  • 2
    @cst1992 - No, the commands are completely different: export var copies the internal variable var to the environment, where it is inherited and imported as a variable in any sub-shell, but it has no effect on a parent shell. Normally a sub-shell is created to run a script, and any changes it makes (including exported variables) are lost when it completes. In order for a script to set variables in a shell it has to run in that shell, and this is what the . command does: run the script without creating a sub-shell. Curiously, scripts run by . need not have executable permission. – AFH May 8 '16 at 20:36
  • Thanks for the info. I'd insist that you include this info in your post. This is useful, and frankly, comments have no guarantee that they'll be there tomorrow. – cst1992 May 8 '16 at 20:45
  • @cst1992 - I addressed the original question, but I don't want to clutter my answer with ancillary issues. If you were looking for an explanation of the . and export commands, the title of this question would not lead you to expect an answer here, so I'll let my answer stand as it is, but thanks for your comment. – AFH May 8 '16 at 21:08
  • +1 in general, but especially for the alias. I have a few scripts which need to be sourced and I always forget to run them that way. The alias takes care of that nicely. – Joe May 9 '16 at 22:47
13

Not a single command, but you don't have to retype all the path just use !$ (which is the last argument from the previous command in the shell history).

Example:

mkdir -p /arbitrarily/long/path
cd !$
1

By creating aliases. Very popular way to create your own commands.

You can create an alias on the fly:

alias myalias='mkdir -p /arbitrarily/long/path; cd /arbitrarily/long/path'

That's it. If you want to keep that alias permanently (not just the current session), then put that command in a dotfile (typically ~/.bash_aliases or ~/.bash_profile).

  • 4
    As the long directory name is hardcoded, this alias wouldn't be very useful unless this directory is regularily destroyed by some other process. – jlliagre May 9 '16 at 10:01
1

In addition to what has already been suggested, I'd like to recommend thefuck. It's a Python framework for streamlining your shell process by fixing mistakes. Inspired by this tweet, all you have to do is type your configured alias (by default fuck) and it will attempt to correct your previous command. One of its corrections behaves as follows:

/etc $ cd somedir
bash: cd: No such file or directory
/etc $ fuck
mkdir somedir && cd somedir
/etc/somedir $ 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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