46

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?

frederj
  • 103
  • 3
Zombo
  • 1
  • 5
  • 43
  • 62
  • 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. –  Jan 30 '13 at 09:10
  • 2
    @Kansha combine that with `dirname` and `basename` and we'll only need the single argument; profit! :) –  Jan 30 '13 at 09:12
  • Aye, good call. –  Jan 30 '13 at 09:14

9 Answers9

48

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.

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
derobert
  • 107,579
  • 20
  • 231
  • 279
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"
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
ldx
  • 207
  • 1
  • 5
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'
ViKiG
  • 167
  • 1
  • 3
5
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.

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
francis
  • 61
  • 1
  • 5
4

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.

Grisha Levit
  • 304
  • 1
  • 7
zoltankundi
  • 141
  • 4
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
razorbeard
  • 119
  • 3
  • 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 Mar 10 '16 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 Mar 10 '16 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"
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
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.

dotancohen
  • 15,494
  • 26
  • 80
  • 116
  • Since you're using zsh syntax, you might as well do `(){mkdir -p $1:h && touch $1} path/to/file` – Stéphane Chazelas Jul 11 '22 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 Jul 11 '22 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 Jul 11 '22 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. – Stéphane Chazelas Jul 11 '22 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 Jul 11 '22 at 12:25
-1
dir=$(dirname "$f")
test -d $dir || mkdir -p "$dir"
Yordan Georgiev
  • 269
  • 2
  • 4