Bash scripting Guides
The main place for my own scripts is /vm/video/scripts and /ssa/TV/scripts, or now linux/scripts on Sigillo and backups. For examples of bash scripts, see sigillo:/usr/share/doc/bash/examples. Shell Script Frontend Tools The package ssft has these functions:
For usage, see /usr/share/doc/ssft/examples/ssft-test.sh and run it for a demo -- very cool and simple. Display steps and variables bash -v ./test See exit status In a directory where there's a file called x, but no file called xx:
Rename files (source) You want to rename all files in a directory by adding a new extension. Try to use the xargs command: ls | xargs -t -i mv {} {}.old xargs reads each item from the ls ouput and executes the mv
command. The '-i' option tells xargs to replace '{}' with the name of
each item. The '-t' option instructs xargs to print the command before
executing it. The tv capture scripts (channel, rsync-nfs, and others) need a
sorted list of drives called 'tv'-something. They use the -d switch to
indicate slash is the divider for finding the fourth column: for i in $(df | cut -d'/' -f4 | grep tv | sort); do These scripts also need to tell the user how much space is left on a drive, and use the -b switch "($(df -h /$DRV | grep $DRV | cut -b 35-38)B free)"We can also get a more precise measure of the space left, as now used in rsync-cc: if [ $DISK = "tvspare" -o $(df /$DISK | grep $DISK | cut -b 32-40) -lt 10000 ]This lets you skip drives have less than 10,000 bytes on them -- in practice newly formatted drives. Find one of two strings It's often useful to be able to check whether at least one of
a list of strings is present in a file. This is accomplished with
egrep, or grep -E, and an extended regular expression. For instance,
this from rsync-nfs: # Delete the list if it contains no avi or txt files (add extensions as needed)While I've not tested it, it's likely we can also use '(txt|avi|mp4)'. To find a string that starts with a hyphen, grep '\-lt' * For loop for file in [jx]* ; dorm -f $file # Removes only files beginning with "j" or "x" in $PWD. echo "Removed file \"$file\"". done Use a list of strings to operate on in turn for box in {brazzi,lucca,pisa,prato} ; do ssh $box "sudo cp /etc/network/interfaces.ATS /etc/network/interfaces" doneRemove blank lines sed: $ sed '/^$/d' input.txt > output.txtgrep: $ grep -v '^$' input.txt > output.txtRemove line feeds tr -d '\012'tr -d '\n' <50zap > 1zap sed '/search/{N;s/\n//;}' file Add a line feed before a string sed 's/Add a line feed before a paragraph sed 's//\n /g' $FIL > $FIL.spaced Insert text at the first line of a file sed '1i\your text goes here' file_name > new_filename Append text on the last line of the file sed '$a\your text goes here' file_name > new_filename Double condition in a while loop You can make a while loop depend on a string of conditions linked by && -- this one is from the blue-serve script. I don't know how to make the countdown seconds refresh while the rest remains, so I clear the page each time! clear; n=1while [ -z "$(ifconfig -a | grep bnep0)" ] && [ $n -lt 31 ] ; do echo -e "\v\v\v\v\v\v\v\v\v\v\v\t\tThe bluetooth server is running" echo -e "\t\tPlease tell the client to connect ... $(( 31-$n ))" echo -n -e "\t\tOr press Ctrl-c to quit" sleep 1; clear; let n=$(( $n+1 )) done This one says, "if there's no bnep0 device, check that we haven't
waited more than thirty seconds" -- on the other hand, if the bnep0
device is found, forget about the wait time and exit the while loop
right away. Double conditions in an if statement Test if either a OR b is true: if [ "$r" = "Y" -o "$r" = "y" ]Test if both a AND b are true: if [ "$r" = "Y" -a "$r" = "y" ]Combine: if [ $SCORE -gt 53 -a $MP3SIZE -gt 45000000 -o $SCORE -gt 54 ] File size
Querying variables (see bash tests) Test if the user typed in y or Y: if [[ "$r" = [Yy] ]] Test if a variable has not been assigned: DRIVER="$1" Nice, no? Similarly, test if a string has some content: if [ "$(grep natsemi /proc/modules)" != "" ] if [ -n "$(grep ipw2200 /proc/modules)" ] Or if it's empty: if [ "$(grep natsemi /proc/modules)" = "" ] if [ -z "$(grep natsemi /proc/modules)" ] This one checks if a file exists (cf. tv-move and dv2xvid-mencoder): if [ -e $TAR/.mounted ]This one checks if the file doesn't exist: if [ ! -e "$file" ] # Check if file exists.Check if a file exists and is not empty (see rsync-nfs): if [ -s /tmp/rsync-$BOX-files ] Useful if the file is created by a script and may be empty. Test if a variable is an integer (finally an elegant solution):if [ "$(echo "$var" | egrep -s '^[-+]?[0-9]+$' )" ] ; then echo "this is a number" ; fiOr define a function (not tested): function is_integer {Test if a file contains a string: if grep -q string fileTest if a string contains a letter-sequence: word=Linux Directory tests Tests if a directory exists (cf. Bash FAQ): if [ -d $DIR ] This one tests if a directory doesn't exist: if test ! -d $DIR Test if a directory has files of a certain type in it -- note the ! to negate: if [ ! "$(ls *.txt 2> /dev/null)" ] List only the directories ls -p1 | grep \/ Or this one might work in some cases (not tested): find 2* -type d Which directory am I in? pwd | sed 's/.*\///' Useful in a loop that wants only directories: for DIR in $(ls -p1 | grep \/) ; do Arithmetric operations Add two numbers:let "LAST = $NUM_FILES - 1" Another arithmetric expression example: X=`expr 3 \* 2 + 4` Round up a decimal to a round number ending in zero: INT=$(printf "%1.0f" $1) NEAREST=${2:-10} while ((INT % NEAREST)); do let INT++ done echo $INTComparisons Then you have the comparisons (see http://www.faqs.org/docs/abs/HTML/comparison-ops.html): if [ $TARGET -gt $(date +%s) ] And a different type of comparison, from toMP3: if [ "$2" = -d ]; then Or this weird one (see dv2divx): if [ $VCODEC* != *-2 ] And equally, from dv2vob, if [ $# -lt 2 ]An odd one from ABSG: if echo "Next *if* is part of the comparison for the first *if*."Get a file's basename, extension, and directory name (from bash_faq; see also http://splike.com/wiki/Bash_Scripting_FAQ): # set the 'file' variable firstReading from files Pick each word in sequence from a file: for word in `cat somefile`Read a line at a time from a file: #!/bin/bashRead a line by number (see /usr/local/bin/insert-timecodes-from-file): for i in $(seq 1 `wc -l $captions | cut -d" " -f1`) ; doUser input for x Wait five seconds for the user to press any key once, unechoed (see read under man bash): read -t 5 -n 1 -sSwitchbox Set the port in a switchbox Brace expansion (see)
Generate all numbers between 1 and 99: echo {0,1,2,3,4,5,6,7,8,9}{1,2,3,4,5,6,7,8,9} Arrays # Load the filenames into an arrayClear an array element Clear an array
See also the soundmerge script. Functions #!/bin/bash List extensions only: function ext() Nice use of functions, though I don't understand how they work. Get the PID of some program ps ax | grep hotkeys | grep -v grep | awk '{ print $1 }' -- where "hotkeys" is the name of the program you want the pid for. Or put it straight into a variable, using this alias in your bash shell: pid=$(ps -ax | grep $1 | grep -v grep | awk '{ print $1 }') Then you can do stuff like "kill -HUP $pid" to reread config files. dates Read about the secret of relative dates in "info coreutils date" -- for instance, YESTERDAY=echo $(date --date="-1 day" +%Y-%m-%d)This can be pushed quite far, as in the tape-timestamp script, which adds running seconds to any past time: START=$(date +%s)Or add a number of seconds to the current time: echo $(date -d "+$LAPSED seconds"\ $(date +%F)\ $(date +%H:%M:%S) +%F_%H:%M:%S) Perhaps more usefully, convert a duration in seconds to a duration in hours and minutes: echo $(date -d "+$LAPSED seconds"\ $(date +%F) +%H\ hours,\ %M\ minutes,\ and\ %S\ seconds) Convert hours and minutes to seconds for arithmetric operations (this uses UTC): CUT1="$(echo $(date -u -d 1970-01-01\ 00:28:46 +%s))"Back to hours and minutes: echo $(date -u -d "+$CUT1 seconds"\ $(date +%F) +%M:%S)Reconstruct a base directory address from a filename (no initial or final slash): DIR="$(echo $FIL | sed -r 's/([0-9]{4})-([0-9]{2})-([0-9]{2}).*/\1\/\1-\2\/\1-\2-\3/')"Reconstruct a base directory address from a date (includes initial and final slash): DIR="$(echo $DATE | sed -r 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\/\1\/\1-\2\/\1-\2-\3\//')"Note that the "-r" (regex) switch doesn't work with the sed on OSX; use gsed. Remove the middle of a string -- any content, fixed number of characters -- use (.{n}) -- which makes sed into a fancy cut: echo " 422198868 2007-05-01 14:06 2007-05-01_1300_MSNBC_Tucker_Carlson.avi" | sed -r 's/([0-9\ ]{11})(.{17})(.*)/\1\3/' Switch two strings around (keep the initial tab for the first, but not the second), and leave out the middle strings (used with check-cc-backlog to generate a tab-delimited file of all word counts): echo " 4324 Total number of words on 2007-07-10" | sed -r s/'([0-9\t]{5}).*([0-9-]{10})/\2\1/'
Suppress output selectively Redirect the error message (idenfied by '2'), but not the value (identified by '1'): SCCORE2="$( ssh $1 "cat /$2/$DDIR/$i 2> /dev/null" )" The second example redirects both. cmp -- Compare two files if cmp a b &> /dev/null # Suppress output.
sed Replacements using variables -- don't quote:
Replace only matches on lines starting with 200 sed '/200/s/XXX/KCET/' filenameReplace only matches on lines that contains an expanded variable $ID sed /$ID/s/Tavis/Lavis/ filenameReplace the first twenty characters with $STEM in lines that start with 200: sed s/^200................./$STEM/ Prepend the string Howdy to lines that start with a 2, or $STEM_ to lines that contain $PROG Remove _$PROG from the end of lines that contain $PROG: Pick the date or time out from a timestamp (from Andrey): echo 2007-01-01_1930_KNBC_Access_Hollywood_2007-01-01_19:48:13 | sed -r 's/([0-9]{4}-[0-9]{2}-[0-9]{2}).*/\1/' Add a string inside a filename: echo "2007-01-17_0000_MSNBC_Chris_Matthews000357.png" | sed -r s/$NAM'([0]{3})'/$NAM'.img000'/Am I in a directory called z?
ed -- editing within a bash script Within a bash script, use ed to make editing changes to a file: ed filename-- the w writes and the q quits. You can practice ed from the command line. ed gives you the number of characters in the file as feedback to a write command. If you need to use a slash or space, escape it first: ,s/2005-04\//rm\ \/tv1\/2005\/2005-04\//greplaces "2005-04/" with "rm /tv1/2005/2005-04/". A comma or a percent sign (%) are equivalent, meaning "operate on every line". To match only the beginning of each line, use ^ to start the regular expression: ,s/^2005/rm\ \/tv1\/2005\/2005/gIn command-line mode, ^ means "see the current line and move to the previous" -- ed starts at the bottom of a file. To incorporate ed into a bash script, you use the << label redirector as follows: ed /tmp/$(date +%F)-deleted << EOFThe logic here is that the << EOF command tells ed to read commands from the bash script it is in until it encounters a line containing only EOF in that script. So you can give a series of editing commands that will be passed to ed rather than to bash. Notes
See man ed for details. A more powerful editor is ex, the executable version of vim. Shinn wrote, This works, but the << EOF is useful if you don't want to place the script with the commands in a different file. Besides, ed has a man page that actually explains how to use it, while vim is so huge it's hard to even get into.ex myfile < my.scriptand my.script has If you want to use ex instead of ed, you need to add a /g at the end of the %s/ line -- the rest should be the same, though in practice I couldn't make ex work. screen Issue this for some pretty cool stuff:
Also see if there's bitchx and nohup on your systems.
Process managment
Working remotely
Passing arguments from the input line
Managing debian packages dpkg --get-selections | grep -w install | awk '{print $1}' | xargs -m 10 -- apt-get -y --reinstall installSee also Commands for some examples. |
|
|
|
|||||
Maintained by Francis F. Steen, Communication Studies, University of California Los Angeles |