Make: A Quick and Easy Guide

Table of Contents

1. Introduction

GNU make is a common unix programming helper that controls the building of targets
from prerequisites. At its most basic, it takes a look at the time-stamp of a target file
and compares that to the timestamp of its prerequisites. Think of targets as completed pieces of
a larger whole, or the whole itself. Think of prerequisites as source files. Any target whose
prerequisites have been changed (they are newer) is regenerated with the build rules you supply.

make can be used for any task where you want to turn sources into completed code.
You can use it to convert many .tex files into a final .ps, or
many .c files into a final object (making all the little .o files in between.)

Imagine you have 30 .c files,
each of which makes a .o, and then you link them into the final ELF executable.
Since make only rebuilds targets whose sources have changed, if you only change one
.c file, only one .o file is regenerated, and is then relinked with the pre-existing
and unchanged .o object files.

2. Basics

When you type make at the command prompt, make looks
in makefile or Makefile in the pwd

for your rules (how targets and prereqs. are related. You can
make a specific target to build:
make target, or even define macros: make EMPREL=emp_2410p4i kernel,
or with just make build the first target in the Makefile.

From the man page:

-f file file use file as a makefile
-n notify show what make would do, but don’t do it
-s silent do not print the commands as they are executed
-o file old file is treated as very old and its rules are ignored
-j # jobs number of jobs (commands) to run simultaneously
-d debug which files are being considered for remaking? …

The basic rule set to make a target from a dep is:

target: sources
________rule(s)/command(s) to turn sources into target (runs in a sh shell)

Don’t put in the underscores, this indicates a tab. If you forget to make this
a tab, and instead have spaces, make will give you very hard to diagnose errors.

2.1. Syntax

Make has a fairly picky language. You must write perfect Makefiles, or it will whine.
every command in a Makefile that make decides needs to be run is run inside it’s
own sub-shell. If you need sequential commands in one shell, you must &&

them together, or use ( ; ; 😉. You can use \ to split long lines.

: separates a target from a dep
= separates a macro name from its value
:= appends to the end of a macro name
+= appends to the end of a macro name
[TAB] is required before any command
# makes the rest of the current line a comment
\ continues this line to the next (kill the \n newline)
- before a command tells make to continue on failure
@ before a command prevents its echo to the terminal
$ token stands for the input file (first dependency)
$^ token stands for the input files (all dependencies)
$@ token stands for the output file (target)
$* token is the input file without its suffix
% glob’s files together like the shell’s *

2.2. Variables (Macros)

Make variables are defined in NAME = value pairs anywhere in the file.
You then use these later as $(NAME).
You can define things to be evaled by the shell with backticks (``).
Or, you can just make shorthand for easier coding later.

NAME = empclone## easily change base file name
EXT = .tgz## gzipped tar
VER = 248-saic## easily change version #

TGZFILE = .$(NAME).v$(VER)$(EXT)## archive file name

DATE = `date +%m%d%y`## current date
UNAME = `uname -r`## running kernel

INS = /usr/bin/install -D -m 644#
MKD = /bin/mkdir -p#
SYSLINUX_FILES = boot.msg vmlinuz.emp
SYSLINUX_FILES := $(SYSLINUX_FILES) ldlinux.sys param.msg\

syslinux.cfg empclone.ird
SYSLINUX_FILES += ldlinux.sys param.msg syslinux.cfg empclone.ird

2.3. Suffix and Pattern Rules

The following suffix rule is built into make, and it says to build
an object file from C source:

gcc -c ${CFLAGS} $

Pattern rules are better, they use the % sign to glob, just
like the shell does with
*. So now we have:

%.o: %.c
gcc -c -o $@ $(CFLAGS) $

Now, any time there is a .o file requested by some other
dependency, make will run off and build it from the .c using:
gcc -c -o source.o ${CFLAGS} source.c

2.4. Functions

If you info make, you will discover a vast amount of good stuff built into make.
There are several of these constructs used in our sample makefiles, but there are a
great many more available. Some examples: $(wildcard, $(shell, $(subst,
$(foreach, $(suffix, and $(basename.

3. Evolution Example

Usually we start with a simple makefile for a simple program and it evolves,
adding increasing complexity as our needs dictate.

Here we just give the full compilation command line. All intermediate objects
must be re-built every time.

foobar: foo.c bar.c foo.h
gcc -o foobar foo.c bar.c foo.h


rm -f foobar

Now, we split the objects out, so if only foo.c is touched,
only foo.o is made.
But we paid a terrible price, in that we typed foo.o4 times!

foobar: foo.o bar.o

gcc -o foobar foo.o bar.o

foo.o: foo.c foo.h
gcc -c foo.c

bar.o: bar.c foo.h
gcc -c bar.c

rm -f foobar foo.o bar.o

We really don’t want to keep typing foo.o bar.o foo.c and bar.c everywhere.
What if we were to split off modules called baz.c and fie.c?
Now we can just add them once to OBJECTS and SOURCES.

OBJECTS:=foo.o bar.o fie.o baz.o
SOURCES:=foo.c bar.c fie.c baz.c

foobar: $(OBJECTS)
$(CC) -o foobar $(OBJECTS)

gcc -c $(SOURCES)

rm -f foobar $(OBJECTS)

Now, what if someone gave us a bum gcc? We don’t want to replace gcc -c 29 times,
so right now before this gets any bigger, we just define CC:=gcc and use it as $(CC).
This will also allow us to issue make CC=kgcc and not even edit the makefile.
On a quad processor machine, we might even say make -j 16 for this small sample.
Be aware that inheritance in recurvive make can make make MAKE=`make -j 16` really a bad idea (its broke).

We also notice that we can obfuscate this a bit, and type less if we replace (in the commands)
$(SOURCES) with $. If we expand $(OBJECTS), we have
a target with multiple files, and each is built seperately using the same
template. Also note that foobar becomes $(FOOBAR)
and then becomes $@, the output file!

CC:=gcc# or kgcc, or g77 ...
OBJECTS:=foo.o bar.o fie.o baz.o
SOURCES:=foo.c bar.c fie.c baz.c

$(CC) -o $@ $

$(CC) -c $



4. EmpClone

Download this
example file (text format).

5. EmpWeb

Download this
example file (text format).

6. Resources

Using the Korn Shell

Table of Contents

1. The Korn Shell

1.1. What It Is

The Korn Shell, or ksh, is the Bourne-compatible shell created by David Korn
of AT&T Research. It offers a very powerful programming/scripting language
as well as a command shell.

  • There are two major versions in use today. The 1988 release (ksh88)
    and the 1993 release (ksh93).
  • Most commercial UNIX vendors ship ksh88 as /bin/ksh.
  • SunOS 5.x include ksh93 as /usr/dt/bin/dtksh. This particular build
    integrates support with CDE.
  • Slackware Linux includes ksh93 as /bin/ksh (starting with the 8.0
  • Red Hat Linux includes the Public Domain Korn Shell as /bin/ksh. This
    is not a 100% ksh88 or ksh93 compatible shell.

1.2. Features

Command Shell Programming Language
  • vi or Emacs command editing modes
  • History file has configurable size and is shared across all shell
  • Process substitution
  • Self generating documentation for shell builtins (text, HTML, or
  • Command name completion in both editing modes.
  • Job control – ksh allows for managing multiple jobs at the same
  • cd command – change to similarly named directories or completely
    rewrite cd with your own shell function.
  • 100% Bourne shell compatible.
  • Open multiple files at the same time.
  • Keystroke and debugging traps builtin.
  • Menu primitive (resizes according to the terminal size).
  • Associative and indexed array support.
  • Discipline functions
  • Builtin integer arithmetic as well as access to math.h functions.
  • Substring operators and recursive function support.
  • Co-process facility

1.3. So Why Use It?

GNU bash, zsh, tcsh, and other open source shells offer great alternatives to
ksh. But there are some reasons you may want to run ksh over another shell.

  • Guaranteed compatibility with future releases.
  • ksh is common on commercial UNIX systems.
  • Same across all platforms it supports.
  • Includes some language features not found in other shells.

2. Korn Shell Tips

The following examples are to be executed from a ksh command prompt.

2.1. Show the version of ksh you’re currently running.

print ${.sh.version}

This works in ksh93 only.

what /bin/ksh | grep Version

This works only on systems with the what command.

2.2. Compile ksh.

Download ast-base, ast-base-locale, and INIT.

$ cd /tmp
$ mkdir -p ast/lib/package/tgz
$ cp ast-base* ast-base-locale* INIT* ast/lib/package/tgz
$ cd ast
$ gzip -dc lib/package/tgz/INIT* | tar -xvf -
$ bin/package read
$ bin/package make

2.3. View current option settings.

(This command is also used to
modify options, see the ksh man page for descriptions.)

set -o

2.4. Fix backspace.

stty erase ^h

2.5. Recall previous commands.

history [ -{num} | {num} {num} | {num} ]

That’s limit list to num, display from num to num, and show
commands starting from num.

r [ {num} | {string cmd starts with} ]

Recall a previous command by its history number or by
typing in the first few characters of its name.

2.6. Command completion — Emacs mode

[esc] [=]

Display list of pathnames that result from expanding the
word under the cursor.

[esc] [esc]

Append characters to the word under the cursor to
complete the pathname of an existing file.

[esc] [*]

Replace words under the cursor with the list of pathnames
that result from expanding the word.

2.7. Command completion — vi mode


Display list of pathnames that result from expanding the
word under the cursor.


Append characters to the word under the cursor to
complete the pathname of an existing file.


Replace words under the cursor with the list of pathnames
that result from expanding the word.

2.8. csh-style directory stack.

Download the code here.

2.9. Documentation.

All ksh builtins have self-generated text, HTML, and troff
documentation. Just type –help, –man, or –html for the appropriate

2.10. Easily set the umask.

umask -S =rx,u+w

2.11. Make a bash-style PS1 prompt, like \u@\h:\w\\$, the ksh way.

export PS1='${USER}@$(hostname):${PWD/#$HOME/~}\$ '

2.12. You can trap keystrokes in your shell scripts.

typeset -A Keytable
trap 'eval "${Keytable[${.sh.edchar}]}"' KEYBD

2.13. Quickly change to /usr/openwin/bin from

$ pwd
$ cd local openwin
$ pwd

2.14. Create, modify, and get information on variables and

typeset --help | more

2.13. Make a menu in your shell script.

PS3='Pick one of the above: '
select i in list edit quit

case $i in
list) cat "$foo" ;;
edit) ${EDITOR-vi} "$foo" ;;
quit) break ;;

"") print -u2 you must select one of the above ;;

2.14. Autoload your collection of functions each time you start ksh.

autoload foobar

2.15. Process substitution (only works on systems that use /dev/fd).

paste (cut -f1 file1) (cut -f3 file2) | tee (pro1) (pro2)

Understanding and Using SSH

Table of Contents

1. How SSH Works

1.1. How SSH negotiates an encrypted session

The server has a 1024bit public/private host key pair that remains
constant, as well as a 768bit public/private server key pair that changes

When a client connects, the server sends its public host and server keys
to it. To determine if the host is legitimate, the client keeps a cache of
the host keys of all the servers it has ever connected to. If the public
key sent by the server is indeed the same as the one in the cache, all is
good. If not, the client is warned: someone may be trying to intercept the

The client then generates a random 256-bit key, which it encrypts with
both of the public keys sent from the server, and sends back this
encrypted key. The server unencrypts the 256-bit key with its own private
host key. Now both sides have a common key unknown to anyone else, which
will be used to encrypt the rest of the traffic in the ssh session, with a
cipher such as Blowfish or 3DES.

1.2. Password authentication

Once the encrypted session has begun as described above, the password can
be sent from the client to the server with no worry that it will be
sniffed; it is never sent in plaintext since the session is already

The advantage of password authentication is that it is very secure, prone
only to keylogging. Because of this weakness, however, you should try not
to type any passwords on systems where root may be running a keylogger.
This, of course, is a precaution that applies much more broadly than to
just ssh.

1.3. RSA key authentication

A more secure method of authentication is through the use of RSA keys. The
basic principle is as follows. Each user generates a 1024bit
public/private key pair for himself. Generally, each user has a different
key for every system he is on, but frequently, if he has multiple trusted
hosts, it can be more convenient to use the same key on all the systems to
simplify things.

Any host to which the user wants to connect must be aware of his public
RSA key, as the server uses it during the authentication process. The user
must place his public key living on the originating client machine, into
his own authorized_keys file on the server.

When he wants to connect to that server, ssh will first negotiate an
encrypted session, then send the server the client’s public key. The
server checks that the public key is in the user’s authorized_keys. If so,
the server sends the client a challenge (a random number encrypted with
the user’s public key). If the client can then send back the random number
unencrypted, it has just proven that it has the private key (there is no
other way to unencrypt the challenge number), and is therefore authentic.

The user’s private key is a very sensitive piece of data – with it,
anyone can connect to any host on which the corresponding public key is in
the authorized_keys. Therefore, the user’s private key is never written to
disk unencrypted.

The private key is encrypted using a passphrase made up by the user. The
passphrase should generally be around 20 characters in length to prevent
brute force cracking attempts against it. With a safe passphrase, there is
little risk in exposing the private key.

The advantage of RSA based authentication is that a password is never sent
across the line, even in encrypted form. Additionally, you have to
remember only one passphrase rather than a password for each system you
log into. However, because of the neccessity for a long passphrase on a
private key, it is becomes very inconvenient to type it every time you
connect to another host.

1.4. Authentication agents

To ease the use of RSA keys, one may use an authentication agent such as
ssh-agent. ssh-agent is simply a daemon that caches a user’s unencrypted
private key(s), to save the user from having to type his passphrase every
time he wants to connect to another host.

Every key that the user wishes to be cached must be added to the agent’s
cache by running ssh-add and typing in the passphrase for the key being
added. Multiple keys may be added to a single agent. The agent will
automatically determine which key (if any) can be used to authenticate you
on a host.

When run, ssh-agent creates a named socket file under /tmp, whose
permissions are set to allow only you to read from and write to it. It
places the name of this file into an exported environment variable,
$SSH_AUTH_SOCK. ssh client processes that have this agent running as an
ancestor process will have this variable available to them to inform them
how to communicate with the agent. Communication with the agent is done
by reading from and writing to the named socket in $SSH_AUTH_SOCK.

Once the ssh client has contacted the running ssh-agent, it attempts to
authenticate with the server in the same method as mentioned before.
However, the user does not have to type the passphrase for the private
key, as it is already available from the agent in its unencrypted form.

Because the user’s private keys are stored unencrypted in the memory of
the client machine when using ssh-agent, care should be taken that keys
are not added to ssh-agent on systems where root is untrusted, as root has
access to shared memory, and can steal the user’s unencrypted key and
access any hosts to which the added keys grant access.

A drawback of using RSA authentication is that once a key is used to log
into a remote host, they key is no longer available for further
authentication, as it is not available anywhere in the host machine’s
filesystem or memory. To circumvent this problem, an arguably unsafe trick
called agent forwarding is used.

1.5. Agent forwarding

Ssh has the ability to "forward" the ssh-agent it is using, in order to
make it available to the user after he has connected to the remote
machine. Say You have a key on system A that authenticates you on hosts B
and C. The key is not available to you on host B though. With forwarding,
you can ssh into host B and still have a pseudo-agent available to you on
B, which forwards requests back to the real agent running on A. When you
connect to B, the $SSH_AUTH_SOCK is set to point to this pseudo-agent, and
is exported in your environment on B. Now you can authenticate yourself on
C from B without typing a single password or passphrase.

Because agent forwarding provides access to the agent running on system A,
it is now possible for root on system B to connect to the agent on A by
way of the pseudo-agent created for forwarding, and authenticate himself
with the user’s keys. He can then log into to any system allowing one of
the user’s added keys.

Therefore, agent forwarding should never be used when connecting to a
system on which root is untrusted. If the user must connect to multiple
untrusted systems, he should disconnect from one before connecting to
another – forwarded agents can be hijacked and passwords can be

1.6. A Safe Solution

  • Do not forward your agent to untrusted hosts. If you must go through an
    untrusted host B to get to host C, you must then
    trust root on B with access to C, period.
  • If you’re not sure what to do, use password authentication. It requires
    typing a password, but is safer: If someone sniffs your password, they
    only have access to the one system to which you’re connecting, rather
    than all the systems on which your key is authorized. (You are

    using different passwords on each system, right?)

2. Using Secure Shell

2.1. Using the client

To connect to a remote system using ssh, invoke ssh with the following

ssh [user@]host [command]

If your username on the remote system is the same as on the local system,
you may omit the user@, and your local username will be used.

If you would like to execute a command on the remote system and
immediately disconnect (rather than executing a shell and entering an
interactive session), you can specify the command to run after the
hostname specification. Remember that the program may not be in the search
path on the remote system, so it is always a good idea to give the full
path to the program if you’re not sure.

So for example, if I wanted to ssh to and list the
contents of /bin, it would look something like this:

[jehsom@jolt ~]$ /usr/bin/ssh ls \~/bin's password:
[jehsom@jolt ~]$

Notice that I escaped the ~ in ~/bin. Otherwise the local shell would have
expanded it to /home/jehsom, which doesn’t exist on acme. The way I prefer
to pass a command to ssh is to pass the whole thing in quotes:

/usr/bin/ssh "ls -l ~/bin"

Special shell characters such as ~ (as well as many others) will be passed
straight through to the remote system and handled by the remote shell.
This is what you want.

2.2. X11 Forwarding

One of the best features of ssh is its automatic X forwarding. If you have
properly set your DISPLAY environment variable on the local machine (this
will be the case if you’re running in a terminal window under X), then ssh
will automatically negotiate the forwarding of the display to the remote
server. This means that when you connect to the remote machine, you may
start graphical X programs, and have them display their interfaces on the
local X server. All X11 traffic is encrypted, of course, which is an added
benefit to the usual method of using xhost and exporting your DISPLAY.

2.3. SSH Client Configuration Options

Your ~/.ssh/config file contains all the client functionality options.
You can restrict settings to specific hosts by preceeding them with a
"Host hostname" line. Put all your global options first, because every
line following a Host declaration will apply only to that host.

Some interesting options (again, ripped from man ssh(1)) are:

  • Cipher – Specifies the cipher to use for encrypting the session
    in protocol version 1. Currently, "blowfish" and "3des" are
    supported. The default is "3des".
  • EscapeChar – Sets the escape character (default: ‘~’). The escape character
    can also be set on the command line. The argument should be a
    single character, ‘^’ followed by a letter, or "none" to disable
    the escape character entirely (making the connection transparent
    for binary data).
  • ForwardAgent – Specifies whether the connection to the authentication agent (if
    any) will be forwarded to the remote machine. The argument must
    be "yes" or "no". The default is "no".
  • ForwardX11 – Specifies whether X11 connections will be automatically redirected
    over the secure channel and DISPLAY set. The argument must be
    "yes" or "no". The default is "no".
  • Protocol – Specifies the protocol versions ssh should support in order of
    preference. The possible values are "1" and "2". Multiple
    versions must be comma-separated. The default is "1,2". This
    means that ssh tries version 1 and falls back to version 2 if
    version 1 is not available.
  • User – Specifies the user to log in as. This can be useful if you have
    a different user name on different machines. This saves the
    trouble of having to remember to give the user name on the command
    line. Usually this is used on a host-specific basis.

2.4. Using RSA Authentication

I’ll try to break this up into several steps.

  1. Generate your own personal RSA key pair on the client machine using the
    ‘ssh-keygen’ command with no parameters. It will prompt you for a
    passphrase which you will type whenever you use this key. The new
    keyfiles are placed ~/.ssh.
  2. For security reasons, you MUST chmod -R go-rwx ~/.ssh, or you will not
    be able to connect to this host using RSA keys. This is because ssh
    sees a potential security breach and disallows access using the keys.
  3. ssh into your account on the server machine (you will obviously still
    be using password authentication at this point). Make the ~/.ssh
    directory and chmod it as above.
  4. cat your ~/.ssh/ on the client machine. You will see a very
    long line that should wrap when it gets to the edge of the screen. Copy
    and paste this line into the ~/.ssh/authorized_keys file on the remote
    system. This is one of perhaps many keys that you may put in the remote
    authorized_keys file. Make sure you chmod ug-rwx authorized_keys.
  5. Now when you ssh from the client to the server, you will be prompted
    for the passphrase for your new RSA key. Type it in and you will be
    automatically logged into the remote system. If you mistype, you will
    be prompted for the regular password.

2.5. Using ssh-agent to simplify things

Since it is a bit tedious to type your passphrase every time you wish to
use your key, you can use ssh-agent to hold your keys for you, and provide
them to ssh whenever you need to log into a remote system.

If you normally log into your system on a text console and then start X,
you’ll want to put the following in your login script (~/.bash_profile if
you are using bash):

[ -z "$SSH_AUTH_SOCK" ] && eval `ssh-agent`

This way, if you have no agent running when you log in, ssh-agent will be
run and you will be prompted for your key’s passphrase. Now any
subprocesses will inherit the connection to the agent. You can start X,
and any shell sessions you start will have the agent available to them.

If you normally log into your system via a graphical console, place the
following lines at the top of your ~/.xinitrc:

eval `ssh-agent`
ssh-askpass &-
# Now execute your window manager with one of these commands:

Now when you log in, you will be graphically prompted to enter your ssh
passphrase before your window manager starts. All shell sessions you
create will now have the $SSH_AUTH_SOCK variable set, and ssh will use the
agent to authorize you.

2.6. SCP – Secure Copy

A very handy extension to ssh is scp. It allows you to copy files between
systems quickly and securely, while keeping a simple interface.

You will most commonly use scp in one of the following two ways:

scp [-r] [user@]host:[path] path
scp [-r] path [user@]host:[path]

The first copies a remote file or directory to the local machine at the
given path location. The second copies from local to remote.

If the user@ is not given, the local username will be assumed on the
remote side. You must have a colon after the hostname, though. If it is
omitted, you will create a file called user@host in your current
directory. Whoops!

The remote path specification is relative to your home directory. You can
begin the path with a ‘/’ to make it absolute, though. If it is omitted,
the files are copied to or from your home directory.

If the first path spec is a directory, you’ll probably want to use the -r
option, which will send the entire directory tree, recursively. If it is a
file, the -r is unneccessary. Only one file will be copied.

NOTE: If the first path spec is a directory and you’re using -r, DO NOT
follow your path spec with a trailing ‘/’, or else the files inside the
directory will be copied straight into the destination path, instead of
making a path and placing the files within. For example:

scp ~/bin acme:

will copy the entire ~/bin directory tree to acme, and place it in my home
directory. I will end up with a ~/bin/ path on acme that looks exactly
like the one here. However,

scp -r ~/bin/ acme:

will recursively copy all files and directories in ~/bin into my home
directory on acme. So my home directory there will look like my bin
directory here.

2.7. Starting up the server

On SysV-ish systems (Redhat, Mandrake, etc), you can usually start the ssh
server by running the following command as root:

/etc/rc.d/init.d/sshd start

If your host keys have not been generated yet, it should automatically
take care of that. If the server does not start up automatically on
bootup, your distribution should have a tool to "turn it on". On Redhat
and Mandrake, it is chkconfig:

/sbin/chkconfig --level 35 sshd on

On BSD-ish systems (Slackware, most unixes, *BSD, etc), there is a little
more complexity involved. Let’s assume you compiled and installed ssh
under /usr/local (If not, just strip off the /usr/local from my examples).
You will first have to generate your host keys:

# Generate the ssh1 key
ssh-keygen -f /usr/local/etc/ssh/ssh_host_key -N ""

# Generate the ssh2 keys
ssh-keygen -f /usr/local/etc/ssh/ssh_host_dsa_key -N "" -t dsa
ssh-keygen -f /usr/local/etc/ssh/ssh_host_rsa_key -N "" -t rsa

Now you may start the server:


You may want to add that line to your startup scripts so sshd will start
when your system starts up.

2.8. Server Configuration

The server configuration file has a lot of interesting settings. Edit the
file, usually /etc/ssh/sshd_config. You will probably be interested only
in the following options (descriptions brutally ripped from the sshd(1)

Specifies whether root can login using ssh(1). The argument must
be "yes", "without-password", "forced-commands-only" or
"no". The default is "yes".

If this option is set to "no" root is not allowed to login.

Specifies whether password authentication is allowed.
The default is "yes". Note that this option applies to both protocol
versions 1 and 2.
Specifies whether pure RSA authentication is allowed. The
default is "yes". Note that this option applies to protocol
version 1 only.
Specifies whether X11 forwarding is permitted. The default is
"no". Note that disabling X11 forwarding does not improve
security in any way, as users can always install their own

Remember to killall -HUP sshd after making changes, so they will take