Missing Semester Lecture 1 - The Shell
MIT The Missing semester Lecture of Your CS Education Lecture 1 - The Shell
Using the shell
When you launch your terminal, you will see a prompt that often looks a little like this:
1 | missing:~$ |
It tells that you are on the machine missing
and that your “current working directory”, or where you currently are, is ~
(short for “home”). The $
tells you that you are not the root user.
We can execute a command with arguments like this:
1 | missing:~$ echo hello |
In this case, we told the shell to execute the program echo
with the argument hello
. The echo
program simply prints out its arguments. The shell parses the command by splitting it by white space, and then runs the program indicated by the first word, supplying each subsequent word as an argument that the program can access. If you want to provide an argument that contains spaces or other special characters (e.g., a directory named “My Photos”), you can either quote the argument with '
or "
("My Photos"
), or escape just the relevant characters with \
(My\ Photos
).
But how does the shell know how to find the date
or echo
programs? When you run commands in your shell, you are really writing a small bit of code that your shell interprets. If the shell is asked to execute a command that doesn’t match one of its programming keywords, it consults an environment variable called $PATH
that lists which directories the shell should search for programs when it is given a command:
1 | missing:~$ echo $PATH |
When we run the echo
command, the shell sees that it should execute the program echo
, and then searches through the :
-separated list of directories in $PATH
for a file by that name. When it finds it, it runs it (assuming the file is executable; more on that later). We can find out which file is executed for a given program name using the which
program. We can also bypass $PATH
entirely by giving the path to the file we want to execute.
Navigating in the shell
On Linux and macOS, the path /
is the “root” of the file system, whereas on Windows there is one root for each disk partition (e.g., C:\
). In a path, .
refers to the current directory, and ..
to its parent directory.
To see what lives in a given directory, we use the ls
command. Usually, running a program with the -h
or --help
flag will print some help text that tells you what flags and options are available. For example, ls --help
tells us:
1 | -l use a long listing format |
1 | missing:~$ ls -l /home |
This gives us a bunch more information about each file or directory present. First, the d
at the beginning of the line tells us that missing
is a directory. Then follow three groups of three characters (rwx
). These indicate what permissions the owner of the file (missing
), the owning group (users
), and everyone else respectively have on the relevant item. A -
indicates that the given principal does not have the given permission. Above, only the owner is allowed to modify (w
) the missing
directory (i.e., add/remove files in it). To enter a directory, a user must have “search” (represented by “execute”: x
) permissions on that directory (and its parents). To list its contents, a user must have read (r
) permissions on that directory. For files, the permissions are as you would expect. Notice that nearly all the files in /bin
have the x
permission set for the last group, “everyone else”, so that anyone can execute those programs.
If you ever want more information about a program’s arguments, inputs, outputs, or how it works in general, give the man
program a try. It takes as an argument the name of a program, and shows you its manual page. Press q
to exit.
1 | missing:~$ man ls |
Connecting programs
In the shell, programs have two primary “streams” associated with them: their input stream and their output stream. When the program tries to read input, it reads from the input stream, and when it prints something, it prints to its output stream. Normally, a program’s input and output are both your terminal. That is, your keyboard as input and your screen as output. However, we can also rewire those streams!
The simplest form of redirection is < file
and > file
. These let you rewire the input and output streams of a program to a file respectively:
1 | missing:~$ echo hello > hello.txt |
You can also use >>
to append to a file. Where this kind of input/output redirection really shines is in the use of pipes. The |
operator lets you “chain” programs such that the output of one is the input of another:
1 | missing:~$ ls -l / | tail -n1 |
A versatile and powerful tool
For example, the brightness of your laptop’s screen is exposed through a file called brightness
under
1 | /sys/class/backlight |
By writing a value into that file, we can change the screen brightness. Your first instinct might be to do something like:
1 | $ sudo find -L /sys/class/backlight -maxdepth 2 -name '*brightness*' |
This error may come as a surprise. After all, we ran the command with sudo
! This is an important thing to know about the shell. Operations like |
, >
, and <
are done by the shell, not by the individual program. echo
and friends do not “know” about |
. They just read from their input and write to their output, whatever it may be. In the case above, the shell (which is authenticated just as your user) tries to open the brightness file for writing, before setting that as sudo echo
’s output, but is prevented from doing so since the shell does not run as root. Using this knowledge, we can work around this:
1 | $ echo 3 | sudo tee brightness |
Since the tee
program is the one to open the /sys
file for writing, and it is running as root
, the permissions all work out.
Exercises
- Because I’m on Windows, so I installed Cent OS 7 to do this exercise. To make sure I’m running an appropriate shell, I try the command:
1 | [root@localhost ~]# echo $SHELL |
and make sure I’m running the right program.
- Create a new directory called
missing
under/tmp
1 | [root@localhost tmp]# mkdir /tmp/missing |
- Look up to the
touch
program.
1 | [root@localhost tmp]# man touch |
touch
is mainly used to update the access and modification times of each FILE to the current time.
- Use
touch
to create a new file calledsemester
inmissing
1 | [root@localhost missing]# touch semester |
- Write the following into that file, one line at a time:
1 | #!/bin/sh |
1 | [root@localhost missing]# echo '#!/bin/sh' > semester |
- Try to execute the file, i.e. type the path to the script (
./semester
) into your shell and press enter. Understand why it doesn’t work by consulting the output ofls
.
1 | [root@localhost missing]# ./semester |
It’s obviously that the reason why it doesn’t work is all groups’ users do not have the execute permission.
- Run the command by explicitly starting the
sh
interpreter, and giving it the filesemester
as the first argument, i.e.sh semester
. Why does this work, while./semester
didn’t?
This is because ./semester
requires semester
be executable, and it uses the shell specified as its first line (in the “shebang”, e.g. #!/bin/sh
), if any. In this case, I don’t have semester
‘s execute permission, so it didn’t work.
But sh semester
works as long as semester
is readable, and it uses sh
regardless of what the script shebang specifies. In this case, I have semester
‘s read permission, so it worked.
Reference: What is the difference between sh and ./ when invoking a shell script? - Unix & Linux Stack Exchange
- Look up the
chmod
program.
1 | [root@localhost missing]# man chmod |
chmod
is mainly used to changes the file mode bits of each given file according to mode.
- Use
chmod
to make it possible to run the command./semester
rather than having to typesh semester
. How does your shell know that the file is supposed to be interpreted usingsh
?
1 | [root@localhost missing]# chmod +x semester |
Because the shebang in semester
is #!/bin/sh
, which specifies using /bin/sh
to be intercepted.
- Use
|
and>
to write the “last modified” date output bysemester
into a file calledlast-modified.txt
in your home directory.
1 | [root@localhost missing]# date -r semester | cat > ~/last-modified.txt |