47

mkdir -p will create a directory; it will also make parent directories as needed.

Does a similar command exist for files, that will create a file and parent directories as needed?

3
  • Not that I am aware of.. but you could just do mkdir -p /path/to/make && touch /path/to/file... Which would make an empty file in that new directory structure you created all as needed.
    – Kansha
    Commented Jan 30, 2013 at 9:10
  • 2
    @Kansha combine that with dirname and basename and we'll only need the single argument; profit! :)
    – akaIDIOT
    Commented Jan 30, 2013 at 9:12
  • Aye, good call.
    – Kansha
    Commented Jan 30, 2013 at 9:14

9 Answers 9

49

The install utility will do this, if given the source file /dev/null. The -D argument says to create all the parent directories:

anthony@Zia:~$ install -D /dev/null /tmp/a/b/c
anthony@Zia:~$ ls -l /tmp/a/b/c 
-rwxr-xr-x 1 anthony anthony 0 Jan 30 10:31 /tmp/a/b/c

Not sure if that's a bug or not—its behavior with device files isn't mentioned in the manpage. You could also just give it a blank file (newly created with mktemp, for example) as the source.

1
  • 1
    This creates executables by default.
    – Pound Hash
    Commented Nov 25, 2022 at 21:29
7

No, it does not as far as I know. But you can always use mkdir -p and touch after each other:

f="/a/b/c.txt"
mkdir -p -- "${f%/*}" && touch -- "$f"
6
mkdir -p parent/child && touch "$_/file.txt"

$_ is a shell parameter, it expands to last argument of previous command. Example mkdir test && cd "$_" will create and cd into the directory test.

0
5

I frequently ran into this kind of situation, so I simply wrote a function in my .bashrc file. It looks like this

function create() {
    arg=$1
    num_of_dirs=$(grep -o "/" <<< $arg | wc -l)
    make_dirs=$(echo $arg | cut -d / -f1-$num_of_dirs)
    mkdir -p $make_dirs && touch $arg
}

So, when I want to create a file inside a path of non-existent directories, I will say

create what/is/it  # will create dirs 'what' and 'is', with file 'it' inside 'is'
1
  • 1
    My man! - thank you, I called it touchp, like mkdir -p
    – ecoologic
    Commented Jun 7, 2019 at 4:53
5

Add the following to your ~/.bashrc:

function mktouch() {
        mkdir -p -- "$(dirname -- "$1")" && touch -- "$1"
} 

Then reload bash then use it like mktouch your/path/file.txt.

1

It's possible to "fake it".

First, some required theory:

Rob Griffiths posted an article in 2007 entitled Easily Create Lots of New Folders on Macworld.com wherein he discussed using the xargs command to read in a list of files to create directories using mkdir.

xargs is capable of referencing a placeholder ({}) with the -I flag, which contains the value for each argument passed to xargs. Here's the difference between with that flag, and without:

$ foo.txt bar.txt | xargs echo
$ => foo.txt bar.txt
$ foo.txt bar.txt | xargs -I {} echo {}
$ => foo.txt
$ => bar.txt

xargs is also capable of running arbitrary shell commands with the sh -c flag:

foo.txt bar.txt | xargs sh -c 'echo arbitrary command!'

Combining the Concepts:

We can combine these concepts with mkdir -p instead of mkdir and the concept in @ldx's answer to produce this:

$ cat files.txt | xargs -I {} sh -c 'f="{}" && mkdir -p -- "${f%/*}" && touch -- "$f"'

This command basically maps each filename in a line-separated list of files, chops off the file part, creates the directories with mkdir -p and then touches the filename in it's respective directory.

Here's a breakdown of what happens in the above command:

Say for instance my files.txt looks like this:

deeply/nested/foo/bar.txt
deeply/nested/baz/fiz.txt
  • cat files.txt produces deeply/nested/foo/bar.js deeply/nested/baz/fiz.txt
  • deeply/nested/foo/bar.js deeply/nested/baz/fiz.txt is piped to xargs
  • because we used -I {}, xargs will translate each argument to it's own command, so we now have:
    • deeply/nested/foo/bar.txt
    • deeply/nested/baz/fiz.txt
  • we then run a shell command that uses the && combinator to group 3 commands that run sequentially - the first command stores the file in an environment variable (that gets re-used on the next file pass) using the placeholder we registered before, so we now have:
    • f=deeply/nested/foo/bar.txt
    • f=deeply/nested/baz/fiz.txt
  • we now have a variable we can pass to mkdir -p, but we need to cut out the filename. Simple enough using '${f%/*}':
    • mkdir -p deeply/nested/foo/
    • mkdir -p deeply/nested/baz/
  • and then we just re-reference the f variable in its entirety when we touch:
    • touch deeply/nested/foo/bar.txt
    • touch deeply/nested/baz/fiz.txt
2
  • 2
    You have all this explaination for what is essentially cat files.txt | xargs -I {} sh -c 'f="{}" && mkdir -p -- "${f%/*}" && touch -- "$f"', which has UUOC, then you subshell into xargs which subshells back into the shell, when a while read loop makes more sense
    – Zombo
    Commented Mar 10, 2016 at 20:53
  • 2
    I'll admit I'm not the most clued up on *nix commands - I don't even know what UUOC is - but I fail to see how this is an awful answer. It is researched, tested and is a working solution to your original question. It's constructive (unlike your rather rude comment). Please, if you feel that I've done something irresponsible here that I should not have, then elaborate further - explain why. I don't care much for opinionated debate that has no substance.
    – razorbeard
    Commented Mar 10, 2016 at 21:03
0

I was going to suggest as it keeps it on one line, though setting the variable separately allows you to change it and rerun the command from the history pretty easily.

B="./make/this/path" && mkdir -p -- "$B" && touch -- "$B/file.txt"
0

2022 Answer

$  mkdir -p foo/bar/baz.c  &&  rmdir $_  &&  touch $_

This solution:

  • Is based on tools that you likely already know.
  • Requires that you type out the full path only once.
  • Does not require creating an intermediate variable.
  • Is easy enough to type that you can use it on foreign servers without declaring a function.

mkdir creates the full directory path, rmdir removes the last portion, and touch recreates the last portion as a file. The $_ pseudo-argument just inserts the last argument of the previous command.

As a bonus, this method completes Alt-. with $_, which is the newly-completed file name. Thus, a simple vim Alt-. will open the new file.

Note that filenames with spaces or other non-A-Za-zא-ת0-9._- characters should quote the argument shortcuts:

$  mkdir -p foo/bar/baz.c  &&  rmdir "$_"  &&  touch "$_"

I personally don't take this much care, but you should be aware of the limitation. Thank you to StéphaneChazelas for mentioning it in the comments.

5
  • Since you're using zsh syntax, you might as well do (){mkdir -p $1:h && touch $1} path/to/file Commented Jul 11, 2022 at 11:28
  • @StéphaneChazelas: Thank you, but as I mentioned I like a format that works on boxes that I SSH into. Most *nixy things support Bash syntax but not zsh out of the box. The solution I posted works fine in Bash, it is not a zsh-specific syntax.
    – dotancohen
    Commented Jul 11, 2022 at 12:15
  • @StéphaneChazelas: I just tested on a few boxen, the syntax in this answer is valid down to at least Bash 4.4 which is the lowest version that I can SSH into for right now.
    – dotancohen
    Commented Jul 11, 2022 at 12:17
  • 1
    In bash, you need quotes around $_ to prevent split+glob. You can omit them if you know the values of $_ doesn't contain wildcard characters or characters of $IFS (which involves knowing the current value of $IFS). Which is why I said it was zsh syntax as zsh doesn't do split+glob upon parameter expansion. Commented Jul 11, 2022 at 12:19
  • @StéphaneChazelas Thank you, of course you are correct. This answer does address the happy case where it's being used by people who typically SSH into nix boxes and create file and are concerned with saving a few keystrokes: sysadmins, who are unlikely to create a file with a space or other non-alphanumperiodunderscoredash character in the filename. I'll add this info to the answer, thank you.
    – dotancohen
    Commented Jul 11, 2022 at 12:25
-1
dir=$(dirname "$f")
test -d $dir || mkdir -p "$dir"
2
  • 3
    The test isn't needed; mkdir -p doesn't do anything if the dir already exists. Doesn't even return an error.
    – derobert
    Commented Jan 30, 2013 at 15:36
  • 1
    And of course, this only creates the directory.
    – user
    Commented Jan 30, 2013 at 15:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .