2.5.3. Making, copying, moving and removing files#

2.5.3.1. Making and editing files#

We talked a lot about files, but you have so far only looked at files that already existed.

2.5.3.1.1. Text editor#

The easiest way to create a new file, or edit an existing fie, is of course to use a text editor from the desktop environment, such as gedit. However, sometimes this won’t be available (for example, if you’re working over a network connection via a secure shell).

In Ubuntu, the easiest to learn text-mode editor that’s available is called nano[1]. It helpfully prints a list of commands at the bottom of the screen, see Fig. 2.6.

Note that ^A is a standard way of describing the keyboard shortcut Ctrl-A. For example, Ctrl-G (^G) will open a help menu, and Ctrl-X (^X) will quit nano.

Likewise, the M- prefix refers to pressing the Alt key (a Meta key), so M-6 refers to the Alt-6 key combination to copy a line.

../../_images/nano_editor.png

Fig. 2.6 Screenshot of editing a file from the terminal using the nano editor.#

Exercise 2.94

Use nano to create a new file containing the text I swallowed a bug. Save it under the name river.txt. As indicated by the bottom bar, you can save the file using ^O (ā€œWrite Outā€).

Note that this embedded bash emulator has a simplified nano emulator, with fewer functions than the real nano you will find on a modern Linux system.

{
  "checks": [
    { "type": "commandEvent", "eventType": "nano.write-out", "match": { "absPath": "/home/student/river.txt" },
      "desc": "should save the file as river.txt from within nano (Ctrl-O, then Enter)" },
    { "type": "fileContains", "path": "/home/student/river.txt", "text": "I swallowed a bug",
      "desc": "river.txt should contain the text I swallowed a bug." }
  ]
}

Exercise 2.95

Use nano to edit the file fruit.txt. Remove the two lines that are not a fruit, and copy-paste the remaining line three times.

{
  "filesystem": { "/home/student/fruit.txt": "robot\npineapple\nbird" },
  "checks": [
    { "type": "commandEvent", "eventType": "nano.write-out", "match": { "absPath": "/home/student/fruit.txt" },
      "desc": "should save the file as fruit.txt from within nano (Ctrl-O, then Enter)" },
    { "type": "fileContains", "path": "/home/student/fruit.txt", "text": "pineapple\npineapple\npineapple\npineapple",
      "desc": "fruit.txt should contain the four times the same line with a fruit." }
  ]
}

Exercise 2.96

Open nano on your real Ubuntu Linux computer, and figure out which of the following statements are true (you can use its help menu, or just try out things). Note that these functions may not be working in the bash emulator in this manual.

  • Alt-U undoes your latest edit

  • Alt-R redoes your latest undo operation

  • Ctrl-/ allows you to search for a text string

  • Ctrl-F allows you to search for a text string

  • Ctrl-F allows you to search for a text string

  • Alt-N shows or hides the line numbers

  • Ctrl-N shows or hides the line numbers

  • Ctrl-C shows the line and column number where the cursor currently is

  • Ctrl-C quits nano

2.5.3.1.2. Creating an empty file with one command, touch#

If you just want to create an empty file, you can also do this in one go with touch:

$ touch testfile.txt

This creates an empty file testfile.txt if it didn’t exist yet, without you needing to open and close an editor. This command can be helpful when you quickly want to create some dummy files to try out copying, removing and other file operations throughout this manual.

However, the main use of touch is not just to create a file, but to update an existing (non-empty) file’s modification time to the current clock. The file system logs for each file when it was first created, but also when it was last modified. For example, with ls -t you can lists your files sorted by modification time, making it easy to find recently updated files in a big directory. The file system automatically changes the modification time every time a file is saved, but sometimes you want to update a file’s modification time without actually modifying its content. This can be done by using touch on the file.

Exercise 2.97

Create two empty files fileA.txt and fileB.txt using touch, in this order:

touch fileA.txt
touch fileB.txt

Now list the files sorted by time using ls -t. Which file shows up first in this order?

Now run touch fileA.txt again, and check the output of ls -t. How did the order change?

You can also run touch river.txt to update the modification time of the file from the previous exercise. Check the file order of ls -t, and also confirm (with cat, less or nano) that the content of the file was left unaltered.

{
  "filesystem": {
    "/home/student/river.txt": "I swallowed a bug.\n"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.touch", "match": { "absPaths": ["/home/student/fileA.txt"] },
      "desc": "touch fileA.txt" },
    { "type": "commandEvent", "eventType": "coreutil.touch", "match": { "absPaths": ["/home/student/fileB.txt"] },
      "desc": "touch fileB.txt" },
    { "type": "stdoutMatches", "cmdPattern": "^ls\\s+-t$", "pattern": "fileB\\.txt[\\s\\S]*fileA\\.txt", "passOnce": true,
      "desc": "ls -t should list fileB.txt before fileA.txt (it was touched more recently)" },
    { "type": "stdoutMatches", "cmdPattern": "^ls\\s+-t$", "pattern": "fileA\\.txt[\\s\\S]*fileB\\.txt", "passOnce": true,
      "desc": "after touching fileA.txt again, ls -t should list it before fileB.txt" },
    { "type": "commandEvent", "eventType": "coreutil.touch", "match": { "absPaths": ["/home/student/river.txt"] },
      "desc": "touch river.txt" },
    { "type": "stdoutMatches", "cmdPattern": "^ls\\s+-t$", "pattern": "river\\.txt[\\s\\S]*fileA\\.txt[\\s\\S]*fileB\\.txt", "passOnce": true,
      "desc": "river.txt should now be listed first by ls -t" },
    { "type": "fileContains", "path": "/home/student/river.txt", "text": "I swallowed a bug.",
      "desc": "river.txt's content should be unchanged by touch" }
  ]
}

Exercise 2.98

Use a single touch command to create three empty files at once: x.txt, y.txt and z.txt.

{
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.touch",
      "match": { "absPaths": ["/home/student/x.txt", "/home/student/y.txt", "/home/student/z.txt"] },
      "desc": "touch x.txt y.txt z.txt in one command" },
    { "type": "fileExists", "path": "/home/student/x.txt" },
    { "type": "fileExists", "path": "/home/student/y.txt" },
    { "type": "fileExists", "path": "/home/student/z.txt" }
  ]
}

2.5.3.1.3. Downloading files with wget#

Another option is to download a file from an URL. This can be done from the terminal using wget [URL], where you replace [URL] with a direct link to the file that you want to download. By default, wget will then try to download the file, and save it with the same name in your current working directory (of course, wget has many more options, as you can find in its manual).

For example, the following will download a README.md from the internet, and save it as README.md in your local directory.

$ wget https://raw.githubusercontent.com/ros2/ros2_documentation/refs/heads/rolling/README.md

You should now be able to see the README.md file listed in your current directory, and be able to inspect it

$ ls
$ less README.md

Important

Downloading will, of course, only work when your Linux system is connected to the internet. If you do not have a working network connection, you will get an error from wget that it could not find the server.

Exercise 2.99

The wget command is also often used with the -O option. What will the following command do?

$ wget -O test.md https://raw.githubusercontent.com/ros2/ros2_documentation/refs/heads/rolling/README.md

Verify by checking the content of the current working directory.

2.5.3.2. Copying files#

To copy files, you can use the command cp. It’s quite simple:

$ cp originalfile copiedfile

will create a file copiedfile (if it’s not already there) and copy the contents of originalfile into it. You can specify more than one file to be copied, but then the last name you specify should be a directory, for example:

$ cp file1 file2 /tmp

Exercise 2.100

Copy your file myfile.txt to anotherfile.txt. Copy both files to /tmp. Verify this using ls /tmp.

{
  "filesystem": {
      "/home/student/myfile.txt": "hello from myfile\n",
      "/tmp": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "pairs": { "absSrc": "/home/student/myfile.txt", "absDest": "/home/student/anotherfile.txt" } },
      "desc": "cp myfile.txt anotherfile.txt" },
    { "type": "fileExists", "path": "/home/student/anotherfile.txt" },
    { "type": "fileContains", "path": "/home/student/anotherfile.txt", "text": "hello from myfile" },
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "absSrcs": ["/home/student/myfile.txt", "/home/student/anotherfile.txt"], "absDest": "/tmp" },
      "desc": "copy both files into /tmp in a single cp command" },
    { "type": "fileExists", "path": "/tmp/myfile.txt" },
    { "type": "fileExists", "path": "/tmp/anotherfile.txt" },
    { "type": "fileContains", "path": "/tmp/myfile.txt", "text": "hello from myfile" }
  ]
}

If the file you copy to already exists, cp will not ask you for confirmation before it overwrites it. Be careful!

Exercise 2.101

The files draft.txt and final.txt already exist, with different content. Copy final.txt over draft.txt (cp final.txt draft.txt), then check what happened to draft.txt’s original content.

{
  "filesystem": {
    "/home/student/draft.txt": "work in progress\n",
    "/home/student/final.txt": "ready to ship\n"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "pairs": { "absSrc": "/home/student/final.txt", "absDest": "/home/student/draft.txt" } },
      "desc": "cp final.txt draft.txt" },
    { "type": "fileContains", "path": "/home/student/draft.txt", "text": "ready to ship" },
    { "type": "fileMatches", "path": "/home/student/draft.txt", "pattern": "work in progress", "negate": true,
      "desc": "draft.txt's original content should be gone, without any warning" }
  ]
}

You can also specify entire directories to be copied, using the -r (recursive) option:

$ cp -r somedirectory anotherdirectory

This will create a directory somedirectory in the directory anotherdirectory and copy all files and directories below somedirectory there.

Exercise 2.102

Copy the whole project/ directory into backup/ using cp -r. Remember: this creates project/ inside backup/, not a backup/ directory with the same contents as project/.

{
  "filesystem": {
    "/home/student/project/README.md": "hello\n",
    "/home/student/project/src/main.cpp": "int main(){}\n",
    "/home/student/backup/": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "pairs": { "absSrc": "/home/student/project", "absDest": "/home/student/backup/project" } },
      "desc": "use cp -r to copy project/ into backup/" },
    { "type": "dirExists", "path": "/home/student/backup/project" },
    { "type": "fileExists", "path": "/home/student/backup/project/README.md" },
    { "type": "fileExists", "path": "/home/student/backup/project/src/main.cpp" },
    { "type": "fileContains", "path": "/home/student/backup/project/README.md", "text": "hello" }
  ]
}

Note

When working on a remote computer, as discussed in Section 2.4.9, it can be very useful to transfer files from one computer to the other. Instead of just cp, the command that accomplishes this is called scp, which securely copies files over the network. It works similar to the cp command, but needs some more information about server. A typical invocation looks like this:

$ scp originalfile user@host:copiedfile

where user is your username on the remote server, and host is its hostname or IP address. As with cp, you can specify a directory as the copiedfile, and the -r option works as well. As a convenience, you can omit copiedfile, in which case the original will be placed in your home directory on the server.

Exercise 2.103

Copy myfile.txt to the Student Linux Bastion server and verify that it arrived by logging in remotely and using the cat command. Also try to copy a file (say, /etc/redhat-release) from the server to your computer. You’ll need to do this locally, because the server doesn’t allow any outgoing connections; in this case, the user@host: specification needs to be given for the originalfile instead of the copiedfile.

2.5.3.3. Moving and renaming files#

Another option is to rename or move the file. In Unix , both these file operations are the same: if you move a file to another directory, all you do is give it another name. The mv command moves files and can be called just like cp.

Exercise 2.104

In a previous exercise, you created a copy of myfile.txt called anotherfile.txt. Rename this file to renamedfile.txt using mv. Also rename the project directory to old_project.

{
  "filesystem": {
    "/home/student/myfile.txt": "hello from myfile\n",
    "/home/student/anotherfile.txt": "hello from myfile\n",
    "/tmp": null,
    "/home/student/project/README.md": "hello\n",
    "/home/student/project/src/main.cpp": "int main(){}\n",
    "/home/student/backup/": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.mv",
      "match": { "pairs": { "absSrc": "/home/student/anotherfile.txt", "absDest": "/home/student/renamedfile.txt" } },
      "desc": "rename anotherfile.txt as renamedfile.txt" },
    { "type": "fileExists", "path": "/home/student/renamedfile.txt" },
    { "type": "fileExists", "path": "/home/student/anotherfile.txt", "negate": true },
    { "type": "commandEvent", "eventType": "coreutil.mv",
      "match": { "pairs": { "absSrc": "/home/student/project", "absDest": "/home/student/old_project" } },
      "desc": "rename project as old_project" },
    { "type": "dirExists", "path": "/home/student/old_project" },
    { "type": "dirExists", "path": "/home/student/project", "negate": true },
    { "type": "tabCompletionHint" }
  ]
}

Warning

Be careful when renaming files that the last argument that you give to mv, the intended new file name, is not an already existing file. If a file with that new name already exists, the existing file will be replaced without any confirmation, and its content will be lost.

To move one or more files into a directory, give mv all the file names you want to move as arguments, and the target directory as last argument. Note that this can lead to subtly different behaviors

  • mv foo.txt test: If test/ is an existing subdirectory, this will move the file foo.txt into that subdirectory

  • mv foo.txt test: If test is an existing file, it will rename foo.txt to this file name, losing the original file content.

  • mv foo.txt test: If test does not exist yet, it will just rename foo.txt to this file name.

  • mv foo.txt bar.txt test: This will attempt to move two files, foo.txt and bar.txt into a subdirectory test/ if it exists. If test does not exist or is a file, mv will give an error because with more than two arguments it understands you are trying to move files, not rename them.

Exercise 2.105

test/ already exists as a directory. Run mv foo.txt test and see where foo.txt ends up.

{
  "filesystem": {
    "/home/student/foo.txt": "content\n",
    "/home/student/test/": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.mv",
      "match": { "pairs": { "absSrc": "/home/student/foo.txt", "absDest": "/home/student/test/foo.txt" } },
      "desc": "mv foo.txt into the test/ directory" },
    { "type": "fileExists", "path": "/home/student/test/foo.txt",
      "desc": "foo.txt should be moved inside the test/ directory" },
    { "type": "fileExists", "path": "/home/student/foo.txt", "negate": true }
  ]
}

Exercise 2.106

This time test already exists, but as a file, not a directory. Run mv foo.txt test again and check what happened to test’s original content.

{
  "filesystem": {
    "/home/student/foo.txt": "new content\n",
    "/home/student/test": "old content, about to be lost\n"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.mv",
      "match": { "pairs": { "absSrc": "/home/student/foo.txt", "absDest": "/home/student/test" } },
      "desc": "mv foo.txt test" },
    { "type": "fileExists", "path": "/home/student/foo.txt", "negate": true,
      "desc": "foo.txt should no longer exist (it was renamed to test)" },
    { "type": "fileContains", "path": "/home/student/test", "text": "new content" },
    { "type": "fileMatches", "path": "/home/student/test", "pattern": "old content", "negate": true,
      "desc": "test's original content should be gone" }
  ]
}

Exercise 2.107

Now test does not exist yet at all. Run mv foo.txt test one more time and check what test turns out to be.

{
  "filesystem": { "/home/student/foo.txt": "content\n" },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.mv",
      "match": { "pairs": { "absSrc": "/home/student/foo.txt", "absDest": "/home/student/test" } },
      "desc": "mv foo.txt test" },
    { "type": "fileExists", "path": "/home/student/test" },
    { "type": "dirExists", "path": "/home/student/test", "negate": true,
      "desc": "test should be a (renamed) file, not a directory" },
    { "type": "fileExists", "path": "/home/student/foo.txt", "negate": true }
  ]
}

Exercise 2.108

Move the file renamedfile.txt into to /tmp (a special subdirectory inside the root directory / that can be used for temporary files). Verify that the file has really moved from your current directory and to /tmp using ls and ls /tmp.

{
  "filesystem": {
    "/home/student/renamedfile.txt": "hello from myfile\n",
    "/tmp": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.mv",
      "match": { "pairs": { "absSrc": "/home/student/renamedfile.txt", "absDest": "/tmp/renamedfile.txt" } },
      "desc": "mv renamedfile.txt /tmp" },
    { "type": "fileExists", "path": "/tmp/renamedfile.txt" },
    { "type": "fileExists", "path": "/home/student/renamedfile.txt", "negate": true },
    { "type": "tabCompletionHint" }
  ]
}

We will discuss mv for directories later in Section 2.5.4.2.

2.5.3.4. Removing files#

Finally, you will often want to remove files once you don’t need them anymore. The rm command does this for you. Be careful: if you remove a file in Unix , there’s no recycle bin, so there’s no way of getting it back!

Exercise 2.109

Use rm to remove the file you just moved, /tmp/renamedfile.txt. Verify using ls /tmp.

{
  "filesystem": {
    "/tmp/renamedfile.txt": "hello from myfile\n",
    "/tmp": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.rm", "match": { "absPaths": ["/tmp/renamedfile.txt"] },
      "desc": "rm /tmp/renamedfile.txt" },
    { "type": "fileExists", "path": "/tmp/renamedfile.txt", "negate": true },
    { "type": "tabCompletionHint" }
  ]
}

Like cp, you can use rm recursively: rm -R. Be very careful with this option! You can throw away things you didn’t really want to[2].

Exercise 2.110

The directory oldproject/ (with some files inside) is no longer needed. Remove it completely, including its contents, using rm -R.

{
  "filesystem": {
    "/home/student/oldproject/notes.txt": "todo\n",
    "/home/student/oldproject/src/main.cpp": "int main(){}\n",
    "/tmp": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.rm",
      "match": { "absPaths": ["/home/student/oldproject"] },
      "desc": "remove oldproject/ with rm (needs -r or -R, since it's a directory)" },
    { "type": "dirExists", "path": "/home/student/oldproject", "negate": true,
      "desc": "oldproject/ should be completely gone" },
    { "type": "tabCompletionHint" }
  ]
}

2.5.3.5. Common options for cp, mv and rm#

The cp, mv and rm commands have two options in common:

  • -i: if specified, always ask for confirmation when overwriting or removing files;

  • -f: if specified, never ask for confirmation when overwriting or removing files. Especially the -f option can be useful, but also very dangerous, as you can imagine…

Exercise 2.111

Select all of the following that are true:

  • -i makes cp, mv and rm always ask for confirmation before overwriting or removing a file.

  • -i disables all confirmation prompts.

  • -f means you will never be asked for confirmation, even if a file would be overwritten or deleted.

  • -f is the default behavior of cp, mv and rm without any options.

There’s actually a third case where rm asks for confirmation, even without -i: when the file isn’t writable by you. Removing a file only ever requires write permission on the directory it’s in, not on the file itself. So, rm treats ā€œI’m about to delete something I can’t even write toā€ as a likely accident, and double-checks with you first, unless you add -f.

Exercise 2.112

The files alpha.txt, beta.txt and gamma.txt already exist in your home directory. All three are owned by root with permissions that block you from writing to them (mode 600).

Remove alpha.txt and beta.txt in a single command (try rm *.txt), but keep gamma.txt. Since none of the three are writable by you, rm will ask you individually about each one – answer y for the two you want gone, and n for the one you want to keep.

{
  "filesystem": {
    "/home/student/alpha.txt": { "content": "keep me? no.\n", "mode": "600", "owner": "root" },
    "/home/student/beta.txt": { "content": "keep me? no.\n", "mode": "600", "owner": "root" },
    "/home/student/gamma.txt": { "content": "keep me? yes.\n", "mode": "600", "owner": "root" }
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.rm",
      "match": { "absPaths": ["/home/student/alpha.txt", "/home/student/beta.txt"],
        "pairs": { "absPath": "/home/student/alpha.txt", "prompted": true } },
      "desc": "should remove alpha.txt and beta.txt in a single rm command, confirmed (y) at the prompt" },
    { "type": "commandEvent", "eventType": "coreutil.rm",
      "match": { "pairs": { "absPath": "/home/student/beta.txt", "prompted": true } },
      "desc": "beta.txt's removal should also have gone through a real prompt" },
    { "type": "fileExists", "path": "/home/student/alpha.txt", "negate": true },
    { "type": "fileExists", "path": "/home/student/beta.txt", "negate": true },
    { "type": "fileExists", "path": "/home/student/gamma.txt" },
    { "type": "commandEvent", "eventType": "coreutil.rm",
      "match": { "declined": { "absPath": "/home/student/gamma.txt" } },
      "desc": "gamma.txt should have actually been prompted and declined (n), not just left out of the command" }
  ]
}

Exercise 2.113

The same three files exist again, still all owned by root with mode 600. This time, remove all three in a single command using -f: rm -f *.txt. Notice that -f skips the confirmation prompts entirely – there’s nothing to answer, and all three files disappear immediately, even though none of them were writable by you. This is exactly why rm -Rf is more dangerous than rm -R: the one safety net that might have given you a chance to reconsider is gone.

{
  "filesystem": {
    "/home/student/alpha.txt": { "content": "keep me? no.\n", "mode": "600", "owner": "root" },
    "/home/student/beta.txt": { "content": "keep me? no.\n", "mode": "600", "owner": "root" },
    "/home/student/gamma.txt": { "content": "keep me? yes.\n", "mode": "600", "owner": "root" }
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.rm",
      "match": { "absPaths": ["/home/student/alpha.txt", "/home/student/beta.txt", "/home/student/gamma.txt"],
        "pairs": { "absPath": "/home/student/gamma.txt", "prompted": false } },
      "desc": "should remove all three files in a single rm -f command, with -f suppressing the prompt entirely" },
    { "type": "fileExists", "path": "/home/student/alpha.txt", "negate": true },
    { "type": "fileExists", "path": "/home/student/beta.txt", "negate": true },
    { "type": "fileExists", "path": "/home/student/gamma.txt", "negate": true }
  ]
}

Unlike rm, cp and mv never ask for confirmation on their own. Only -i turns that on the interactive prompt ability, and only for a destination that already exists (creating a brand new file never prompts).

Exercise 2.114

The files report.txt and summary.txt already exist in your home directory, and so does an empty backup/ directory.

  1. Copy both into backup/ in a single command: cp *.txt backup/.

  2. Update report.txt’s content: echo 'v2 report' > report.txt. Copy again with the exact same command, cp *.txt backup/ (still no -i). Notice it silently overwrites backup/report.txt, without asking anything.

  3. Now also update summary.txt: echo 'v2 summary' > summary.txt. Copy one more time, but now with -i: cp -i *.txt backup/. This time you’ll be asked to confirm each overwrite. Answer y for report.txt, but n for summary.txt, so backup/summary.txt keeps its old content instead of being updated.

{
  "filesystem": {
    "/home/student/report.txt": "v1 report\n",
    "/home/student/summary.txt": "v1 summary\n",
    "/home/student/backup/": null
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "absSrcs": ["/home/student/report.txt", "/home/student/summary.txt"], "absDest": "/home/student/backup" },
      "desc": "copy both files into backup/ in a single cp command" },
    { "type": "fileContains", "path": "/home/student/backup/report.txt", "text": "v2 report",
      "desc": "backup/report.txt should have the updated content (silently overwritten, no -i needed)" },
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "pairs": { "absDest": "/home/student/backup/report.txt", "prompted": true } },
      "desc": "report.txt's overwrite should have gone through an actual -i confirm prompt (answered y)" },
    { "type": "fileContains", "path": "/home/student/backup/summary.txt", "text": "v1 summary",
      "desc": "backup/summary.txt should still have its old content, you should have declined (n) that overwrite" },
    { "type": "fileMatches", "path": "/home/student/backup/summary.txt", "pattern": "v2 summary", "negate": true,
      "desc": "backup/summary.txt should not have been updated" },
    { "type": "commandEvent", "eventType": "coreutil.cp",
      "match": { "declined": { "absDest": "/home/student/backup/summary.txt" } },
      "desc": "summary.txt's overwrite should have actually been prompted and declined (n), not just skipped" }
  ]
}