Bash Lab                
Introduction                
For this lab, you will take a small bash script called rn
,
modify it to do more things, and turn it in for credit.
A video introduction is available.
                
A script                
A bash script (or more generally, a shell script) is
a series of bash commands in a file. You create the file using
an editor, such as vim or nano, and then you execute (or run) the
script, as if it were a program, because it is one.
                
Really, that’s all there is to it. Anything that you could type into an
interactive bash shell (cd, ls, pwd, cp, mkdir, rm,
make, vim, etc.), you can put into a bash script.
                
Also, bash scripts often contain other constructs more often found in
programming languages, such as if
, while
, and for
statements, and variable usage. You could use such things
interactively, but you rarely do.
                
Basic bash programming                
- Start with
#! /bin/bash
This tells the system that this is a bash script,
and not some other sort of program, such as a Python program
or machine language.
- Comments:
# Good morning!
#
until the end of the line is documentation, ignored by bash,
like //
in C++.
- Special variables:
- Name of script:
$0
- Arguments:
$1
, $2
, $3
, …
- Argument count:
$#
- Result of last command:
$?
(0 for success, >0 for failure)
- Use these variables in your script to make decisions, and to
access the arguments given by the user when they ran the script.
if (( $# == 0 ))
then
echo "I have no arguments. ☹"
fi
I have no arguments. ☹
- Quote special characters to prevent them from acting specially:
echo Wildcard: /etc/host*
echo "Quotes protect it: /etc/host*"
Wildcard: /etc/host.conf /etc/hostname /etc/hosts
Quotes protect it: /etc/host*
- Variables are evaluated inside double quotes:
echo "I am $USER"
I am cs253
- Variables are not evaluated inside single quotes:
echo 'Cost: $0.24'
Cost: $0.24
- Use
$
to get the value of a variable; not when assigning it:
alpha="foo bar"
echo "I say $alpha"
I say foo bar
- Capture command output with
$(
…)
:
now=$(date)
echo "Current time: $now"
Current time: Fri Nov 22 02:10:59 MST 2024
- Numeric variables with
let
:
let beta=2+2
echo "beta is $beta"
beta is 4
if condition
then
statements
fi
if [[ -e /etc/hostname ]] # Does the file exist?
then
cat /etc/hostname # Let’s see it!
fi
beethoven
- A more complex conditional:
if condition
then
statements
else
statements
fi
if condition
then
statements
elif different-condition
then
statements
else
statements
fi
while condition
do
statements
done
for variable in list
do
statements
done
- The list can be an explicit list:
for person in John Paul George Ringo
do
echo $person was a Beatle.
done
John was a Beatle.
Paul was a Beatle.
George was a Beatle.
Ringo was a Beatle.
- The list can be a filename pattern:
for file in /etc/*-release
do
echo -n "Lines in $file: "
wc -l <$file
done
Lines in /etc/almalinux-release: 1
Lines in /etc/centos-release: 1
Lines in /etc/os-release: 19
Lines in /etc/redhat-release: 1
Lines in /etc/system-release: 1
((
arithmetic condition ))
, like C++:
let age=2024-1957 money=20
if (( age >= 21 && money > 4 ))
then
echo "You can buy a beer!"
fi
You can buy a beer!
while [[ $suffix = "txt" ]]
do
somehow change the suffix
done
ls -l /bin/sync /etc/hostname /etc/shadow
# Is this an executable file?
if [[ -x /bin/sync ]]
then
echo /bin/sync is executable
fi
if [[ -e /etc/hostname ]]
then
echo /etc/hostname exists
fi
if [[ -r /etc/shadow ]]
then
echo /etc/shadow is readable
fi
-rwxr-xr-x 1 root root 38328 Apr 1 2023 /bin/sync
-rw-r--r--. 1 root root 10 Jan 5 2022 /etc/hostname
---------- 1 root root 1880 Nov 30 2022 /etc/shadow
/bin/sync is executable
/etc/hostname exists
- Any command that succeeds or fails:
if grep -x -q selfie /usr/share/dict/words
then
echo Dictionary is modern!
else
echo Stupid stone-age dictionary.
fi
Stupid stone-age dictionary.
- Spaces matter:
- Good:
if [[ $suffix = "txt" ]]
- Bad:
if[[$suffix="txt"]] # 🦡 Need spaces around words and operators.
- Line breaks matter:
then
, else
, elif
, fi
, do
,
and done
must each be on their own line.
- Routing output:
|
connects output of first command to input of second command:
grep "foo.*bar" filename | sort
>
sends standard output (non-error output) to a file:
grep "^foo" filename >results
>&2
sends standard output to standard error:
echo "$0: Don’t do that!" >&2
- Termination:
exit
(or falling off the end) terminates the script.
exit 0 # stop the script; indicate success
exit 1 # stop the script; indicate failure
- Obsolete syntax:
There are several old-fashioned constructs, useful only for bash
scripts that will work on computers from the previous century. For
this assignment, you may not use:
- the expr command
- the test command
- the single-bracket
[
… ]
conditional syntax.
Script                
Here is a small bash script. It changes file suffixes:
                
#! /bin/bash
old_suffix=$1
new_suffix=$2
for f in *.$old_suffix
do
new_name=${f%.*}.$new_suffix
echo Rename $f to $new_name
done
You might use it like this, to rename all of your C files
(ending in .c
) to be C++ files (ending in .cpp
):
                
chmod +x rn
./rn c cpp
The chmod command makes the script executable, so we can treat
it as a program. You only need to do that once.
                
The leading ./
in ./rn
means that we want to execute the file
rn
that’s in the current directory, not some other file named rn
somewhere else. For example, if you had a script called date
,
and foolishly just typed date
, it might execute the official system
command named date. You need ./date
to make sure which date
you’re running.
                
Copy the script, above, to the file rn
. No suffix—just the
two-character filename rn
with nothing after. You will modify
rn
, and turn it in for credit.
                
For this lab, you should:
                
- Understand how the script works.
You don’t have to write any of these down, you just have to do them.
All that you turn in is the modified file
rn
.
- Understand why we type
./rn
to run the script, as opposed to just
typing rn
.
- Copy & paste what I gave you into your own file
rn
, and try it out.
Make sure that the first two characters in the script are #!
,
with nothing before them.
- Make it actually rename the file, rather than just saying so.
- Add a usage message if too many or too few arguments are given.
A usage message is a message that tells the user what arguments
to give to this script, if an improper number of arguments (including none)
are given.
- Send the usage message to standard error.
- Complain (to standard error) and stop the script if a file can’t be renamed.
- Not clobber existing files. That is, if we run the script like this:
./rn foo bar
- and both
alpha.foo
and alpha.bar
exist, the script should complain and refuse to do anything else.
For extra fame & glory (but no extra points), you could:
                
- Add a
-n
option that doesn’t rename,
but just says what it would do
- Add a
-v
option that renames verbosely
- Complain if no files have the given suffix
- Make it work for filenames and suffixes that contain spaces
- Make it work for suffixes that are wildcards (whatever that means)
How to submit your work:                
In Canvas, check in the
file
rn
to the assignment “Lab02”.
It’s due 11:59ᴘᴍ MT Saturday, with a five-day late period.
                
How to receive negative points:                
Turn in someone else’s work.