2.5.1. Files, and their properties#

In Listing the content of a directory you learned the basic usage of the ls command to ā€œlistā€ all files in a directory. A very useful option to the ls command you saw before is -l, which tells ls to give more information on each file or directory:

$ ls -l /bin
total 6632
-rwxr-xr-x 1 root root  818232 2010-04-19 03:51 bash
-rwxr-xr-x 3 root root   30200 2010-02-08 11:54 bunzip2
-rwxr-xr-x 1 root root 1269432 2010-04-22 22:04 busybox
-rwxr-xr-x 3 root root   30200 2010-02-08 11:54 bzcat
lrwxrwxrwx 1 root root       6 2010-07-07 11:48 bzcmp -> bzdiff
-rwxr-xr-x 1 root root    2140 2010-02-08 11:54 bzdiff
lrwxrwxrwx 1 root root       6 2010-07-07 11:48 bzegrep -> bzgrep
-rwxr-xr-x 1 root root    4874 2010-02-08 11:54 bzexe
(etc.)

First of all, it shows you how many blocks the entire directory takes up on the filesystem. A block does not have the same size on all machines, but on Linux PCs it’s usually one kilobyte (1024 bytes). The directory /bin seems to take up 6632 blocks, or roughly 6.6 megabytes.

Next, for each file, ls -l now shows the file type, the first character of the string on the left. The file type indicates whether the file is a plain file (indicated by -), a link (l, see below) or a directory (d). There are some other file types, like b (block device) and c (character device), but you will not encounter these regularly, so don’t worry about them.

Some other important information given by ls -l is the user and group of the file (here: user root, group root); the date and time of creation; and, of course, the filename.

Exercise 2.65

A directory ~/project already exists. Change into it and run ls -l to see its contents for yourself.

{
  "filesystem": {
    "/home/student/project/README.md": "# Project notes\n",
    "/home/student/project/run.sh": "#!/bin/bash\necho hi\n",
    "/home/student/project/src/": null,
    "/home/student/project/data.txt/": null,
    "/home/student/project/logs": "No messages logged",
    "/home/student/project/src_link": { "symlink": "src" }
  }
}

Based on the first character shown by ls -l, select all of the following that are true:

  • README.md is a directory.

  • run.sh is a directory.

  • src is a directory.

  • data.txt is a directory.

  • logs is a directory.

  • src_link is a directory.

  • src_link is a link.

2.5.1.1. Wildcards and glob patterns#

Above, you used ls to list files. However, often you want to limit the number of files you see, for example when you know that you’re looking for a filename containing a certain word. If this is the case, you can use wildcards: special characters that the shell expands into a list of matching filenames before it even runs the command. A command argument built out of ordinary characters plus one or more wildcards (like my*.txt below) is called a glob pattern (or just ā€œa globā€), and the expansion itself is called globbing. (The name comes from an early Unix program literally called glob, short for ā€œglobalā€: it expanded these patterns into matching filenames before shells did that job themselves.)

Here are some of the most common wildcards, with some example glob patterns in action:

  • * will match any piece of filename. For example:

    • ls *: list all files in the current directory;

    • ls my*: list all file names starting with my;

    • ls *.txt: list all file names ending with the .txt file extension (including the .);

    • ls /etc/*.conf: list all files in the /etc directory ending with .conf.

    • ls */*: list all files in all immediate subdirectories.

  • ? will match a single character in a filename. For example:

    • ls myfile?.txt: lists myfile0.txt, myfile1.txt, myfileq.txt, etc.

  • [abZ] will match the letter a, or the letter b, or the letter Z. For example:

    • ls myfile[X_8b].txt: can list myfileX.txt, myfile_.txt, myfile8.txt and myfileb.txt.

  • [a-z] matches all letters in the range a - z, e.g.:

    • ls myfile[A-K].txt: lists myfileA.txt … myfileK.txt.

    • ls myfile[0-4].txt: lists myfile0.txt, myfile1.txt, myfile2.txt, myfile3.txt or myfile4.txt.

    • ls myfile[a-zA-Z].txt: lists any variant of the same file, with either a lowercase (e.g., myfilej.txt) or an uppercase letter (e.g., myfileY.txt)

You can always use wildcards in the Unix shell when you have to specify a filename.

Exercise 2.66

List all files in /usr/bin starting with a z. Also list all files in /usr/bin that contain the string text in their name.

Exercise 2.67

cd into ~/photos, which already contains a mix of numbered and lettered files, and a couple of subdirectories. Using the wildcards from above:

  1. List only the files named photo followed by a single digit and the extension .jpg (so photo1.jpg, but not photoA.jpg), using ?.

  2. List only the log files report_a.log through report_e.log (not report_1.log), using a character range.

  3. List every file with the .jpg extension, regardless of what comes before it, using *.

  4. List every file inside all of ~/photos’s immediate subdirectories, in a single command, using a directory wildcard.

{
  "filesystem": {
    "/home/student/photos/photo1.jpg": "",
    "/home/student/photos/photo2.jpg": "",
    "/home/student/photos/photo3.jpg": "",
    "/home/student/photos/photoA.jpg": "",
    "/home/student/photos/report_a.log": "",
    "/home/student/photos/report_b.log": "",
    "/home/student/photos/report_c.log": "",
    "/home/student/photos/report_1.log": "",
    "/home/student/photos/vacation/beach.jpg": "",
    "/home/student/photos/work/slides.pdf": ""
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.ls",
      "match": { "absPaths": { "$equals": ["/home/student/photos/photo1.jpg", "/home/student/photos/photo2.jpg", "/home/student/photos/photo3.jpg"] } },
      "desc": "should list exactly photo1.jpg, photo2.jpg and photo3.jpg, and nothing else" },
    { "type": "commandEvent", "eventType": "coreutil.ls",
      "match": { "absPaths": { "$equals": ["/home/student/photos/report_a.log", "/home/student/photos/report_b.log", "/home/student/photos/report_c.log"] } },
      "desc": "should list exactly report_a.log, report_b.log and report_c.log, and nothing else" },
    { "type": "commandEvent", "eventType": "coreutil.ls",
      "match": { "absPaths": { "$equals": ["/home/student/photos/photo1.jpg", "/home/student/photos/photo2.jpg", "/home/student/photos/photo3.jpg", "/home/student/photos/photoA.jpg"] } },
      "desc": "should list exactly the four .jpg files, and nothing else" },
    { "type": "commandEvent", "eventType": "coreutil.ls",
      "match": { "absPaths": { "$equals": ["/home/student/photos/vacation/beach.jpg", "/home/student/photos/work/slides.pdf"] } },
      "desc": "should list exactly the files in vacation/ and work/, and nothing else" }
  ]
}

The use of wildcards is not unique to ls, but works with any command that can take one or more file paths as arguments.

Exercise 2.68

A single cat command with multiple file names as arguments, like cat a.txt b.txt, would print the content of all the given files, one after another. In other words, it would ā€œconcatenateā€ the file contents of a.txt and b.txt (hence the name cat).

Various text files already exist in your home directory. Use a single cat command with a glob pattern to print all text files with the .txt extension, one after another, without writing out the full filenames as arguments.

{
  "filesystem": {
    "/home/student/myfile1.txt": "first file\n",
    "/home/student/myfile2.txt": "second file\n",
    "/home/student/myfile3.txt": "third file\n",
    "/home/student/myfile4.md": "markdown file\n",
    "/home/student/zzz.txt": "last file\n"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.cat",
      "match": { "absPaths": ["/home/student/myfile1.txt", "/home/student/myfile2.txt", "/home/student/myfile3.txt", "/home/student/zzz.txt"] },
      "desc": "should cat all .txt files in one command" },
    { "type": "commandSucceeded", "pattern": "^cat\\s+\\S*[*?]\\S*\\s*$",
      "desc": "should use an actual glob pattern (e.g. myfile*.txt), not just list both filenames literally" },
    { "type": "stdoutContains", "text": "first file" },
    { "type": "stdoutContains", "text": "second file" },
    { "type": "stdoutContains", "text": "third file" },
    { "type": "stdoutContains", "text": "last file" },
    { "type": "stdoutContains", "text": "markdown file", "negate": true }
  ]
}

2.5.1.3. File permissions#

Next to the file type, ls -l shows the file permissions, the row of r’s, w’s and x’s. This list really consists of three groups of three characters each. The first three characters contains the permissions for the user, the owner of the file; the second three are for the group, the group of the file; and the last three are for others, everybody else. The idea of a user group will be explained below.

The three permission flags are:

  • r: if this flag is present, the file can be read from;

  • w: if this flag is present, the file can be written to;

  • x: if this flag is present for a file, the file can be executed; if it’s present for a directory, the directory contents can be read.

This might all sound a bit cryptic, so here are a couple of examples. These are the permissions you will encounter most:

  • rw-r--r-- myfile.txt means that the owner of myfile.txt can read and write the file, but people in the same group and others can just read the file. This is usually the default for text files: you allow people to see the contents of the file but not to change them.

  • rwxr-xr-x myprogram means that everybody can execute the program myprogram, but only the owner can overwrite it. Again, this is often the default for programs.

  • rwxr-xr-x mydirectory means that everybody can see which files reside in mydirectory, but only the owner of the directory can write new files to that directory.

Exercise 2.72

Try to list the files in the administrator’s home directory (see the Linux filesystem overview). Find out with ls -l why this doesn’t work. Now try again, prefixing your command with sudo. Explain why this second method works by studying the sudo manual page[1].

You can change file permissions with the chmod command. The command, in its simplest form, looks like this:

$ chmod ugo+rwx somefile

This call of chmod gives permission to read (r), write (w) and execute (x) somefile to the user (u) of the file, its group (g) and others (o). Of course, you can specifiy subsets, e.g.:

$ chmod u+rx somefile

The + means ā€œadd permissionā€. It’s also possible to remove permissions, using -. For example, to make sure you are the only one able to read somefile:

$ chmod go-r somefile

Exercise 2.73

Change the permissions on your file myfile.txt so that you yourself (the user) cannot read it anymore. Verify this using cat or less. Finally change the permissions back so that you can read it again.

Exercise 2.74

A script backup.sh already exists in your home directory. Change its permissions so that only you (the owner) can read, write and execute it, with no permissions at all for group or others.

{
  "filesystem": {
    "/home/student/backup.sh": "#!/bin/bash\necho backing up...\n"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.chmod",
      "match": { "pairs": { "absPath": "/home/student/backup.sh" } },
      "desc": "should use chmod to alter permissions of backup.sh" },
    { "type": "fileMode", "path": "/home/student/backup.sh", "mode": "700",
      "desc": "backup.sh should have mode 700 (owner: rwx, group/others: none)" }
  ]
}

Typing ugo+rwx can be rather verbose. If you want to quickly set all file permissions at once, chmod also provides a numeric shorthand notation to express the permissions: The idea is to interpret the file permissions can be considered as 3 sets of 3 bits, and then interpret each 3-bit sequence as the binary notation for a regular (decimal) number in the range 0 - 7. This results in a code consisting of 3 decimal numbers.

For example, consider you want to give all permissions to the file’s user, read and execute permissions to the group, and only read permissions to everybody else. In other words, we would want ls -la to show us rwxr-xr-- for the complete file permissions, then:

  • user: rwx is 111 in binary, which becomes 7 in decimal.

  • group: r-x is 101 in binary, which becomes 5 in decimal.

  • other: r-- is 100 in binary, which becomes 4 in decimal.

The full permission shorthand would therefore correspond to code 754, and we can set all permissions at once with:

$ chmod 754 myfile.txt

Exercise 2.75

Which of the following are true?

  • Mode 000 corresponds to ---------

  • Mode 111 corresponds to r--r--r--

  • Mode 644 corresponds to rw-r--r--

  • Mode 644 corresponds to -wxr-xr-x

  • Mode 744 corresponds to r--r--rwx

  • Mode 755 corresponds to rwxr-xr-x

  • Mode 777 corresponds to rwxrwxrwx

  • Mode 600 gives the group read permission

  • Mode 531 gives all users execution permission

  • Mode 755 is suitable for executable programs

  • Mode 644 is suitable for executable programs

Exercise 2.76

A configuration file startup.conf already exists in your home directory. Restrict its permissions so that only its owner can read and write it, and nobody else has any access at all (mode 600). Don’t be surprised if your first attempt doesn’t work: look closely at who actually owns the file, and think back to how you solved a similar problem earlier in this chapter.

{
  "filesystem": {
    "/home/student/startup.conf": { "content": "autostart=true\n", "mode": "644", "owner": "root" }
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.chmod",
      "match": { "pairs": { "absPath": "/home/student/startup.conf", "mode": "600" } },
      "desc": "should chmod startup.conf to mode 600" },
    { "type": "fileMode", "path": "/home/student/startup.conf", "mode": "600",
      "desc": "startup.conf should end up with mode 600 (owner: rw-, group/others: none)" }
  ]
}

Exercise 2.77

Below is a project directory in your home folder, containing a few files with different permissions already set. The ls program itself (/usr/bin/ls) is also relevant here, and already exists on the system regardless of what you seed yourself. Inspect their permissions for yourself, then select all of the statements below that are true.

{
  "filesystem": {
    "/home/student/project/notes.txt": "meeting notes\n",
    "/home/student/project/private.txt": { "content": "shh, secret\n", "mode": "600" },
    "/home/student/project/system.conf": { "content": "port=8080\n", "mode": "640", "owner": "root" }
  }
}
  • The file notes.txt has execute permission for the owner.

  • The directory project has execute permission for others.

  • The file /usr/bin/ls does not have write permission for the group.

  • The user student has write permission for /usr/bin/ls.

  • The user student has read permission for notes.txt.

  • The file private.txt has read permission for the group.

  • The file private.txt does not have any permissions for others.

  • The user student has read permission for private.txt.

  • The user student has read permission for system.conf.

  • The file system.conf has read permission for the group.

  • The user student has write permission for system.conf.

  • The user student has execute permission for /usr/bin/ls.

2.5.1.4. User groups#

As discussed above, you can assign permissions for the user of a file, the group of a file and others. The output of ls -l shows the user and group to which the files belong. In the listing of /usr/bin above, all files are owned by user root, who is in group root.

You might wonder what a group exactly is. Groups were made to make system administration easier. Say you run a company in which three project teams work on different projects. You would like the members of each team to be able to read and write the team’s files, but members of one team shouldn’t be able to read another team’s files. The group mechanism makes this very easy, by putting users into three different groups and setting the file permissions correctly.

To see what groups you belong to, you can (for example) use id:

Exercise 2.78

Try this:

$ id

It should print your user id (that is, your user number), your group id and all groups you belong to.

Exercise 2.79

You just used id to find your own numerical user id. What is the numerical user id of the root user? Find out for yourself.

{
  "checks": [
    { "type": "stdoutMatches", "pattern": "uid=0\\(root\\)", "cmdPattern": "id",
      "desc": "run a command that shows the root's uid" }
  ]
}

If you belong to more than one group, you can decide which group your file should have. The command chgrp can be used to assign a new group to the file, for example:

$ chgrp othergroup myfile.txt

You are often part of many groups, such as the people allowed to use the modem (dialout) or that can administer the printer (lpadmin). The role of groups for sharing files with different people has been largely replaced by other mechanisms.

Exercise 2.80

The file shared_notes.txt already exists in your home directory, currently owned by your own user and group. First run id to confirm which groups you belong to. Since root is not among them, change the file’s group to root anyway using sudo chgrp.

{
  "filesystem": {
    "/home/student/shared_notes.txt": "shared with the team\n"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.id", "desc": "should run id to check your groups" },
    { "type": "commandEvent", "eventType": "coreutil.chgrp",
      "match": { "absPaths": ["/home/student/shared_notes.txt"], "group": "root" },
      "desc": "should use sudo chgrp root to change shared_notes.txt's group" },
    { "type": "fileGroup", "path": "/home/student/shared_notes.txt", "group": "root" }
  ]
}

2.5.1.5. Hidden files#

To avoid cluttering the screen whenever you type ls, the convention is that all files or directories with a name that starts with a single . are considered hidden.

There is nothing special about hidden files and directories themselves, you can use them (cd into them, rename them, delete them, etc.) just like regular files and directories. It is only certain programs, such as ls but also the graphical file explorer in the desktop environment, that have builtin logic to names starting with a . by default.

A common use case is for programs to store their configuration options in a hidden file or directory (usually in your home directory), so that you don’t notice them unless you specifically look for them.

For example, a version control system like git, can store information about a repository in a hidden .git/ directory in the repository root. Bash stores various configuration files in hidden files directly in your home directory. Let’s take a look.

Ensure you are in your home directory, and list the files there, e.g., with ls or ls -l,

$ cd ~
$ ls

You should not see any file or directory names starting with a . in the list output.

If you do want to see them, you will have to use ls -a or ls -la (the -a switch indicates you want to see all files).

$ ls -a

You should now see the hidden files and subdirectories in your home directory, including several ones related to Bash, such as .bashrc and .bash_history. You can inspect these files just as any other file, e.g.

$ less .bash_history

Note that with the -a switch ls also lists the current directory . and parent directory .., which also comply with the rule of the hidden names and are therefore not included in the listing by default.

Exercise 2.81

Find the hidden file in the home directory, and inspect it with less.

{
  "filesystem": {
    "/home/student/.hidden": "hello\nworld!"
  },
  "checks": [
    { "type": "commandEvent", "eventType": "coreutil.ls",
      "match": { "absPaths": ["/home/student"], "flags": ["a"] },
      "desc": "list the hidden files in home directory" },
    { "type": "commandEvent", "eventType": "builtin.less",
      "match": { "file": ".hidden" },
      "desc": "use `less` to inspect the file" }
  ]
}

Exercise 2.82

In the home directory there is a few hidden ā€œto doā€ notes. Instead of having several hidden files, make a hidden directory .todos in the home, and place the notes as regular (non-hidden) files in that directory .todos.

{
  "filesystem": {
    "/home/student/.tasks.txt": "# TODO\n- [x] do laundry\n- [ ] complete homework",
    "/home/student/.homework.md": "# Homework\n- [ ] finish shell exercises\n- [x] install Linux"
  },
  "checks": [
    { "type": "dirExists", "path": "/home/student/.todos", "desc": "hidden .todos directory should exist" },
    { "type": "fileExists", "path": "/home/student/.todos/tasks.txt", "desc": "tasks file should exist in the directory" },
    { "type": "fileExists", "path": "/home/student/.todos/homework.md", "desc": "homework file should exist in the directory" }
  ]
}

Exercise 2.83

Which of the following files or directories or hidden, and exist in the shell session below?

{
  "filesystem": {
    "/home/student/.setup.sh": "MYVAR=42\n",
    "/home/student/.config/log": "no messages\n",
    "/home/student/.config/.setup": "init(1)\n",
    "/home/student/tasks.txt": "# TODO\n- [ ] do laundry\n- [x] delete hidden tasks",
    "/var/log/.sentinel": "",
    "/var/log/d.messages": ""
  }
}
  • /

  • /home/

  • /home/student/

  • /home/student/.bashrc

  • /home/student/.config/

  • /home/student/.config/log

  • /home/student/.config/.setup

  • /home/student/.setup.sh

  • /var

  • /var/log

  • /var/log/.sentinel

  • /var/log/d.messages