• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Bash Programming
 

Bash Programming

on

  • 2,182 views

 

Statistics

Views

Total Views
2,182
Views on SlideShare
2,174
Embed Views
8

Actions

Likes
0
Downloads
65
Comments
0

1 Embed 8

http://www.slideshare.net 8

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

CC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike LicenseCC Attribution-NonCommercial-ShareAlike License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • This handout is meant to accompany a 45-minute presentation/demonstration. I seriously doubt that we'll have time in 45 minutes to cover all this we'd like to, plus I realize that trying to record all the lines I write as a demo would be daunting for most people. Thus, after an initial introduction this sheet will contain three scripts I'd like to use as examples. This session is by no means intended to be exhaustive. If you want to go farther with scripting, I highly recommend the “Bash Guide for Beginners” available for free from tldp.org. You should also know that this session is intended to teach basic use of bash scripting, not linux command-line tools in general. Thus, although we might use du or tar, we won't take time to really explain them, as they are external programs, not internal to bash. For learning these tools I recommend the “Introduction to Linux – A Hands on Guide” also available from tldp.org. Bash is an acronym for “Bourne Again Shell”, and is a descendant of sh, the original Bourne Shell. It is the default shell in use on most, if not all, modern linux distributions, and is available on most flavors of UNIX as well. This means that, when you open a text terminal on a *nix system, bash is probably what is giving you a prompt, and what is interpreting what you type. Similar to a batch file in Windows, a bash script is simply a collection of command line entries intended to run one after the other. There's nothing you can do in a script that you can't do sitting in front of the keyboard, and, although it might take some creativity, there's nothing you can do sitting in front of the keyboard that you can't do in a script. Scripts are great for repetitive tasks because they allow you to simply call the script rather than typing all 10-1000 lines yourself, each time you need to accomplish a particular task. Cron (which we aren't covering) also allows a user to automate the running of scripts, further increasing their use. Scripts can be made reusable as well, so a backup script for /etc/ can later be used to backup /home instead. For this presentation, I'll mainly be adding a few lines of code to a script, then running it to demonstrate the result. If you're using this at home, I recommend taking a similar approach: add a few lines, then try it. I use vim for text editing. It contains many features that make script editing easier. Here're the contents of my ~/.vimrc: set tabstop=4 set nohlsearch set softtabstop=4 set number set autoindent set showmatch
  • #!/bin/bash -x # demo.sh # This script's real purpose is just to demonstrate some bash scripting basics # That top line is called the "shebang", and gives the "magic" the system needs to determine what program it should use to interpret our script. By adding "-x" after /bin/bash, we run the script in debug mode, which is helpful while writing and testing the script. We should probably remove it when the script is finished. ############################################################################################# # We'll use echo, a bash built-in command, to reference the PATH variable, which contains a colon-separated list of directories in which your shell will search for executable binaries and scripts. Bash runs each line of the script just as if you'd typed it in and pressed "enter". echo $PATH # If your script is in your path, which you could easily do by running "mkdir ~/bin", then creating your scripts inside that directory, then you don't have to type out the path to the script every time, just the name of the script. "chmod +x ~/bin/*" should set all your scripts executable. # If you need bash to not interpret the end of a line as the end of a command (typically for aesthetic reasons), escape the end of the line with a backslash: which bash
  • # Set a variable like this: var=1 # reference it as part of a command like this: ping -c 1 -W 1 192.168.150.$var # The following command might be intended to run "ping -c 1 -W 1 192.168.150.11", but if you try it it will fail, so we'll leave it commented. #ping -c 1 -W 1 192.168.150.$var1 # To get the .11 to work, use curly braces, which are typically optional but always best practice: ping -c 1 -W 1 192.168.150.${var}1
  • # The following might not work quite as expected: echo $var+3 # Encapsulate the operation with $[] to run it as a math operation: echo $[$var+$var1] # Use {} to create a list: echo item{1,3,5} # comma-separated or .. ranges: echo item{1..5}
  • # Use $() to run a command, and substitute its output. echo "Today's date is $(date), or so I'm told." # backquotes work too. echo "In ISO format, that would be `date -I`." ls -l $(which passwd) # tilde expands to a home directory: ls -ld ~ ls -ld ~root # Wildcard globs expand to match a filename ls -ld /usr/* # Globs start in the current directory if they are the beginning of the word ls -ld *.sh
  • # Quite a bit of expansion here: echo /{,usr/{,local/}}{,s}bin ~/bin $PATH `which date` $[1+3] number{1..5}item # We can prevent it by escaping the special expansion characters echo /{,usr/{,local/}}{,s}bin ~/bin $PATH `which date` $[1+3] number{1..5}item # Or with single quotes echo '/{,usr/{,local/}}{,s}bin ~/bin $PATH `which date` $[1+3] number{1..5}item' # Double quotes prevent some, but not all, expansion echo "/{,usr/{,local/}}{,s}bin ~/bin $PATH `which date` $[1+3] number{1..5}item"
  • # The easiest kind of loop is a for loop, which runs a set of commands once per value in a list. for COOKIE in sugar mint oatmeal-raisin chocolate chip do echo "You ate the $COOKIE cookie! Yum!" # $(()) is a more flexible way of working with math echo "You've eaten $((++CKCOUNT)) cookies so far!" done # You could do that whole thing in a much uglier way...the way it's formatted above is just for aesthetics. Keep in mind that a semi-colon represents the end of a command, usually just accomplished by ending a line. for COOKIE in sugar mint oatmeal-raisin chocolate chip; do echo "You ate the $COOKIE cookie! Yum!"; echo "You've eaten $((++CKCOUNT)) cookies so far!"; done # Every time a command runs, it either succeeds or fails. The exit status records this. 0 means a success. 1-255 represent failure, allowing a programmer to specify the type of failure numerically. Reference the exit status of the last command with $?. echo "echo always succeeds" echo $? ls /idontexist echo $? # Sometimes you need to know something more specific about a file or variable. Tests allow you to find out. echo "Does /idontexist exist?" test -f /idontexist echo $? # Test shorthand: echo "Does /usr/local/bin exist?" [ -f /usr/local/bin ] echo $? # You can find a nice list of tests at http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html # while loops run a test or command, and continue to run the loop as long as the exit status of the condition is 0: echo "Eat 5 cookies!" # Sometimes we have to be explicit about a variable being a specific type, in this case an integer: declare -i NEWCKCNT=0 while [ $NEWCKCNT -lt 5 ]; do echo "You ate a cookie!" echo "You've eaten $((++NEWCKCNT)) cookie(s)!" done # until loops keep going so long as a condition is false: echo "Eat 5 more cookies!" declare -i UNTCKCNT=1 until [ $UNTCKCNT -ge 6 ]; do echo "You ate another cookie!" echo "You've eaten $((UNTCKCNT++)) cookie(s)!" done
  • # Conditional operators allow you to run a particular command depending on the outcome of the previous. # () opens a subshell, or new instance of bash, which "exit" tells to exit with a particular status code. (exit 0) && echo "Success!" || echo "Epic Fail!" (exit 1) && echo "Success!" || echo "Epic Fail!" # Be careful to think through what a particular bit of code might do: (exit 0) && ( echo "Success!"; exit 1 ) || echo "But fail, too!" (exit 1) && echo "Success!" || echo "failure!" echo $? # Subshells allow you to group commands, in order to keep status code sanity: (exit 1) && echo "Success!" || ( echo "Failure!"; exit 1) echo $? # If statements allow you to make your conditional scenarios more complex: declare -i MYNUM=1 while [ $MYNUM -lt 5 ]; do if [ $MYNUM -eq 1 ]; then echo "Alright, we're number $((MYNUM++))!" elif [ $MYNUM -eq 2 ]; then echo "$((MYNUM++)) is an okay number as well..." else echo "Blech! $((MYNUM++)) is really not one of my favorite numbers!" fi done
  • # Instead of using multiple elif statements, case might be a better choice: for ROTATION in kibbles broccoli fish pizza cardboard rocks random stuff; do case $ROTATION in kibbles) echo "$ROTATION are too dry!" ;; broccoli) echo "Seriously, do you even like $ROTATION?" ;; fish) echo "Now that's what I'm after: $ROTATION!" ;; pizza) echo "$ROTATION looks good, but I'm too full!" ;; cardboard|rocks) echo "Seriously, I can't eat $ROTATION!" ;; *) echo "I don't even know what that is!" ;; esac done
  • # By default bash expects to get and receive data from the keyboard and terminal window. Redirect operators allow us to depart from the default. # Channel 2 is stderr. Observe the errors from the following command, then that the second time, they disappear. ls -l /idontexist /bin/bash ls -l /idontexist /bin/bash 2> /tmp/out # Since we redirected stderr to a temporary file, we should be able to look at it now: cat /tmp/out # It's common to redirect errors to /dev/null, out of sight, out of mind: ls -l /idontexist /bin/bash 2> /dev/null # Redirecting with > overwrites the target file: ls -l /neitherdoi 2> /tmp/out cat /tmp/out # >> appends instead of overwriting ls -l /heymeniether 2>> /tmp/out ls -l /idontexist 2>> /tmp/out cat /tmp/out # If you don't specify a channel number, bash assumes you mean channel 1, stdout. Observe the following command's output, then that the second time, you'll see only the error: ls -l /idontexist /bin/bash ls -l /idontexist /bin/bash > /dev/null # Same rule for appending: ls -l /bin/bash >> /tmp/out
  • # To redirect both channels together, use &> ls -l /idontexist /bin/bash &> /tmp/out cat /tmp/out # Same rule for appending: ls -l /neitherdoi /bin/date &>> /tmp/out cat /tmp/out # Of course you can redirect the channels to separate locations as well: ls -l /heymeneither /bin/date 2> /dev/null > /tmp/out cat /tmp/out # The pipe character redirects the stdout of the program on its left as the stdin of the program on its right: ls -l /idontexist /neitherdoi /bin/bash /bin/date | cat -n # Notice that stderr doesn't go across the pipe, just stdout. To send both, we'll have to send stderr to stdout: ls -l /idontexist /neitherdoi /bin/bash /bin/date 2>&1 | cat -n # Sometimes while scripting you want to send a custom message on stderr. To test this, you'd actually have to run this entire script with 2>. echo "Uh-oh, something went wrong!" >&2 # Sometimes, for various reasons, you may need to redirect the contents of a file to stdin, channel 0, rather than having it take input from the keyboard: tr 'a-z' 'A-Z' < /tmp/out # This accomplishes the same thing as the following command, but in a more elegant and flexible way. cat /tmp/out | tr 'a-z' 'A-Z' # Most programs, like cat, easily accept stdin if they're to the right of a pipe, but sometimes you have to be specific, using a hyphen where the file argument would go: ls -l /bin/bash /bin/date /bin/cp | cat -n -
  • # One of the ways to make a script recyclable is to allow the user to give input through the use of command line arguments. echo "Your first argument, referenced as $1, is $1" echo "Your second argument, referenced as $2, is $2" echo "Arguments above 9 must be referenced with curly braces, as in ${10}, which returns ${10}" echo "Return the full list of arguments with $*, which returns $*" echo "The number of arguments, $# is referenced as $#" echo "$0 returns the full path of the program you called. My name is $0" # If you want to stop in the middle of a program and take user input (typically not desirable), use the read bash built-in: read -p "Please enter three words:" WORD1 WORD2 WORD3 # -p changes from the default prompt. Each of the variables listed gets one word from the user input. If you give too few words as input, some of the variables will have null values. If you give too many words, the last variable in the list acts as a catchall for the extra words. Try it a few times. echo "Your first word was $WORD1" echo "Your second word was $WORD2" echo "Your third word was $WORD3" echo "Well, that's the end of this demo script...You can learn more from the Bash Guide for Beginners available at tldp.org.'
  • Here&apos;s a script that&apos;s designed to test whether a range of IP addresses are reachable: #!/bin/bash # pinger.sh # This script is intended to ping a group of systems, evaluate whether the down ones are already known, and assist in tracking this info. # It&apos;s also intended to demonstrate bash scripting principles. ################################################################################################# # We want two variables/piecies of information. If we have more or fewer, give an error and exit if [ $# -eq 2 ]; then # Does the ip addr match our regular expression? echo $1 | grep -qE &apos;^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-4]).(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-4]).(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-4]).([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-4])$&apos; if [ $? -eq 0 ]; then # Is the second argument 1 or 2 digits? echo $2 | grep -qE &apos;^[1-9][0-9]?$&apos; if [ $? -eq 0 ]; then echo &quot;Proceeding, using arguments to perform operation&quot; # We&apos;re good...go ahead and set the variables STARTIP=$1 IPRANGE=$2 else # This message goes to stderr echo &quot;Invalid range. Range must be between 0 and 100&quot; >&2 exit 3 fi else # This message goes to stderr echo &quot;Invalid starting ip. Use format: 192.168.0.1&quot; >&2 exit 2 fi elif [ $# -eq 0 ]; then # keep asking till we get a good ip declare -i SIGOOD=0 while [ $SIGOOD -ne 1 ]; do read -p &quot;Please enter starting ip address like this: 192.168.0.1: &quot; STARTIP # Does the entered ip address meet our goals? echo $STARTIP | grep -qE &apos;^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-4]).(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-4]).(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-4]).([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-4])$&apos; if [ $? -eq 0 ]; then declare -i SIGOOD=1 else echo &quot;Bad IP!&quot; >&1 declare -i SIGOOD=0 fi done # keep asking till we get a number in range declare -i IRGOOD=0 while [ $IRGOOD -ne 1 ]; do read -p &quot;Please enter the number of ip addresses you&apos;d like to check: &quot; IPRANGE # is it okay? echo $IPRANGE | grep -qE &apos;^[1-9][0-9]?$&apos; if [ $? -eq 0 ]; then declare -i IRGOOD=1 else # error message echo &quot;Bad Range number: please enter a number between 0 and 100, exclusive&quot; >&1 declare -i IRGOOD=0 fi done echo &quot;Proceeding, using arguments to perform operation&quot; else # This message goes to stderr echo &quot;Incorrect number of arguments. Either use no arguments, or this format: $0 &quot; >&2 exit 1 fi # Alright, at this point we should have a valid IP address, and a range from 1-99 # Let&apos;s separate the octets of our IP Address declare -i OCT1=$(echo $STARTIP | sed &apos;s/.[0-9]*.[0-9]*.[0-9]*$//&apos; ) declare -i OCT2=$(echo $STARTIP | sed &apos;s/.[0-9]*.[0-9]*$//&apos; | sed &apos;s/^[0-9]*.//&apos;) declare -i OCT3=$(echo $STARTIP | sed &apos;s/^[0-9]*.[0-9]*.//&apos; | sed &apos;s/.[0-9]*$//&apos; ) declare -i OCT4=$(echo $STARTIP | sed &apos;s/^[0-9]*.[0-9]*.[0-9]*.//&apos; ) # Let&apos;s write our actual ping script declare -i PINGS=0 while [ $PINGS -lt $IPRANGE ]; do PINGS+=1 # ping the next address. Don&apos;t display any output. IP=$OCT1.$OCT2.$OCT3.$OCT4 ping -c 1 -W 1 $IP &> /dev/null if [ $? -eq 0 ]; then echo &quot;$IP is UP!&quot; # is it in our maintenance record yet? elif grep &quot;$IP &quot; /tmp/maintenance.txt; then LISTED=1 # If not, put it there else echo &quot;$IP is DOWN! Listing in /tmp/maintenance.txt.&quot; >&1 # Put it in the maintenance record echo &quot;$IP is DOWN! Listed $(date -I)&quot; >> /tmp/maintenance.txt # Set the following variable to set email recipient: TOADDR=nobody@example.com # Uncomment the following line to send a warning email #echo &quot;$IP is DOWN!&quot; | mail -s &quot;System Unreachable&quot; $TOADDR fi # Now we&apos;ll advance to the next IP Address OCT4+=1 # If any of the octets hits 255, we&apos;ll go back to 1 and up the next octet if [ $OCT4 -ge 255 ]; then OCT4=1 OCT3+=1 if [ $OCT3 -ge 255 ]; then OCT3=1 OCT2+=1 if [ $OCT2 -ge 255 ]; then OCT2=1 OCT1+=1 if [ $OCT1 -ge 255 ]; then echo &quot;Range too high...Created an invalid IP address. Exiting.&quot; exit 4 fi fi fi fi done Lastly, here&apos;s a backup script: #!/bin/bash # backup.sh # This script will backup your /etc/sysconfig directory, unless you specify some other directory as a command-line argument. ####################################################################################################### # Make sure /backups exists mkdir /backups &> /dev/null # Creating a function allows us to keep our code tidy and reusable. function DOBACKUP { # This references the first argument to the function, not the script THEDIR=$1 # Use a little regexp to strip out the unwanted information. DIRSIZE=$(du -sh $THEDIR 2>&1 | grep -v &apos;Permission denied&apos; | grep -v &quot;cannot access&quot; | sed -e &apos;s/ */.*//&apos;) # Make our script notice permission issues du -sh $THEDIR 2>&1 | grep -q &apos;Permission denied&apos; && ERRMSG+=&quot;Some directories beneath $THEDIR were not accessible. You may want to re-run this script against $THEDIR with root privileges. <br /> &quot; du -sh $THEDIR 2>&1 | grep -q &apos;cannot access&apos; if [ $? -eq 0 ]; then ERRMSG+=&quot;$THEDIR is inaccessible. This probably means $THEDIR doesn&apos;t exist. <br /> &quot; return fi echo -e &quot;$THEDIR uncompressed size: $DIRSIZE&quot; # Set our archive name BAKNAME=$(basename $THEDIR)-$(date +&quot;%Y-%m-%d-%H-%M-%S&quot;).tar.gz #echo -e &quot;Backing up $THEDIR to /backups/$BAKNAME...please wait...&quot; # Perform the backup tar -czf /backups/$BAKNAME $THEDIR &> /dev/null RESULT=$? case $RESULT in # 0 from tar is success, while 2 simply means that some subdirs were Permission denied - something we already documented for the user 0|2) ARCSIZE=$(ls -lh /backups/$BAKNAME | awk &apos;{ print $5; }&apos;) echo -e &quot;$THEDIR backup complete! Archive size is $ARCSIZE&quot; ;; *) ERRMSG+=&quot;$THEDIR backup failed!&quot; ;; esac } if [ $# -gt 0 ]; then # If the user gave one or more arguments, loope through the directories they listed for NEXTDIR in $*; do DOBACKUP $NEXTDIR done else # If not, use our default, /etc/sysconfig DOBACKUP /etc/sysconfig fi # Echo on stderr any error messages that were recorded if [ -n &quot;${ERRMSG}&quot; ]; then # heretext is an easy way of spitting out multi-line text in a wysiwyg sort of way cat >&2 &lt;&2 fi System V init scripts are different from normal bash scripts in that they are typically intended to control background services, mostly at boot and shutdown time. As such, they need a few extra pieces of information, and a particular format. SysV scripts typically reside in /etc/init.d/. Check there for better examples. Here is a sample SysV script: #!/bin/bash # # sample Startup script for the sample service # # chkconfig: - 80 20 2345 # the above line would tell “chkconfig --add sample” that it should set sample to automatically start in runlevels 2, 3, 4 and 5, and that it should be in the 80 th tier of services to start at boot time, and the 20 th tier of services to stop at shutdown time. # description: This is a sample init script, not really intended for use on a system. You&apos;ll notice there are a couple extra comments compared to a normal init script. # processname: sampled # config: /etc/sample/sample.conf start() { echo “This would normally be the function that starts the service” } stop() { echo “This would normally be the function that stops the service” } reload() { echo “If possible, this would probably be a rehup” } # Run the appropriate function(s) depending on the argument given case “$1” in start) start ;; stop) stop ;; restart) stop start ;; reload) reload ;; esac

Bash Programming Bash Programming Presentation Transcript

  • Bash Programming Andrew Vandever RHC{T,E,I,X} [email_address] http://avcomp.net
  • Bash Programming Andrew Vandever Technical Training Resources Scripting Basics Expansion Loops Conditional Statements I/O Redirection User Input System V Init Scripts
  • Scripting Basics
    • Bourne Again Shell
    • Posix-Compliant
    • Interactive Shell ↔ Script
    • Automation, Repetition
    • NOT exhaustive
    • NOT covering external programs
    • Check out guides on tldp.org
  • Scripting Basics
    • Magic - #!/bin/bash
    • Add -x to debug
    • Comments - #
    • ; = EOL
    • escapes EOL
    • chmod +x
    • echo $PATH
  • Expansion
    • VARIABLE=value
      • All-caps not needed, just recommended
    • echo ${VARIABLE}
      • {} not always needed, but stops problems
      • $ is necessary
    • declare -i VARIABLE=value
      • Specifies variable is an integer
  • Expansion
    • Math
      • $[]
      • $(())
    • Curly-brace expansion
      • {items,in,list}toexpand
      • {a..z}range
  • Expansion
    • Command Substitution
      • $(command)
      • `command`
    • Tilde
      • ~ = your home directory
      • ~user = user's home directory
    • Wildcard globs
      • man 7 glob
  • Expansion
    • Prevention
      • escapes a single special character
      • “” escapes all but $, ` and
      • '' escapes all special characters
  • Loops
    • for VAR in items in list; do commands; done
    • exit status - $?
    • test - [ condition ]
    • while [ test ]; do commands; done
    • until [ test ]; do commands; done
  • Conditional Statements
    • Operators
      • &&
      • ||
    • if [ test ]; then commands
    • elif [ test ]; then commands
    • else commands
    • fi
  • Conditional Statements
    • case VAR in
    • option)
      • commands
      • ;;
    • *)
      • commands
      • ;;
    • esac
  • I/O Redirection
    • Channel 2 – STDERR
      • 2>
      • 2>>
    • Channel 1 - STDOUT
      • >
      • >>
      • |
  • I/O Redirection
    • Combining 1 and 2
      • &>
      • &>>
      • 2>&1
      • >&2
    • Channel 0 – STDIN
      • <
      • -
  • User Input
    • Arguments
      • $1, $2, ${10}, etc.
      • $*
      • $#
      • $0
    • read
      • Force your users to give you the data you want in the form you want
  • System V Scripts
    • Help start and stop background processes at boot time and shutdown
    • Necessary components:
      • chkconfig line to tell position, default levels
      • case statement to accept typical service arguments – start, stop, restart, status
    • Examples in /etc/init.d
  • Bash Programming
    • Many more possibilities!
    • Find more info in guides at tldp.org