In this lab, we will go through
gcc
and make
We can find the type of shell in-use in a terminal in the environment variable SHELL
.
$ echo $SHELL
In our course VM, the output is /bin/bash
, which is the path to the shell executable.
A path can be absolute, i.e., starting from the root of the file system, e.g. /home/csci3150
, or relative, i.e., starting from the current directory (.
), e.g. to list the current directory with ls ./
and the parent directory with ls ../
.
Every time the terminal is opened, we land on the home directory, which is stored in the variable HOME
.
$ echo $HOME
When you navigate among folders, the absolute path to the current working directory is always stored in PWD
.
$ echo $PWD
When you execute a command from the terminal, e.g. ls
, the shell first looks into the directories stored in PATH
sequentially for the first executable named ls
. It then runs the executable. The value of PATH
can be shown by executing,
$ echo $PATH
and in the course VM, you get the following sequence of folders
/usr/local/sbin:/usr/local/bin/:/usr/sbin:/usr/bin:/bin:/usr/games:/usr/local/games
with each of them separated by a colon :
. Users can run executables under these directories without specifying the path to these executables.
On the other hand, users need to specify the complete path to executables that are outside these directories, e.g. to run an executable named hello_world
under the current directory by executing ./hello_world
.
The practice of including only a few trusted directories is for better security, e.g. to avoid sproofing programs (see the explanation from IBM).
There are other common environment variables in Linux and users can also define their own variables using export
(see more details here).
echo
echo
simply repeats the input. Here is an example,
$ echo "hello world"
top
/ htop
top
is a built-in command line task manager which shows processes and resources utilization.
$ top
The first line shows the average system load in the last 1 minute, 5 minutes, and 15 minutes; the forth line reflects the utilization of physical memory.
Below the fifth line, there is a table of processes with resources utilization shown. We take the fifth process as an example and name the information shown in the columns from left to right,
csci3150
top
You may also refer to the manual page of top for details on the information shown.
To close the task manager, simply press q
.
Besides top
, you may install and use a more colorful task manager, htop
.
To install htop
,
$ sudo apt-get -y install htop
(The -y
option means automatically answer "Yes" for question asked)
Try calling htop
in a terminal,
$ htop
Same as top
, it can be close by pressing q
.
file
file
tries to identify the type of files using three different methods (see its manual page for details). For example, to determine the file type of /bin/bash
, execute
$ file /bin/bash
and file
reports /bin/bash
is an 32-bit executable.
cat
/ more
/ less
/ head
/ tail
cat
, more
, less
, head
, and tail
are commonly used commands for showing file content in a terminal, i.e., printing files' content to the terminal.
cat
concatenates files and prints all content to the terminal at once. For example, to print the content of a file named hello.txt
,
$ cat hello.txt
more
and less
are two other commands that do a similar job as cat
. Instead of printing all content at once, more
and less
divide and print one screen at a time.
head
prints a certain numbers of lines, 10 by default, from the beginning of a file. The number of printed lines can be controlled via the option -n
, e.g. to print only the first 2 lines of hello.txt
,
$ head -n 2 hello.txt
tail
is almost the same as head
, except that it counts lines from the end of a file.
You may refer to the online post here for more details and examples on these commands.
grep
grep
searches files and prints the lines in which keywords are found, with keywords highlighted.
To search a phrase with one or more words, embrace the phrase with double quotes (""
).
Besides, grep
supports regular expressions (regex) besides keywords in plaintext, and you may refer to this guide for more details on the use of regex for grep
. Beware that the syntax for regex may differ among commands, i.e., the one works for grep
may or may not work for other commands.
Below are some useful operators in Bash with simple examples of their usage.
&
)Adding &
at the end of a command allows you to start a process in background, e.g. to start and run a task manager in background,
$ htop &
The command returns the number of processes currently in background inside the square barracks ([]
), followed by the id of the last process put into background.
To bring the last "background-ed" process foreground, execute
$ fg
!
)The NOT operator !
allows you specify exceptions when running a command. Let try to understand it via an example.
First, create a folder test
$ mkdir test
Then, we move into the folder test
and create some files under this folder
$ cd test
$ touch a b a.txt b.txt
By default, $ ls
shows the four files we created under the folder test
. To show files except those ended with the extension .txt
, we may use
$ ls !(*.txt)
Note that *
represents any number of characters, and is known as one of the wild characters (see the documentation for more details).
|
)By putting |
between 2 commands, the output of the first command is piped to the second command as its input, e.g. to show the name of files with keyword D
after listing the current directory,
$ ls -1 | grep "D"
pipe ensures that data
(see more details on pipe here).
>
)Adding a >
to the end of a command, followed by a file name, redirects the standard output stream (stdout
) (What are standard streams?) to a file, i.e. the output is written to the file instead of the terminal , e.g.
$ ls >ls.out
The output of ls
written to the file ls.out
instead of the terminal. You may recap the output by executing $ cat ls.out
.
However, errors are usually output to the standard error stream (stderr
) instead of stdout
. To redirect stderr
, we should specify its file descriptor, which is 2
, explicitly before >
, e.g.
$ haha 2>haha.err
The error message is then written to the file haha.err
, instead of the terminal.
;
)To run multiple commands in series, you may chain up the commands with semi-colons (;
). The ;
operator marks the end of each command in a series of commands. For example, to create a file myfile
, copy it as myfile_copy
, create a folder file_copy
, and move the copied file into the folder,
$ touch myfile; cp myfile myfile_copy; mkdir file_copy; mv myfile_copy file_copy/
Note that the command is in one single line (although it may overflow in the html display).
&&
)If we replace ;
with &&
between two commands, the second command only runs if the first command succeeds, i.e., the first command exits with status 0 (more on exit status of Bash commands). For example,
(1) $ ls && echo ">> Success <<"
vs.
(2) $ haha && echo ">> Success <<"
Since ls
is a valid command but haha
is not, echo ">> Success <<"
runs in (1), but not (2).
||
)Opposite to &&
, if ||
is used between two commands, the second command only runs if the first command fails, i.e., the first command exits with a non-zero status. For example,
(1) $ haha || echo ">> Failed <<"
vs.
(2) $ ls || echo ">> Failed <<"
Since haha
is an invalid command but ls
is a valid one, echo ">> Failed <<"
runs in (1), but not (2).
&&
) and OR Operator (||
) together&&
and ||
complement each other. We may achieve similar (but not always the same) outcome as if using if ... then ... else ...
.
For example,
(1) $ haha && echo ">> Success <<" || echo ">> Failed <<"
Since haha
is an invalid command, echo ">> Success <<"
is skipped, but echo ">> Failed <<"
runs.
On the other hand, for
(2) $ ls && echo ">> Success <<" || echo ">> Failed <<"
since ls
runs successfully, echo ">> Success <<"
runs. Then, since echo ">> Success <<"
also runs successfully, echo ">> Failed <<"
is skipped.
[Pitfall] By reordering (1) a bit,
(3) $ haha || echo ">> Failed <<" && echo ">> Success <<"
we see that both &&
and ||
have the same precedence and are evaluated from left to right (see the guide here). Hence, echo ">> Failed <<"
runs because haha
fails, and echo ">> Success <<"
runs because echo ">> Failed <<"
succeeds.
{}
)By putting multiple commands within a {}
, the commands are run (or not run) as a whole. For example, to print both ">> Success <<" and "XD" if the first command succeeds, and ">> Failed <<" otherwise, we should group the first two echo
s with {}
,
$ haha && { echo ">> Success <<"; echo "XD"; } || echo ">> Failed <<"
Without {}
,
$ haha && echo ">> Success <<"; echo "XD" || echo ">> Failed <<"
haha && echo ">> Success <<"
is treated as the first sequence of commands, while echo "XD" || echo ">> Failed <<"
is another. Therefore, echo ">> Success <<"
is skipped when haha
fails, but echo "XD"
runs.
Let's briefly review how to compile a C program using the "hello world" example,
First, create a file named 'hello.c' with the following C source code,
// printf()int
Then, compile the code using gcc
in a terminal under the folder where hello.c
locates by
We first compile the object hello.o
using hello.c
as the input in the first command. Then, we compile the executable hello
using hello.o
as the input in the second command.
Run the program by
$ ./hello
And you will get "hello world!
" printed to the terminal.
Now we remove the executable hello
and hello.o
by $ rm hello hello.o
.
Other than repeating the commands every time to compile our program, we may use make
to automate the compilation. We may directly create a simple Makefile to compile hello
as follows:
hello: hello.ogcc -o hello hello.ohello.o: hello.cgcc -c -o hello.o hello.c
The first and third line are two rules for compilation, in the format of
[target]: [prerequisites]
target refers to the name of program to generate, and prerequisites refers to files required to run the rule. Hence, in this Makefile, hello.o
is required to compile hello
, and hello.c
is required to compile hello.o
.
The second and forth line are the recipes, which tell make
how the targets should be compiled. A recipe may contains multiple lines, and all lines use tabs (\t
) for indentation, instead of spaces.
Then, we can simply compile the program hello
using the Makefile by executing
$ make
$ make
recompiles the program for us whenever any of the prerequisites is updated. Otherwise, it gives the message make: '[target]' is up to date.
.
Let's further refer to the GNU manual for the basic concepts on Makefile writing.
make
processes a Makefilesed
and awk
cowsay
$ sudo apt-get -y install cowsay
sl
(when you make a typo for ls
)$ sudo apt-get -y install sl