|
|
(21 intermediate revisions by 4 users not shown) |
Line 1: |
Line 1: |
− | == Class Description ==
| + | [[Intro to bash scripting old|old content]] has moved here. |
| | | |
− | The class: a six week course on Tues evenings covering basic and intermediate
| + | = Class Description = |
− | scripting in the bash shell. Each night, we will create a few scripts of about
| |
− | 15 lines of code, modify the code, run it, and talk about the results. We will
| |
− | also get to know the "Advanced Bash Scripting Guide" [1] and the Gnu "Bash
| |
− | Reference Manual" [2] and learn to research and solve our own programming
| |
− | problems. User projects are encouraged -- bring your problems and we will solve
| |
− | them together!
| |
| | | |
− | I am a fifth year Ph D student from Berkeley in Demography, writing an
| + | A six-week course on Saturdays covering basic and intermediate scripting in Bash! This course will teach you all you need to know to begin writing your own scripts. You can use these to automate system administration tasks and write your own custom commands. Scripting will give you use of the full power and flexibility of Bash. The course aims to teach a strong grasp of the basic concepts through practical examples. We will teach these one at a time, with back up exercises and notes for use outside the classroom. |
− | anthropology of a small lumber town in Oregon. I have worked as a
| |
− | programmer in Linux for almost 10 years, and I am currently employed
| |
− | part time as a software project manager at Portland State.
| |
| | | |
− | The first class: Feb 17, 5:00 to 6:30, 2009.
| + | Dates and Time: Saturdays, 1:00 - 3:00pm |
| | | |
− | Some helpful links:
| + | May 15, 22, (no class Memorial Day weekend, May 29) |
| + | June 5, 12, 19, 26 |
| | | |
− | http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html
| + | Location: Free Geek Classroom |
| | | |
− | http://tldp.org/LDP/abs/html/
| + | Cost: $60 for entire series; $30 for Free Geek volunteers! |
| | | |
− | http://www.gnu.org/software/bash/manual/bashref.html
| + | To register: |
| + | Send an e-mail to: Laurel (laurel@freegeek.org) and to instructor Paul Munday (paul.munday@gmail.com) |
| | | |
− | http://www.intuitive.com/wicked/wicked-cool-shell-script-library.shtml
| + | In your email, please include the following: |
| + | * Why you want to take the class |
| + | * Other classes you have taken at Free Geek |
| + | * Your computer experience |
| | | |
− | == Class Outline ==
| + | '''Please note: The class is a sequence. Please don't register if you don't think you can attend all 6 classes. There will be more chances to learn Bash at Free Geek -- promise!''' |
| | | |
− | '''Day Zero:''' Introductions and adminitrivia. Command line refresher. How to find your way round the filesystem at a terminal. How to use the text editor [http://www.nano-editor.org/ nano]. How to use version control with [http://svnbook.red-bean.com/ subversion]. Collaborating with multiple people on version control.
| + | == Class Tutor == |
| | | |
− | '''Day One:''' What is a "script" and what is a "variable". We will write a simple script in nano, with comments, a "shebang" line, appropriate permissions, and simple output. We work on the idea of a variable, using shell expansion to assign output to variables, interpolating variables, and exporting environment variables. We will also examine the output and input streams ("stdin", "stdout", "stderr"). We will "comment-out" code. Finally we will talk about style, including indentation, variable names, trickiness, and comment-first scripting. | + | Paul Munday. I've worked as a Linux sysadmin for 12 years as well as an IT trainer focusing on FLOSS. |
| | | |
− | '''Day Two:''' "For-loops", "word splitting", and printf. We will explore the for-loop in all its glory, going over lists stored in variables. This will require a discussion of how Bash automatically splits strings into words and how we can control this through quoting syntax. We will also make interesting formatting commands with the printf command.
| + | == Class Requirements == |
| | | |
− | '''Day Three:''' Conditionals ("if/ then" statements). We will show how to write "if" and "case" statements, and incorporate pattern matching and "file tests" into our scripts.
| + | No previous programming experience is necessary but you will need to be familiar with use of the command line/terminal (bash shell). If you have taken the basic command line for builders class this should be sufficient. |
| | | |
− | '''Day Four:''' File input, "while-read", and useful scripting. We will investigate how to get an input line from a file, parse it into useable pieces, and do interesting things with that data (like write a simple email "spambot").
| + | = Class Outline = |
| | | |
− | '''Day Five:''' SQL databases and the command line. We will use [http://www.sqlite.org SQLite] to store email addresses, query the table, and use the results to send spam to our friends. | + | * '''Lesson 1: First Steps & Variables'''. The first lesson covers a basic introduction to scripting: what it is, and how to write one; good scripting style and how to comment your scripts; data & strings; variables --- a first building block for scripts [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_1 Class Notes] |
− | | + | * '''Lesson 2: Math with Bash & Tests.''' The second lesson covers doing basic math with bash and introduces control structures, specifically tests (a.k.a conditionals). [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_2 Class Notes] |
− | == Class Approach ==
| + | * '''Lesson 3 : Loops.''' More on control structures, specifically loops and how to build basic menus. [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_3 Class Notes] [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_3_exercises Sample Exercises] [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_3_answers Answers] |
− | | + | * ''Lesson 4 : Arrays and Functions'' Lesson four covers arrays, a data structure with multiple elements and functions --- a useful way to reuse code and keep it organized. [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_4 Class Notes] [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_4_exercises Sample Exercises] |
− | The class is based on each student learning by doing -- writing and running code. Each class period I will have posted code on the wiki. You will "checkout" the class code repository. Inside this repository, you will type examples from this wiki page, with modifications to make your own scripts, run them from the command line, and fix bugs. At the end of the class, you will "commit" your new scripts for looking at later. | + | * '''Lesson 5: Regular Expressions, Sed and Awk.''' Regular expressions are a handy way to match text strings, combined with sed and awk they provide a powerful way to search and edit large amounts of data. [http://www.gatheringstorms.org/wiki/doku.php?id=bash_course:lesson_5 Class Notes] |
− | | + | * '''Lesson 6: Putting it all together.''' Lesson 6 will walk through the construction of a functional and useful script, step by step, so you can learn how to put them together and how you can use them for systems administration. |
− | == Code Listings and Class Notes ==
| |
− | | |
− | === Every day instructions ===
| |
− | | |
− | Each class we will go through the same drill with checking out the code tree, working in it, and committing our changes. This allows us to save our work and collaborate.
| |
− | | |
− | Each new class period should be done within a new directory, and each new script is a different file within that directory.
| |
− | | |
− | Note that this class will only cover the tip of the iceberg for Bash scripting; to learn more -- follow the bash links above, type in the code you find while you read the explanations, and experiment. Be brave. You don't have enough permissions to break anything important.
| |
− | | |
− | Note also that this class is a work in progress, and I might jump into the wiki to make a change to the notes or the code for next time. Feel free to suggest changes or ask questions, especially if you buy me dinner after class.
| |
− | | |
− | Sometimes my instructions will be too terse -- ask questions, but try to learn what I am talking about and translate it into commands at the prompt without me specifying exactly what those commands are all the time.
| |
− | | |
− | Each script should be commented at the top with (1) your email address, (2) a sentence about what the script does, and (3) another sentence about HOW it does it in high-level terms.
| |
− | | |
− | Finally, give Charlie a big hand! He will be the TA for the class, walking around and helping, having successfully completed the first iteration of the soon to be famous FG Bash Scripting Class. All alumni are eligible to TA next time and earn valuable volunteer hours.
| |
− | | |
− | === Zeroth day -- working on the command line, editing files, using version control ===
| |
− | | |
− | ==== The command line, editing, etc. ====
| |
− | | |
− | Here we will practice working on the command line and editing files and such...
| |
− | | |
− | First note: DON'T EVER USE SPACES IN FILENAMES!!!! It just makes trouble you may not know how to get out of (yet). Just use alphanumeric characters, underscores, and dots -- nothing else for now!
| |
− | | |
− | Second note: WHEN LOST IN THE FILE SYSTEM, RUN `pwd` AND `ls -l`. It is a good habit anytime you change to a directory to run those two commands, read the output, and visualize where you are and what is happening.
| |
− | | |
− | Make a directory (`mkdir foo`), change current working directory to it (`cd foo`), list all the files in it (`ls`), print the current working directory (`pwd`), touch a file (`touch myfile.txt`), list the files again, delete the file created with touch (`rm myfile.txt`), change directories one level up (`cd ..`), delete the directory you started by making (`rmdir foo`).
| |
− | | |
− | Do it all again except use different names.
| |
− | | |
− | Make a directory and cd to it. Start editing a file (`nano myfile.txt`). Figure out how to save the file in nano. Exit nano. List files in your directory. Edit the file again with nano. Repeat until you understand what is going on.
| |
− | | |
− | Start a new directory, mess around like above, then look at permissions with the verbose option to ls, `ls -l`. Change permissions so you can't view the file (`chmod 0000 myfile.txt`), list the directory verbosely again, and try to edit the file. Change the permissions again to 0700. Edit it again. Then clean out the directory and get back to home.
| |
− | | |
− | Ask questions.
| |
− | | |
− | Repeat until understood. Talk with your neighbors
| |
− | | |
− | ==== Subversion and google code ====
| |
− | | |
− | Here we will practice working with a code repository -- pulling files from a central location, modifying them, pushing our changes back to the central location, seeing what other people have done.
| |
− | | |
− | (Take a deep breath.)
| |
− | | |
− | Open a new browser window:
| |
− | | |
− | http://code.google.com/p/freegeekbash/ | |
− | | |
− | Sign in with your Gmail name (referred to as GOOGLENAME below) and its password (look in the top right corner for "Sign In").
| |
− | | |
− | Click the "source" tab. Under the heading '''Command Line Access''' you see a line that ends with the link <nowiki> "googlecode.com password"</nowiki>. Click on this link, and you are taken to a new web page that displays an ugly 12 character PROJECTPASSWD. Write down this password and save it for future use. Then browser-back to the source tab page.
| |
− | | |
− | Just above the line with the link that you just clicked is a line that reads
| |
− | <nowiki>"svn checkout https://freegeekbash.googlecode.com/svn/trunk/ freegeekbash --username GOOGLENAME" </nowiki>. Open a terminal window, put this whole line (''not'' the too similar line with <nowiki>"http://..."</nowiki>) into the command line and hit Enter.
| |
− | You will be given a blank prompt for a password. Enter your PROJECTPASSWD.
| |
− | Wait a bit, read the output and notice you have a new directory freegeekbash with files and subdirectories. List the files, best with the -a or -al option.
| |
− | cd to freegeekbash, ls -al. cd to semester2, ls -al. Ask questions.
| |
− | | |
− | Here in semester2, make a directory named GOOGLENAME (remember--your Gmail name, without the "@gmail.com"), cd to GOOGLENAME, make a directory named class0, cd to class0, and finally create and edit a file here.
| |
− | | |
− | ONE STUDENT AT A TIME: cd up two levels (`cd ..; cd ..`); run `pwd` to make sure it is "~/freegeekbash/semester2". Run `svn add GOOGLENAME` to add your new directory, READ THE OUTPUT. Run <pre>svn commit -m 'started a new dir for the bash class'</pre>; this "commits" your changes to the central repository, along with a comment (after the "-m" switch) about them. MAKE SURE YOU PUT QUOTES AROUND YOUR COMMENT AFTER THE "-m".
| |
− | | |
− | ONE STUDENT AT A TIME: cd to ~/freegeekbash, run `svn update`, read the output. Then explore the new files (running `ls -al` and `pwd`) and directories underneath the directory "semester2".
| |
− | | |
− | Once all the students are caught up, everyone cd to ~/freegeekbash and run `svn update`, then `svn log`, and look at the output.
| |
− | | |
− | Questions.
| |
− | | |
− | Rinse repeat until we can all deal with editing new and old files, making directories, committing, and updating.
| |
− | | |
− | MAKE SURE AT END: everybody run `svn commit -m "final commit at end of first day"` in ~/freegeekbash/
| |
− | | |
− | === First day -- scripts and variables ===
| |
− | | |
− | What is a shell script anyway? It is ...
| |
− | | |
− | ... a file of text ...
| |
− | | |
− | ... full of unix commands, variables, and control structures ... | |
− | | |
− | ... that usually executes from top to bottom ...
| |
− | | |
− | ... using variables to hold data ...
| |
− | | |
− | ... and loops and conditionals to do fancy programming stuff...
| |
− | | |
− | ... with a way to read input and write output ...
| |
− | | |
− | ... that probably has some "side-effects".
| |
− | | |
− | Why learn shell? Old-school Unix style? Class?
| |
− | | |
− | ==== Getting code from repository ====
| |
− | | |
− | <pre>
| |
− | | |
− | svn checkout https://freegeekbash.googlecode.com/svn/trunk/ freegeekbash --username PROJECTPASSWD
| |
− | | |
− | cd freegeekbash/semester/GOOGLENAME/
| |
− | | |
− | mkdir class1
| |
− | | |
− | cd class1
| |
− | | |
− | # Do your programming thing here
| |
− | | |
− | </pre>
| |
− | | |
− | ==== Class 1 - Script 1 ====
| |
− | Following just prints my name. Run it as is, but then change it to print your name.
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 1 wsprague@nospam.org 2008-10-29
| |
− | # Script just prints my name
| |
− | #
| |
− | | |
− | FIRSTNAME='Webb'
| |
− | LASTNAME='S.'
| |
− | echo "Hello, $FIRSTNAME $LASTNAME"
| |
− | </pre>
| |
− | | |
− | Edit it by running the following: nano script1.sh, then save it (ctrl-O in nano), then chmod 0700 script1.sh, and run as
| |
− |
| |
− | <pre>
| |
− | ./script1.sh
| |
− | </pre>
| |
− | | |
− | in the other terminal window.
| |
− | | |
− | ==== Class 1 - Script 2 ====
| |
− | | |
− | This script takes a parameter from the command line and uses it as the name: "variable expansion". Note the expansion, but also how different quotes or lack thereof have different effects.
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 2 wsprague@nospam.org 2008-10-29
| |
− | # Prints names from command line
| |
− | #
| |
− | | |
− | NAME=$1
| |
− | echo "Hello, $NAME."
| |
− | echo 'Hello, $NAME.'
| |
− | echo Hello, $NAME.
| |
− | echo Hello, "$NAME".
| |
− | echo Hello, \"$NAME\".
| |
− | echo "Hello, ${NAME}with text."
| |
− | echo "Hello, $NAMEwith text."
| |
− | </pre>
| |
− | | |
− | try from the command line:
| |
− | | |
− | <pre>
| |
− | ./script2.sh
| |
− | ./script2.sh Foobar
| |
− | ./script2.sh "Foobar Smith" | |
− | ./script2.sh Foobar Smith
| |
− | </pre>
| |
− | | |
− | ==== Class 1 - Script 3 ====
| |
− | | |
− | This script does some basic math, and then outputs it using variable expansion. Check out the quoting. Also note what happens when we try to do math on weird input.
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # Script adds two numbers from command line
| |
− | | |
− | LEFT=$1
| |
− | RIGHT=$2
| |
− | RES=$(( $LEFT + $RIGHT ))
| |
− | echo $(( $LEFT + $RIGHT ))
| |
− | echo '$(( $LEFT + $RIGHT ))'
| |
− | echo "$(( $LEFT + $RIGHT ))"
| |
− | echo "$LEFT + $RIGHT = $RES."
| |
− | echo '$LEFT + $RIGHT = $RES.' # Why does this do what it does?
| |
− | | |
− | </pre>
| |
− | try from the command line:
| |
− | | |
− | <pre>
| |
− | ./script3.sh 100 10
| |
− | ./script3.sh 'one hundred' 'ten'
| |
− | ./script3.sh 34 | |
− | </pre>
| |
− | | |
− | ==== Class 1 - Script 4 ====
| |
− | This script does some "shell expansion" using the unix command "date",
| |
− | which gives a formatted string of the date; use "date --help" to see more).
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # Script 4. Script calculates the year if we give it years from now.
| |
− | #
| |
− | | |
− | YEAR=$( date +'%Y' )
| |
− | YEARS_FORWARD=$1
| |
− | echo "Start at year $YEAR, finish at year $(($YEAR + $YEARS_FORWARD))"
| |
− | | |
− | </pre>
| |
− | | |
− | Try from the command line
| |
− | | |
− | <pre>
| |
− | ./script4.sh
| |
− | ./script4.sh 10 | |
− | ./script4.sh "ten"
| |
− | </pre>
| |
− | | |
− | ==== Class 1 - Script 5 ====
| |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # Script 5 takes a filename, strips any spaces from it, and moves the original file
| |
− | # to the new name.
| |
− | | |
− | RES=$( echo $1 | sed 's/ /_/g' )
| |
− | echo "After stripping of spaces, \"$1\" looks like \"$RES\"" # Why do we escape the quotes here?
| |
− | echo mv "$1" "$RES" # try this same script without "echo" here, after touching a file with a name that you put into the parameters of the script
| |
− | | |
− | </pre>
| |
− | | |
− | Try from the command line:
| |
− | | |
− | <pre>
| |
− | touch "a file with spaces in the name"
| |
− | ./script5.sh "a quoted filename with spaces"
| |
− | ./script5.sh an unquoted filename with spaces
| |
− | ././script5.sh a_filename_wo_spaces
| |
− | </pre>
| |
− | | |
− | ==== Committing your changes ====
| |
− | | |
− | <pre>
| |
− | | |
− | cd freegeekbash/GOOGLENAME/
| |
− | | |
− | svn update
| |
− | | |
− | svn add class1
| |
− | | |
− | svn ci -m 'Committing code for class 1'
| |
− | | |
− | # Make sure you see update -- call the teacher if not
| |
− | | |
− | svn update
| |
− | | |
− | </pre>
| |
− | | |
− | ==== Discussion ====
| |
− | | |
− | Coding style -- variable names, comments, indentation, trickiness.
| |
− | | |
− | Comment first design.
| |
− | | |
− | "Commenting out" code.
| |
− | | |
− | === Second day -- for-loops ===
| |
− | | |
− | First checkout the code following the instructions (bad as they may be) for the previous day.
| |
− | | |
− | What is a for-loop? It is a bash construct that repeatedly grabs one item from a sequence of data separated by whitespace, does something with that piece of the sequence, until there is no more data.
| |
− | | |
− | Here is the paradigm:
| |
− | | |
− | <pre>
| |
− | for VARIABLE in SEQUENCE; do
| |
− | # bunch of statements in here that are done repeatedly
| |
− | # referencing $VARIABLE
| |
− | # more statements
| |
− | done
| |
− | </pre>
| |
− | | |
− | ==== Finger exercises ====
| |
− | | |
− | Edit a few files with nano, find them with ls, and delete them with rm
| |
− | | |
− | Run the command `seq 1 10` from the command line
| |
− | | |
− | Run the command `printf "hello %s, my name is %s, I am %i years old" Tarzan Jane 32`
| |
− | | |
− | Run the command `factor 144` from the command line
| |
− | | |
− | ==== Class 2 - Script 0 ====
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 0 wsprague@nospam.org 2008-10-29
| |
− | # Basic for loop with seq and printf
| |
− | #
| |
− | | |
− | START=$1
| |
− | FINISH=$2
| |
− | FULL_SEQ=$( seq $START $FINISH )
| |
− | COUNT=0
| |
− | | |
− | printf "Start = %i, finish = %i\n" $START $FINISH
| |
− | for X in $FULL_SEQ; do
| |
− | echo "touching file.$X"
| |
− | touch "file.$X"
| |
− | COUNT=$(( $COUNT+1 ))
| |
− | done
| |
− | printf "finished working on %i files\n" $COUNT
| |
− | </pre>
| |
− | | |
− | Save this as script0.sh, then chmod 0700 script0.sh. Run as follows:
| |
− | | |
− | <pre>
| |
− | ./script0.sh 1 10
| |
− | ./script0.sh 4 14
| |
− | </pre>
| |
− | | |
− | Examine your directory to see the new files and their names. Those are from this script.
| |
− | | |
− | ==== Class 2 - Script 1 ====
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 1 wsprague@nospam.org 2008-10-29
| |
− | # Do some fancy formatting with printf,
| |
− | # by calculating the first 10 "orders of magnitude", and printing them
| |
− | | |
− | MAX=10
| |
− | SEQ=$( seq 0 $MAX)
| |
− | echo $SEQ
| |
− | for I in $SEQ; do
| |
− | OUT=$(( 10 ** $I ))
| |
− | printf "The %3i order of magnitude = %i.\n" $I $OUT
| |
− | done
| |
− | </pre>
| |
− | | |
− | Run this as
| |
− | | |
− | <pre>
| |
− | ./script1.sh
| |
− | </pre>
| |
− | | |
− | Modify it to make "MAX" store the number from $1.
| |
− | | |
− | ==== Class 2 - Script 2 ====
| |
− | The following factors a number and creates a bunch of files based on the result. Note the sed pipeline below. As Charlie correctly inferred over email, it erases any initial sequence of numbers that is followed by a colon. needed because the output of factor 5040 begins with "5040:" before the factors 2 2 2 ...
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 2 wsprague@nospam.org 2008-10-29
| |
− | # Takes a name and a number as parameters,
| |
− | # factors the number, touches all files "$name.$number".
| |
− | | |
− | NAME=foobar # use $1
| |
− | NUMBER=12
| |
− | FACTORS=$(factor $NUMBER | sed 's/^[0-9]*://g' )
| |
− | printf "Factors working on: %s\n" "$FACTORS"
| |
− | echo
| |
− | | |
− | TEST=1
| |
− | COUNT=0
| |
− | for SUFFIX in $FACTORS; do
| |
− | TEST=$(($TEST * $SUFFIX))
| |
− | COUNT=$(($COUNT + 1))
| |
− | touch "$NAME.$SUFFIX"
| |
− | printf "Touched: prefix = %s, suffix = %i\n" $NAME $SUFFIX
| |
− | done
| |
− | printf "\nFinished working on %i files\n" $COUNT
| |
− | </pre>
| |
− | | |
− | Run this script as
| |
− | | |
− | <pre>
| |
− | ./script2.sh
| |
− | </pre>
| |
− | | |
− | Then modify it to set "NAME" and "NUMBER" from the command line.
| |
− | | |
− | ==== Class 2 - Script 3 ====
| |
− | | |
− | Script 3 prints out times table using TWO loops, one "inside" the other.
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 3 wsprague@nospam.org 2008-10-29
| |
− | # Generate times tables by using a nested loop.
| |
− | | |
− | START=$1
| |
− | FINISH=$2
| |
− | INCREMENT=1 # try changing this
| |
− | SEQ=$(seq $START $INCREMENT $FINISH )
| |
− | | |
− | # Print top row of output table
| |
− | printf " "
| |
− | for x in $SEQ; do
| |
− | printf "%4i " $x
| |
− | done
| |
− | | |
− | # Print top row separator
| |
− | printf "\n "
| |
− | for x in $SEQ; do
| |
− | printf "_____"
| |
− | done
| |
− | | |
− | echo
| |
− | | |
− | # Fill in each row with left label and cell result
| |
− | for x in $SEQ; do
| |
− | printf "\n%4i| " $x
| |
− | for y in $SEQ; do
| |
− | printf "%4i " $(( $x * $y ))
| |
− | done
| |
− | done
| |
− | printf "\n"
| |
− | </pre>
| |
− | | |
− | Run this as
| |
− | | |
− | <pre>
| |
− | ./script3.sh 1 12
| |
− | </pre>
| |
− | | |
− | ==== Discussion ====
| |
− | | |
− | Debugging loops with echo statements
| |
− | | |
− | Precalculating things like sequences
| |
− | | |
− | Code style -- when to add whitespace between sections and stanzas
| |
− | | |
− | === Third day -- conditionals ===
| |
− | | |
− | http://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs | |
− | | |
− | ==== Class 3 - Script 1 -- determine relative order of two numbers, and check input validity ====
| |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # script 1 wsprague@nospam.org 2008-10-29
| |
− | # Categorize numbers based on input.
| |
− | # Also check input (finally!).
| |
− | | |
− | USAGE='./script1.sh num1 num2'
| |
− | ERROR="Error. Usage: $USAGE"
| |
− | | |
− | # Handles the input
| |
− | if [[ $1 ]]; then # $1 is "true" if something is in there
| |
− | LEFT=$1
| |
− | shift
| |
− | else
| |
− | echo $ERROR
| |
− | exit 1
| |
− | fi
| |
− | | |
− | if [[ $1 ]]; then
| |
− | RIGHT=$1
| |
− | shift
| |
− | else
| |
− | echo $ERROR
| |
− | exit 1
| |
− | fi
| |
− | | |
− | # Compares the left and right variables and prints a message
| |
− | if [[ $LEFT < $RIGHT ]]; then
| |
− | echo "$LEFT is strictly lesser than $RIGHT _lexicographically_"
| |
− | elif [[ $LEFT > $RIGHT ]]; then
| |
− | echo "$LEFT is strictly greater than $RIGHT _lexicographically_"
| |
− | else
| |
− | echo "$LEFT is exactly the same as $RIGHT _lexicographically_"
| |
− | fi
| |
− | </pre>
| |
− | | |
− | Do the chmod 0700 etc dance. Try this as
| |
− | | |
− | <pre>
| |
− | ./script1 aardvark zebra
| |
− | ./script1 1 9
| |
− | ./script1 10 9
| |
− | ./script1 foo
| |
− | </pre>
| |
− | | |
− | ==== Class 3 - Script 2 -- filter prime numbers ====
| |
− | | |
− | Prints out prime numbers, uses a lot of crazy conditional operations.
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | # script2.sh
| |
− | # displays prime numbers less than input parameter.=
| |
− | # tests for input validity
| |
− | # breaks out of loop once go over max
| |
− | # does lots of stuff!
| |
− | | |
− | # write a function that tweaks factor command
| |
− | function myfactor (){
| |
− | OUT=$(factor $1 | sed 's/^[0-9]*: //')
| |
− | echo "$OUT"
| |
− | }
| |
− | | |
− | # Verify input
| |
− | USAGE="script2.sh num"
| |
− | if [[ $1 && $(echo $1 | grep '^[0-9][0-9]*$') ]]; then ## Note boolean operator
| |
− | MAX=$1;
| |
− | shift
| |
− | else
| |
− | echo $USAGE
| |
− | exit 1; ## "1" signifies error. Run a script and then try "echo $?"
| |
− | fi
| |
− | | |
− | # Verify script has not already been than run for given number
| |
− | if [[ -e $MAX ]]; then
| |
− | echo "Already ran script for number $MAX. rm file to do repeat." 1>&2
| |
− | exit 1
| |
− | else
| |
− | touch $MAX
| |
− | fi
| |
− | | |
− | # Get prime numbers, but not above MAXMAX
| |
− | MAXMAX=100
| |
− | for X in $(seq $MAX); do
| |
− |
| |
− | # Test whether have more factors than input number
| |
− | # when X is a prime, it has only one factor which is itself
| |
− | # this loop prints a list of primes
| |
− | FACTORS=$( myfactor $X )
| |
− | if [[ $X == $FACTORS ]]; then
| |
− | echo $X
| |
− | else
| |
− | echo "foobar" > /dev/null # do nothing
| |
− | fi
| |
− | | |
− | # test whether above MAXMAX, "break" out of loop if so.
| |
− | if [[ $X -gt $MAXMAX ]] ; then
| |
− | echo "won't let $X go above $MAXMAX. Breaking out of loop"
| |
− | break
| |
− | fi
| |
− | | |
− | done
| |
− | | |
− | # do something to highlight how the break statement works
| |
− | echo bye
| |
− | | |
− | </pre>
| |
− | | |
− | Run this as
| |
− | | |
− | <pre>
| |
− | ./script2.sh
| |
− | ./script2.sh 80 | |
− | ./script2.sh 800
| |
− | ./script2.sh blahblahlbah
| |
− | </pre>
| |
− | | |
− | ==== Class 3 - Script 3 -- using case ====
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | #
| |
− | # Script that does some silliness case statements
| |
− | | |
− | USAGE="./$0 input"
| |
− | if [[ $1 ]]; then
| |
− | ARG=$1; shift
| |
− | else
| |
− | echo "$USAGE"
| |
− | exit
| |
− | fi
| |
− | | |
− | case $ARG in
| |
− | foo | bar | baz) echo "speaking unix baby-talk";;
| |
− | mama | dada)
| |
− | echo "speaking real baby-talk";; ## note multiline block
| |
− | [0-9]*) echo "numbers"
| |
− | touch $ARG.file
| |
− | ;;
| |
− | mama ) echo ma-ma;; ## we won't ever do this
| |
− | *) echo other stuff;;
| |
− | esac
| |
− | </pre>
| |
− | | |
− | Run this as
| |
− | | |
− | <pre>
| |
− | ./script3.sh
| |
− | ./script3.sh 12
| |
− | ./script3.sh mama
| |
− | ./script3.sh dada
| |
− | ./script3.sh "this is a long line"
| |
− | </pre>
| |
− | | |
− | ==== Discussion ====
| |
− | | |
− | ...
| |
− | | |
− | === Fourth day -- while-read and functions ===
| |
− | | |
− | We are going to read a file with email addresses (and other information like "valid") and send spam to all of them.
| |
− | | |
− | The main things to look out for here are (1) writing a "function", (2) "sourcing" one bash script inside another script, and (3) reading and processing line-by-line input to do something useful.
| |
− | | |
− | | |
− | ==== Class 4 - Script 1 -- simple function ====
| |
− | | |
− | The function syntax creates a "mini" command that can be accessed wherever it is sourced.
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | | |
− | # creates a function (just like a new unix command), that sends
| |
− | # semi-customized spam to the recipient
| |
− | | |
− | function spam() {
| |
− | if [[ $1 ]]; then
| |
− | local RECIP=$1
| |
− | shift
| |
− | else
| |
− | echo "spam: missing recipient parameter" 1>&2
| |
− | return 1
| |
− | fi
| |
− | | |
− | local TODAY=$(date +%A)
| |
− | | |
− | printf "First para: Buy me!\n\nSecond para: type your password!\n" \
| |
− | | mail -s "$TODAY's winning lotto ticket" $RECIP
| |
− | | |
− | printf "Finished: %s\n" $RECIP 1>&2
| |
− | | |
− | if [[ -e "$RECIP" ]]; then
| |
− | echo "removing $RECIP" 1>&2
| |
− | rm "$RECIP"
| |
− | fi
| |
− | | |
− | return 0
| |
− | }
| |
− | </pre>
| |
− | | |
− | Try this from the command line
| |
− | | |
− | <pre>
| |
− | | |
− | $ source script1.sh
| |
− | $ spam SOME_EMAIL_ADDRESS
| |
− | | |
− | </pre>
| |
− | | |
− | ==== Class 4 - Script 2 -- simple while-read ====
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | # use "while-read" to parse a text file
| |
− | | |
− | # print column headings
| |
− | printf "COLUMN-1 REMAINING\n"
| |
− | | |
− | # print four characters of first column, and then the first little bit of
| |
− | # everything else.
| |
− | | |
− | while read V1 VPLUS ; do
| |
− | printf '"%4.4s..." "%.5s..."\n' $V1 "$VPLUS"
| |
− | done
| |
− | return 0
| |
− | </pre>
| |
− | | |
− | Try this (note the funky "<<END" syntax -- I will explain...)
| |
− | | |
− | <pre>
| |
− | $ cat | ./script2.sh <<END
| |
− | column1 some more date
| |
− | column2 a whole lot more data
| |
− | column3
| |
− | | |
− | END
| |
− | </pre>
| |
− | | |
− | ==== Class 4 - Script 3 -- source a function, while-read a file, and send data from file to the function ====
| |
− | | |
− | Now we are going to send the contents of a file through a script that sends spam to each email address on each new line
| |
− | | |
− | <pre>
| |
− | #!/bin/bash
| |
− | | |
− | # processes a file, sends spam to each valid email address (verified with grep
| |
− | # and an "if" statement)
| |
− | | |
− | # By "sourcing" the function in this file, we can use all its code without
| |
− | # looking at it
| |
− | source script1.sh
| |
− | | |
− | # set up some variables
| |
− | SUCCESS_COUNTER=0
| |
− | FAILURE_COUNTER=0
| |
− | FULL_EMAIL_PATTERN='^[a-z0-9.][a-z0-9.]*@[a-z0-9][a-z0-9]*\.[a-z0-9][a-z0-9]$*'
| |
− | USER_EMAIL_PATTERN='^[a-z0-9.][a-z0-9.]*'
| |
− | | |
− | # process the file from "standard input"
| |
− | while read V1 VPLUS ; do
| |
− | if echo $V1 | grep $FULL_EMAIL_PATTERN ; then
| |
− | spam $V1
| |
− | echo "spammed (internet): $V1" 1>&2
| |
− | SUCCESS_COUNTER=$(( $SUCCESS_COUNTER + 1))
| |
− | elif echo $V1 | grep $USER_EMAIL_PATTERN; then
| |
− | spam $V1
| |
− | echo "spammed (local): $V1" 1>&2
| |
− | SUCCESS_COUNTER=$(( $SUCCESS_COUNTER + 1))
| |
− | else
| |
− | echo "unreadable email: $V1" 1>&2
| |
− | FAILURE_COUNTER=$(( $FAILURE_COUNTER + 1))
| |
− | fi
| |
− | done
| |
− | | |
− | # summarize what we did and exit
| |
− | echo "Successfully spammed $SUCCESS_COUNTER emails!" 1>&2
| |
− | exit 0
| |
− | | |
− | </pre>
| |
− | | |
− | Try this (use your own email where it says "USEREMAIL"):
| |
− | <pre>
| |
− | | |
− | cat | ./script3.sh <<END
| |
− | USEREMAIL
| |
− | wsprague@pdx.edu
| |
− | wsprague
| |
− | a_bogus3email
| |
− | END
| |
− | </pre>
| |
− | | |
− | === Fifth Day -- Discussion and accumulated questions ===
| |
− | | |
− | This is a good time to have an open discussion about Unix and Bash programming, as well as give tips for further reading and hacking. To get us started, here are some bullet points:
| |
− | | |
− | ==== More learning! ====
| |
− | | |
− | * Questions?
| |
− | | |
− | * Manuals
| |
− | | |
− | * Look at important FG scripts
| |
− | | |
− | * Own projects (mine: a stuff tracker with barter extensions)
| |
− | | |
− | ==== Class meta discussion ====
| |
− | | |
− | * Feedback about the class (also tell Laurel)
| |
− | | |
− | * Other classes (text editing!)
| |
− | | |
− | * Teaching assistants
| |
− | | |
− | ==== Important ideas in software ====
| |
− | | |
− | * Commenting code
| |
− | | |
− | * Other languages
| |
− | | |
− | * Software design
| |
− | | |
− | * Version control systems
| |
− | | |
− | * Crazy Unix programming (APUE)
| |
− | | |
− | ** Processes
| |
− | | |
− | ** Interprocess communication
| |
− | | |
− | ** File descriptors and input/ output
| |
− | | |
− | * The character of Unix and programming:
| |
− | | |
− | ** DIY versus "shrinkwrapped"
| |
− | | |
− | ** Tools for later piping versus applications
| |
− | | |
− | ==== Community ====
| |
− | | |
− | * How to help FG and practice programming with new skills
| |
− | | |
− | * "Advanced Bash Programming with SQL" -- the next class
| |
| | | |
| [[Category: Classes]] | | [[Category: Classes]] |