Working with the shell

The purpose of this tutorial is to get you familiarized and comfortable with the command line. For this reason you should avoid using a file manager or similar program during this tutorial, and stick to the text terminal exclusively.

Launch a terminal emulator window (why is it called a terminal “emulator”?) or switch to the text console [Ctrl-Alt-F1] (Linux only; use Alt-F7 or Ctrl-Alt-F7 to get back to the graphics sytem).

Shell prompt and command editing

Type some stuff and press Enter. See what happens.
Note how you can retrieve the previous command(s) using the arrow-up key.
Note what happens if you press Tab.
(What is the name of the program reading and interpreting your input?)

Filesystem

Most operating systems these days (including Linux) implement a hierarchical file system (what does this mean?).

Use pwd to find your place in the filesystem. Type ls to see what’s there.
Use cd .. to go one level upward, and cd name to enter a directory called name (substitute directory names you find in your filesystem for name).
See if you can find any interesting files or directories this way.
What does cd .. actually mean? (What does cd . mean?)
Type ls -al and see if you can figure it out.
What do all the items in the output mean? What does -al mean?
Type man ls and find out. (What does man do?)

If your output is very long and you want to view it one page at a time, you can “pipe” the output into a “pager” such as more or less:

First, try ls -alF /usr/bin.
Compare this to ls -alF /usr/bin | less.

In fact, many command-line utilities can be strung together into “pipelines” where the output of one program is the input to another program.
For example, to see all the shells running on the system except ssh:

ps ax | grep '[ /][a-z]*sh\( \|$\)' | grep -v ssh
(What does ps do? What about grep?)

Note the quotes! See the difference:

ps ax | grep '.*'
ps ax | grep .* What is actually happening in this case?

The quotes prevent the pattern (intended for grep) from being “expanded” by the shell (the shell looks at all existing files and replaces the pattern with a list of those filenames that match the pattern).

Read all about quoting in the bash manual page. (What is the command for displaying the bash manual page?)

Working with files

Go back to your home directory using cd (without arguments).
Create a working directory for this tutorial with mkdir tutorial.
Change into this directory.

Create (empty) files HD_66811, SN_1994D, He2-131, M_31, NGC_221, He2-108, NGC_224, HD_30614, NGC_6543, HD_93129A, SN_1987A, and NGC_XXX.
(How do you create a file?)

Rename NGC_XXX to NGC_104 using the program mv.
Create directories stars, galaxies, nebulae, supernovae, and globular_clusters.
Use mv to move the files into the corresponding directories.
What happens if you make a mistake?
Can you move more than one file at a time? How?

Remove NGC_224 (because it is actually the same object as M 31) using the program rm. Can you do this without first changing into the directory the file is in?
Then create a recursive listing of the directory and save it in a file my_objects.
(How do you save a program’s output in a file instead of having it printed to the terminal?)

Output redirection:

ls -R > my_objects

This is like a pipe, except that the destination is a file and not the input of another program.
(See the manual page of tee for a program that acts like a “pipe fitting”.)

By default, programs actually have two outputs, one for “normal” output (“stdout”) and one for error messages and diagnostics (“stderr”). Both can be redirected:

command >file (output into file, errors to terminal)
command >>file (output appended to file, errors to terminal)
command >file 2>&1 (output into file, errors into the same file)
command >file1 2>file2 (output into file, errors into different file)

> alone is the same as 1>:

command 1>file1 2>file2 (output into file, errors into different file)

Example for redirecting stdout and stderr (note that, for demonstration purposes, we’re deliberately trying to list a file that does not exist):

touch a b c
ls a b c d >x
cat x
ls a b c d >x 2>y
cat x
cat y
ls a b c d >z 2>&1
cat z

Working with many files

Try the command seq -w 100.

(MacOS users: try jot if you don’t have seq. See manpage for options.)

Now create a sequence of files with: touch `seq -w 100` (backquotes, not normal quotes!!!) or touch $(seq -w 100).

This useful feature is known as “command substitution”.

Note: there is a difference between

mydir=`pwd`; echo $mydir (backquotes, command substitution)

and

mydir='pwd'; echo $mydir (normal quotes, stuff taken literally)

Rename 001 to exp_001.fits, 002 to exp_002.fits, etc.
Is there an easy way to accomplish this?
Why doesn’t mv * exp_*.fits work? How many arguments does mv get?
How does mv interpret its arguments when there are more than two?
(Discuss: filename “globbing”.)

Answer to “easy way”: two “obvious” methods:

  1. a text file (“shell script”) created with the help of the “search & replace” functions of your text editor, or
  2. a “for” loop in the shell.

Using a (trivial) shell script:

Create a text file rename_my_files containing mv 001 exp_001.fits mv 002 exp_002.fits mv 003 exp_003.fits (etc.) Then let a shell execute these commands with bash rename_my_files.

Using a “for” loop:

for f in [0-9][0-9][0-9]; do mv $f exp_$f.fits; done

(Note pattern for the shell to match the existing filenames against.)

Now rename all exp_*.fits files to Wendelstein-*.jpg (assume that we made a mistake, and the files aren’t actually FITS files but rather JPEG files).

One possible way:

for f in exp_*.fits; do mv $f `echo $f | sed -e 's/exp_\(.*\)\.fits/Wendelstein-\1.jpg/'`; done

or

for f in exp_*.fits; do mv $f $(echo $f | sed -e 's/exp_\(.*\)\.fits/Wendelstein-\1.jpg/'); done

Note the use of command substitution.

To understand how this works, experiment a bit with these commands:

echo exp_hallo.fits | sed -e 's/exp_\(.*\)\.fits/Wendelstein-\1.jpg/'
echo abc_xyz | sed -e 's/\(.*\)_\(.*\)/\2_\1/'
echo abc_xyz | sed -e 's=\(.*\)_\(.*\)=\2_\1='

Other useful utilities for working with filenames are basename and dirname:

basename filename [extension]
dirname filename

Example:

basename /etc/passwd
basename /etc/passwd wd
dirname /etc/passwd

Shell programming

What makes the shell so useful is that it is both a user interface for interactive work as well as a programming language you can use to automatize complex tasks.
For this, it provides the usual control structures:

for name in list; do command1; command2; command3; done
if command0; then command1; command2; command3; fi
while command0; do command1; command2; command3; done

Examples:

var1=foo
var2=bar
if test "$var1" = "$var2"; then echo "Yes!"; else echo "No!"; fi
if test -f myfile; then echo "myfile exists"; fi
i=77
if test $i -gt 10; then echo "too large"; fi
while true; do date; sleep 1; done

(The last one is an endless loop. Terminate this with Ctrl-C.)

You can also write these with newlines (this looks nicer in shell scripts):

if command0 then command1 command2 command3 else command4 command5 fi

Modern shells can also handle (integer) numerical expressions using $(( )):

echo $((1+5))
for i in {1..10}; do j=$((i+5)); echo "$i -> $j"; done

Older shells that don’t understand arithmetic expressions can use the program expr:

for i in `seq 1 10`; do j=`expr $i + 5`; echo "$i -> $j"; done

If you also need fractional numbers, use a utility such as dc:

for i in `seq 100`; do j=`dc -e "50k $i v100+p"`; echo "$i -> $j"; done
(Can you figure out what this does?)