2.5.4. Working with directories#
Working with directories is quite similar as to working with files. This section will discus the basic commands.
2.5.4.1. Making directories#
Like files, you will also want to create and remove directories.
Creating directories is done with the mkdir (for make directory) command.
For instance, to create a directory somedirectory/ in the current working directory, use
$ mkdir somedirectory
It is also allowed to write the new directory name with a trailing slash /.
This is not really necessary, but it can make it more clear for people inspecting the commands that this is intended to create a directory,
$ mkdir somedirectory/
We can also create a directory by specifying an absolute path, so that the directory is not created relative to the current working directory
$ mkdir ~/anotherdir/
Of course, the name of the directory you want to create (here: anotherdir) should not exist yet,
but the parent directory (here your home, ~) does need to exist.
This also applies to relative paths, for example the following wonât work
$ mkdir anotherdir/a/b/c/d/e/f/
since this instructs mkdir to create a directory f/ inside a non-existing parent directory anotherdir/a/b/c/d/e/.
Exercise 2.115
While, mkdir by default only makes a directory if its parent directory already exist,
it does have a handy -p switch which will make it create all non-existing parent directories too, if it can.
This is useful to quickly create a simple nested directory structure.
Create the following nested directories anotherdir/a/b/c/d/e/f/ using a single mkdir command.
If all went well, find anotherdir/ should list all subdirectories.
{
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.mkdir",
"match": { "absPaths": ["/home/student/anotherdir/a/b/c/d/e/f"], "parents": true },
"desc": "should use mkdir -p to create the nested directories in a single command" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a/b" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a/b/c" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a/b/c/d" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a/b/c/d/e" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a/b/c/d/e/f" }
]
}
Exercise 2.116
Suppose anotherdir/a/b/ already exists, but anotherdir/a/b/c/d/ does not yet. What happens if you now run mkdir -p anotherdir/a/b/c/d?
It fails, because
anotherdir/a/b/already exists.It succeeds, creating only the missing
c/andd/directories â the existinga/andb/are left untouched.It fails and empties out
anotherdir/a/b/first, then recreates everything from scratch.It succeeds, but only because
-pimplies-f.
2.5.4.2. Renaming and moving a directory#
Renaming a directory is done with mv, just as you do with files.
We can rename the directory using mv, giving the old and then the intended new name,
$ mv anotherdir testdir
Note that the content of the directory is unchanged, which you can confirm with find testdir/
If the second argument is an already existing directory, mv will not rename the directory in the first argument,
but move it into that second directory.
So, assuming you created somedirectory/ before, this moves testdir into somedirectory/,
$ mv testdir somedirectory/
Finally, it is even possible to move and rename in one go,
$ mv somedirectory/testdir mytestfiles/
Exercise 2.117
The directories notes/ and projects/ already exist in your home directory. Move notes/ into projects/ using a single mv command â since projects/ already exists, this should move notes/ into it, not rename it to projects.
{
"filesystem": {
"/home/student/notes/todo.txt": "buy milk\n",
"/home/student/projects/": null
},
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.mv",
"match": { "pairs": { "absSrc": "/home/student/notes", "absDest": "/home/student/projects/notes" } },
"desc": "should move notes/ into projects/" },
{ "type": "dirExists", "path": "/home/student/projects/notes" },
{ "type": "fileExists", "path": "/home/student/projects/notes/todo.txt" },
{ "type": "dirExists", "path": "/home/student/notes", "negate": true },
{ "type": "tabCompletionHint" }
]
}
Exercise 2.118
The directory workspace/drafts/ already exists. In a single mv command, move drafts/ out of workspace/ and rename it to final/ at the same time â so you end up with ~/final/, not ~/workspace/final/.
{
"filesystem": {
"/home/student/workspace/drafts/report.txt": "draft content\n"
},
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.mv",
"match": { "pairs": { "absSrc": "/home/student/workspace/drafts", "absDest": "/home/student/final" } },
"desc": "should move and rename drafts/ to ~/final/ in one command" },
{ "type": "dirExists", "path": "/home/student/final" },
{ "type": "fileExists", "path": "/home/student/final/report.txt" },
{ "type": "dirExists", "path": "/home/student/workspace/drafts", "negate": true },
{ "type": "tabCompletionHint" }
]
}
Exercise 2.119
Here is a direcotry structure, similar to the one you created in Exercise 2.115 (use find to see all nested paths).
Reorganise the nested directories:
Move the most nested directory f/ directly into the anotherdir/ directory,
such that the e/ directory is now empty.
Then, move the anotherdir/a/ directory into anotherdir/f/.
You should end up with the following nested directory structure: anotherdir/f/a/b/c/d/e/.
Confirm this with the find command.
Hint: when typing the long nested directory names, use repeated Tab-completion as much as possible!
{
"filesystem": {
"/home/student/anotherdir/a/b/c/d/e/f/": null
},
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.mv",
"match": { "pairs": { "absSrc": "/home/student/anotherdir/a/b/c/d/e/f", "absDest": "/home/student/anotherdir/f" } },
"desc": "move f/ directly into anotherdir/, so e/ has no more subdirectories" },
{ "type": "commandEvent", "eventType": "coreutil.mv",
"match": { "pairs": { "absSrc": "/home/student/anotherdir/a", "absDest": "/home/student/anotherdir/f/a" } },
"desc": "move anotherdir/a/ into anotherdir/f/" },
{ "type": "dirExists", "path": "/home/student/anotherdir/f/a/b/c/d/e" },
{ "type": "dirExists", "path": "/home/student/anotherdir/a", "negate": true },
{ "type": "dirExists", "path": "/home/student/anotherdir/e", "negate": true },
{ "type": "tabCompletionHint" }
]
}
Exercise 2.120
In the terminal below, create a directory called archive, then move notes.txt into it.
{
"filesystem": { "/home/student/notes.txt": "some notes\n" },
"checks": [
{ "type": "dirExists", "path": "/home/student/archive" },
{ "type": "fileExists", "path": "/home/student/archive/notes.txt" },
{ "type": "commandEvent", "eventType": "coreutil.mv",
"match": { "pairs": { "absSrc": "/home/student/notes.txt", "absDest": "/home/student/archive/notes.txt" } },
"desc": "should move notes.txt into archive/" },
{ "type": "tabCompletionHint" }
]
}
2.5.4.3. Removing directories#
The safest way to remove a directory is done with rmdir, for example:
$ rmdir somedirectory
which will remove the subdirectory somedirectory/ in the current working directory,
but only if the directory is completely empty.
Of course, you can also use an absolute path to indicate the directory to remove.
For instance, if your home directory contains an empty subdirectory tmp then you can delete
irrespective of your current working directory by specifying
$ rmdir ~/tmp
If the directory is not empty, the directory will not be deleted and an error will be displayed in the terminal.
Assuming mytestfiles/ still has a bunch of subdirectories, the following will not work:
$ rmdir mytestfiles/
This is a useful safety feature of rmdir, because perhaps there are still hidden files
that you forget to backup and remove before deleting the directory.
Ideally, you run rmdir once you believe it is completely empty, which helps prevent unintended deletions.
Exercise 2.121
In the terminal below, remove directory oldstuff using rmdir.
You will need to first make sure it is empty, or manually delete all the files in it.
{
"filesystem": {
"/home/student/oldstuff": null,
"/home/student/oldstuff/foo.txt": "bar",
"/home/student/oldstuff/hello.md": "# Hello\nworld!"
},
"checks": [
{ "type": "dirContents", "path": "/home/student/oldstuff", "names": [], "passOnMissing": true, "desc": "oldstuff/ should be empty before removing it" },
{ "type": "dirExists", "path": "/home/student/oldstuff", "negate": true, "desc": "oldstuff/ should be removed" },
{ "type": "commandEvent", "eventType": "coreutil.rmdir",
"match": { "absPaths": ["/home/student/oldstuff"] },
"desc": "should remove oldstuff/ with rmdir" },
{ "type": "tabCompletionHint" }
]
}
If youâre really sure that you want to remove a possibly non-empty directory
and all its contents (including all its subdirectories!),
then you can also remove it recursively using the rm (not rmdir)
command with the -r (recursive) option:
$ rm -r mytestfiles/
Warning
Be careful with rm -r, because it will not ask you for a confirmation in most cases,
and once the files are deleted, they cannot be recovered!
Double check that you specified the correct directory before pressing Enter to execute the command.
There is one exception: if rm encounters a file it canât write to (e.g. one owned by root), it still asks for confirmation before removing that file, even inside a -r removal and even without -i. See Section 2.5.3.5 for the full -i/-f story.
Exercise 2.122
Create a directory newdir. Now copy myfile.txt into it. Try to remove the directory with rmdir â notice this fails, since newdir/ isnât empty. Then remove everything at once using rm -r.
{
"filesystem": { "/home/student/myfile.txt": "hello\n" },
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.mkdir",
"match": { "absPaths": ["/home/student/newdir"] },
"desc": "should create newdir/" },
{ "type": "commandEvent", "eventType": "coreutil.cp",
"match": { "pairs": { "absSrc": "/home/student/myfile.txt", "absDest": "/home/student/newdir/myfile.txt" } },
"desc": "should copy myfile.txt into newdir/" },
{ "type": "stderrContains", "text": "Directory not empty", "pattern": "^rmdir\\s",
"desc": "rmdir newdir should fail, since it isn't empty" },
{ "type": "commandEvent", "eventType": "coreutil.rm",
"match": { "absPaths": ["/home/student/newdir"] },
"desc": "should remove newdir/ (and its contents) with rm -r" },
{ "type": "dirExists", "path": "/home/student/newdir", "negate": true }
]
}
Exercise 2.123
The directory project/ contains two ordinary files, notes.txt and todo.txt, and one file config.dat thatâs owned by root and not writable by you (mode 600).
Remove project/ recursively with rm -r project/. Youâll be asked about config.dat specifically (the two ordinary files are removed without asking) â answer n to keep it. Notice that project/ itself canât be removed either afterwards, since it isnât empty anymore.
{
"filesystem": {
"/home/student/project/notes.txt": "meeting notes\n",
"/home/student/project/todo.txt": "buy milk\n",
"/home/student/project/config.dat": { "content": "secret=1\n", "mode": "600", "owner": "root" }
},
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.rm",
"match": { "declined": { "absPath": "/home/student/project/config.dat" } },
"desc": "config.dat should have been prompted and declined (n)" },
{ "type": "fileExists", "path": "/home/student/project/notes.txt", "negate": true },
{ "type": "fileExists", "path": "/home/student/project/todo.txt", "negate": true },
{ "type": "fileExists", "path": "/home/student/project/config.dat" },
{ "type": "dirExists", "path": "/home/student/project" },
{ "type": "stderrContains", "text": "Directory not empty", "pattern": "^rm\\s+-[rR]",
"desc": "rm -r project/ should ultimately fail, since config.dat is still inside" }
]
}
Exercise 2.124
The same project/ directory as before exists again, with the same three files. This time, remove it completely using rm -rf project/ â notice -f skips the confirmation for config.dat too, and this time project/ really is gone afterwards.
{
"filesystem": {
"/home/student/project/notes.txt": "meeting notes\n",
"/home/student/project/todo.txt": "buy milk\n",
"/home/student/project/config.dat": { "content": "secret=1\n", "mode": "600", "owner": "root" }
},
"checks": [
{ "type": "commandEvent", "eventType": "coreutil.rm",
"match": { "pairs": { "absPath": "/home/student/project/config.dat", "prompted": false } },
"desc": "config.dat should have been removed without any prompt (that's what -f is for)" },
{ "type": "dirExists", "path": "/home/student/project", "negate": true }
]
}