diff --git a/LDP/guide/docbook/abs-guide/Change.log b/LDP/guide/docbook/abs-guide/Change.log index 547c568a..d4cf56c2 100644 --- a/LDP/guide/docbook/abs-guide/Change.log +++ b/LDP/guide/docbook/abs-guide/Change.log @@ -6,8 +6,146 @@ http://personal.riverusers.com/~thegrendel/Change.log ------------------------------------------------------------------------ -Intermediate release. -Working toward version 4.0, Winterberry release. +Version 4.1, Waxberry release. +10/08/06 + +1) In the "Starting Off With a Sha-Bang" chapter: + Added Sven Mascheck's note to the footnote on magic numbers in 4.2 BSD. + (Thanks, Sven.) + +2) In "Special Characters" chapter: + At "$$" entry, in footnote, added definition of a "process." + +3) In "Here Strings" section of "Here Documents" chapter: + Added short intro example. + Thank you, Sebastian Kaminski, for the suggestion. + +4) In "Functions" chapter: + Added note about single-line functions, with warning that a semicolon + must terminate the final command in such a function. + Embedded Christopher Head's function definition snippet in S.C.'s + inline example. + +5) "System and Administrative Commands" chapter: + At "stat" entry, added in-line example script showing setting of + file-descriptive variables. + (Thank you, Joël Bourquard, for the suggestion.) + At "netstat" entry, added note about "netstat -lptu." + +6) In "Communications Commands" section of "External Commands" chapter: + At "rsync" entry, changed final paragraph to a "note." + +7) In "Text Processing" section of "External Commands" Chapter: + At "tail" entry, noted that "tail -$LINES" is now deprecated, + and corrected examples. + Also cleaned up "head" references and examples, as above. + At "tr" entry, in sidebar, removed misleading statement about mandatory + quoting of letter ranges within brackets. + (Thank you, Omair Eshkenazi, for pointing this out.) + At "nl" entry, changed "cat -n" reference to "cat -b" for clarity. + (Thank you, Omair Eshkenazi, for pointing this out.) + +8) In "Special Variable Types" section of "Introduction to Variables and + Added short definition of "child process" to note about exporting + variable to child processes. + At "shift" entry, added paragraph and short in-line example code + listing on passing a numerical parameter indicating how many positions + to shift. + +9) In "Subshells" chapter: + Added definition of "subshell" in a sidebar box. + Added in-line example, showing subshell with "ps." + Added footnote that "exec" does not fork off a subprocess/subshell. + At "dedicated environment" inline example, noted that the "exit" + only terminates the subshell, not the parent process. + Removed "note" markers from paragraph about variables in a subshell + not being visible outside the subshell. + Added note, with example, about use of "$BASH_SUBSHELL" -- + but _not_ "$SHLVL" to indicate level of nesting within a subshell. + +10) In "Loops and Branches" chapter: + Corrected minor grammar error in "findstring.sh" example. + Added footnote defining "iteration." + Added (needed!) spaces in definitions of "while" and "until" loops. + Noted that "while loop" uses previously-discussed "test brackets," + and can use double-brackets construct. + +11) In "Internal Commands and Builtins" chapter: + At "unset" entry, added string test to "unset.sh" example. + At "exit" entry, added note that this command may also terminate + a subshell. + At "exit" entry, added footnote that this command *only* terminates + the process it is running within. + At footnote to "hash" entry, gave a couple of synonyms for "algorithm." + +12) In "Internal Variables" section of "Variables Revisited" chapter: + At "$SHLVL" entry, added note that this variable not affected + by subshells. + At "$!" entry, added Matthew Sage's "hanging job" example (thank you!). + +13) In "/proc" section of "/dev and /proc" chapter: + Added "cat /proc/acpi/battery/BAT0/info" to introductory usage + examples. + Added note about controlling peripherals by sending commands to /proc. + +14) In "Miscellaneous Commands" section of "External Commands" chapter: + At "mkfifo" entry, added Omair Eshkenazi's example script (thanks!). + +15) In "Time/Date Commands" section of "External Commands" chapter: + At "batch" entry, added short definition of "batch processing." + +16) In "Manipulating Strings" section of "Variables Revisited" chapter: + At "${string%substring}" entry, added Rory Winston's usage example + (thanks!). + +17) In "Colorizing Scripts" section of "Miscellany" chapter: + Modified "Draw-box.sh" example per suggestions of Jim Angstadt + (thanks!). + +18) In "Of Zeroes and Nulls" chapter + Added comments to "ex73.sh" and "ramdisk.sh" example scripts. + +19) In "Contributed Scripts" appendix: + In "days-between.sh" example, + Corrected Gauss' Formula comment (reference date is March 1, + 1600, *not* January 1). + Corrected broken link in above comment. + (Thank you, Nick Alexeev, for the pointers.) + Added "nightly-backup.sh" example. + (Thank you, Richard Neill.) + +20) In the "Sed and Awk Micro-primer" appendix: + Added note about other "sed" delimiters, such as "%" ... + (Thank you, Omair Eshkenazi.) + +21) In "Analyzing Scripts" section of "Exercises" appendix: + Added Rory Winston's one-liner script (thanks!). + +22) In "Writing Scripts" section of "Exercises" appendix: + In EASY section, added "Self-reproducing" script. + In DIFFICULT section, + added "Cross Reference" script. + added "Square Roots" script. + +23) In "Bibliography" section: + At reference and URL for Col Needham's original IBDB scripts, + Noted that the link no longer works. + (Thank you, Colin Brace, for pointing this out.) + +24) In "Assorted Tips" section of "Miscellany" chapter: + Added "pseudo-code" entry. + +25) In "Copyright" chapter: + Added footnote stating author's intention to commit the book + to the Public Domain in 2014. + +26) Miscellaneous: + Added footnote defining "deprecate." + + + +Version 4.0, Winterberry release. +06/18/06 1) "System and Administrative Commands" chapter: Added "gnome-mount" entry. diff --git a/LDP/guide/docbook/abs-guide/Draw-box.sh b/LDP/guide/docbook/abs-guide/Draw-box.sh index 3e83dcd3..08ae4086 100644 --- a/LDP/guide/docbook/abs-guide/Draw-box.sh +++ b/LDP/guide/docbook/abs-guide/Draw-box.sh @@ -2,6 +2,7 @@ # Draw-box.sh: Drawing a box using ASCII characters. # Script by Stefano Palmeri, with minor editing by document author. +# Minor edits suggested by Jim Angstadt. # Used in the "ABS Guide" with permission. @@ -118,9 +119,9 @@ for (( c=$2; count<=$BOX_WIDTH; c++)); do done plot_char $1 $2 $CORNER_CHAR # Draw box angles. -plot_char $1 `expr $2 + $BOX_WIDTH` + -plot_char `expr $1 + $BOX_HEIGHT` $2 + -plot_char `expr $1 + $BOX_HEIGHT` `expr $2 + $BOX_WIDTH` + +plot_char $1 `expr $2 + $BOX_WIDTH` $CORNER_CHAR +plot_char `expr $1 + $BOX_HEIGHT` $2 $CORNER_CHAR +plot_char `expr $1 + $BOX_HEIGHT` `expr $2 + $BOX_WIDTH` $CORNER_CHAR echo -ne "\E[0m" # Restore old colors. diff --git a/LDP/guide/docbook/abs-guide/README b/LDP/guide/docbook/abs-guide/README index 1636235b..7d7b53fa 100644 --- a/LDP/guide/docbook/abs-guide/README +++ b/LDP/guide/docbook/abs-guide/README @@ -30,7 +30,7 @@ ex71.sh (line 7) ex71a.sh (line 8) ex71b.sh (line 22) logevents.sh (lines 29, 36-39, 44, 53, 55, 60, 64) -m4.sh (line 8) +m4.sh (line 8: "\&" --> &) pw.sh (comment in line 4) read-r.sh (lines 5, 6, 19, 26) rnd.sh (comments in lines 38, 55, 64) @@ -61,4 +61,7 @@ hash-example.sh (comment in line 3: < --> <, > --> >) quote-fetch.sh (comment in line 26: & --> &) ftpget.sh (comment in line 28) whx.sh (comment in line 259) +nightly-backup.sh (comment in line 4) In-line code block at beginning of "I/O Redirection" chapter, line 41. +In-line code block at "mkfifo" entro in "Miscellaneous Commands" section of +"External Filters, Programs and Commands" chapter. diff --git a/LDP/guide/docbook/abs-guide/abs-guide.sgml b/LDP/guide/docbook/abs-guide/abs-guide.sgml index 6e6a8c07..e1401df1 100644 --- a/LDP/guide/docbook/abs-guide/abs-guide.sgml +++ b/LDP/guide/docbook/abs-guide/abs-guide.sgml @@ -332,6 +332,7 @@ Uncomment line below to generate index. + @@ -352,19 +353,12 @@ Uncomment line below to generate index. - 4.0 - 18 June 2006 + 4.1 + 08 October 2006 - - 3.8 - 26 Feb 2006 - mc - 'BLAEBERRY' release: Minor Update. - - 3.9 15 May 2006 @@ -374,11 +368,18 @@ Uncomment line below to generate index. 4.0 - 18 May 2006 + 18 Jun 2006 mc 'WINTERBERRY' release: Major Update. + + 4.1 + 08 Oct 2006 + mc + 'WAXBERRY' release: Minor Update. + + @@ -402,7 +403,7 @@ Uncomment line below to generate index. + url="http://personal.riverusers.com/~thegrendel/abs-guide-4.1.tar.bz2"> The latest update of this document, as an archived, bzip2-ed tarball including both the SGML source and rendered HTML, may @@ -665,10 +666,14 @@ Uncomment line below to generate index. to the command interpreter indicated. The #! is actually a two-byte - Some flavors of UNIX (those based on 4.2BSD) - take a four-byte magic number, requiring + + Some flavors of UNIX (those based on 4.2 BSD) + allegedly take a four-byte magic number, requiring a blank after the ! -- - #! /bin/sh. + #! /bin/sh. However, + according to Sven Mascheck, this is probably a myth. + magic number @@ -1709,11 +1714,18 @@ echo $var2 # 23skidoo The $$ variable holds the process ID - A PID, or - process ID, is a number assigned - to a running process. The PIDs - of running processes may be viewed with a ps command. + + A PID, or + process ID, is a number assigned + to a running process. The PIDs + of running processes may be viewed with a ps command. + + + Definition: A process is + an executing program, sometimes referred to as a + job. + of the script in which it appears. @@ -3294,7 +3306,8 @@ arch=$(uname -m) the export command. - A script can export variables only + + A script can export variables only to child processes, that is, only to commands or processes which that particular script initiates. A script invoked from the command line cannot @@ -3302,7 +3315,11 @@ arch=$(uname -m) Child processes cannot export variables back to the parent processes that spawned - them. + them. + Definition: A child process + is a subprocess launched by another process, its + parent. + --- @@ -3320,7 +3337,8 @@ arch=$(uname -m) line: $0, $1, $2, $3 . . . - $0 is the name of the script itself, + $0 is + the name of the script itself, $1 is the first argument, $2 the second, $3 the third, and so forth. @@ -3436,6 +3454,24 @@ fi &ex19; + The shift command can take a numerical + parameter indicating how many positions to shift. + + #!/bin/bash +# shift-past.sh + +shift 3 # Shift 3 positions. +# n=3; shift $n +# Has the same effect. + +echo "$1" + +exit 0 + + +$ sh shift-past.sh 1 2 3 4 5 +4 + The shift command works in a similar fashion on parameters passed to a function. See -a file exists This is identical in effect to -e. - It has been deprecated, and its use is + It has been deprecated, + + + Per the 1913 edition of Webster's + Dictionary: + Deprecate +. . . + +To pray against, as an evil; +to seek to avert by prayer; +to desire the removal of; +to seek deliverance from; +to express deep regret for; +to disapprove of strongly. + + + and its use is discouraged. @@ -4954,12 +5006,12 @@ home=/home/bozo -z - string is null, that is, has zero length + string is null, that is, has zero length -n - string is not null. + string is not null. The -n test absolutely requires that the string be quoted within the @@ -5425,7 +5477,10 @@ echo "a = $a" # a = -2147483648 As of version 2.05b, Bash supports 64-bit integers. - Bash does not understand floating point arithmetic. It + + + + Bash does not understand floating point arithmetic. It treats numbers containing a decimal point as strings. @@ -6774,7 +6829,7 @@ echo "Last command argument processed = $last_cmd_arg" - $SHLVL + $SHLVL $SHLVL @@ -6785,9 +6840,19 @@ echo "Last command argument processed = $last_cmd_arg" shell level - Shell level, how deeply Bash is nested. If, - at the command line, $SHLVL is 1, then in a script it will - increment to 2. + + + Shell level, how deeply Bash is nested. If, + at the command line, $SHLVL is 1, then in a script it will + increment to 2. + + This variable is + not affected by + subshells. Use $BASH_SUBSHELL when you + need an indication of subshell nesting. + + @@ -7166,6 +7231,8 @@ echo $! >> "$LOG" # Thank you, Jacques Lederer, for suggesting this. + Using $! for job control: + possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; } # Forces completion of an ill-behaved program. @@ -7175,6 +7242,29 @@ echo $! >> "$LOG" + Or, alternately: + + + # This example by Matthew Sage. +# Used with permission. + +TIMEOUT=30 # Timeout value in seconds +count=0 + +possibly_hanging_job & { + while ((count < TIMEOUT )); do + eval '[ ! -d "/proc/$!" ] && ((count = TIMEOUT))' + # /proc is where information about running processes is found. + # "-d" tests whether it exists (whether directory exists). + # So, we're waiting for the job in question to show up. + ((count++)) + sleep 1 + done + eval '[ -d "/proc/$!" ] && kill -15 $!' + # If the hanging job is running, kill it. +} + + @@ -7639,10 +7729,29 @@ echo ${stringZ##a*C} # abc + Strips shortest match of $substring from back of $string. + + For example: + # Rename all filenames in $PWD with "TXT" suffix to a "txt" suffix. +# For example, "file1.TXT" becomes "file1.txt" . . . + +SUFF=TXT +suff=txt + +for i in $(ls *.$SUFF) +do + mv -f $i ${i%.$SUFF}.$suff + # Leave unchanged everything *except* the shortest pattern match + #+ starting from the right-hand-side of the variable $i . . . +done ### This could be condensed into a "one-liner" if desired. + +# Thank you, Rory Winston. + + @@ -8704,7 +8813,14 @@ echo $? # 1 Loops A loop is a block of code that - iterates (repeats) a list of commands + iterates + + Iteration: Repeated + execution of a command or group of commands while a + given condition holds, or until a given condition is + met. + + a list of commands as long as the loop control condition is true. @@ -8942,20 +9058,28 @@ echo $? # 1 while - condition + condition do -  command +  command(s) done + The bracket construct in a while + loop is nothing more than our old friend, + the test brackets + used in an if/then test. In fact, + a while loop can legally use the + more versatile double brackets + construct (while [[ condition ]]). + As is the case with for loops, placing the do on the same line as the condition test requires a semicolon. while - condition + condition ; do @@ -9035,9 +9159,9 @@ echo $? # 1 until - condition-is-true + condition-is-true do -  command +  command(s) done @@ -9051,7 +9175,7 @@ echo $? # 1 until - condition-is-true + condition-is-true ; do @@ -10290,10 +10414,10 @@ let "z += 3" # Quotes permit the use of spaces in variable assignment. is a command contained within the Bash tool set, literally built in. This is either for performance reasons -- builtins execute faster than external - commands, which usually require forking off a separate process - -- or because a particular builtin needs direct access to the - shell internals. - + commands, which usually require forking off + a separate process -- or because a particular builtin needs + direct access to the shell internals. + @@ -10389,6 +10513,7 @@ echo $a a terminal newline, but the option suppresses this. + An echo can be used to feed a sequence of commands down a pipe. @@ -11351,8 +11476,13 @@ shift $(($OPTIND - 1)) exit - Unconditionally terminates a script. The - exit command may optionally take an + Unconditionally terminates a script. + Technically, an + exit only terminates the + process (or shell) in which it is running, + not the parent + process. + The exit command may optionally take an integer argument, which is returned to the shell as the exit status of the script. It is good practice to end all but the @@ -11365,6 +11495,10 @@ shift $(($OPTIND - 1)) the exit. This is equivalent to an exit $?. + An exit command may also be used to + terminate a subshell. + @@ -11385,12 +11519,13 @@ shift $(($OPTIND - 1)) a command, it forks off a child process to actually execute the command. Using the exec builtin, the shell does not fork, - and the command exec'ed replaces the shell. When used in - a script, therefore, it forces an exit from the script when - the exec'ed command terminates. - Unless the exec is used - to reassign file - descriptors. + and the command exec'ed replaces + the shell. When used in a script, therefore, it forces an + exit from the script when the exec'ed + command terminates. + Unless the exec is used + to reassign file + descriptors. @@ -11645,7 +11780,9 @@ done creating lookup keys for data stored in a table. The data items themselves are scrambled to create keys, using one of - a number of simple mathematical algorithms. + a number of simple mathematical + algorithms (methods, or + recipes). An advantage of hashing is that it is fast. A disadvantage is that collisions -- @@ -11978,7 +12115,7 @@ wait kill. Sometimes, a kill -15 works. A zombie process, that is, a child process that has terminated, but that - the parent process + the parent process has not (yet) killed, cannot be killed by a logged-on user -- you can't kill something that is already dead -- but init will generally clean it up @@ -12825,7 +12962,9 @@ find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \ works. And even when xargs is not strictly necessary, it can speed up execution of a command - involving batch processing of multiple files. + involving batch-processing of multiple + files. Normally, xargs reads from stdin or from a pipe, but it can also be given the output of a file. @@ -13287,11 +13426,21 @@ OneYearAgo=$(date --date='1 year ago') at + The batch job control command is similar to at, but it runs a command list when the system load drops below .8. Like at, it can read commands from a file with the option. + + + + + The concept of batch processing + dates back to the era of mainframe computers. It means + running a set of commands without user intervention. + + @@ -13783,18 +13932,25 @@ done To list a specific line of a text file, pipe the output of - head to tail -1. - For example head -8 database.txt | tail - -1 lists the 8th line of the file + head to tail -n 1. + For example head -n 8 database.txt | tail + -n 1 lists the 8th line of the file database.txt. To set a variable to a given block of a text file: - var=$(head -$m $filename | tail -$n) + var=$(head -n $m $filename | tail -n $n) # filename = name of file # m = from beginning of file, number of lines to end of block # n = number of lines to set variable to (trim from end of block) + + Newer implementations of tail + deprecate the older tail -$LINES + filename usage. The standard tail -n $LINES + filename is correct. + + See also , and . @@ -14108,8 +14264,9 @@ Here is some text. sed Non-interactive stream editor, permits using - many ex commands in batch mode. It - finds many uses in shell scripts. + many ex commands in batch mode. It finds many + uses in shell scripts. @@ -14295,8 +14452,7 @@ tr -d 0-9 <filename variants. The BSD version does not use brackets (tr a-z A-Z), but the SysV one does (tr '[a-z]' '[A-Z]'). The GNU version - of tr resembles the BSD one, so quoting - letter ranges within brackets is mandatory. + of tr resembles the BSD one. @@ -14435,7 +14591,7 @@ tr -d 0-9 <filename stdin. The output of nl is very similar to - cat -n, however, by default + cat -b, since, by default nl does not list blank lines. @@ -15201,7 +15357,8 @@ function write_utf8_string { Linux/UNIX distribution. The option causes - file to run in batch mode, to read from + file to run in batch mode, to read from a designated file a list of filenames to analyze. The option, when used on a compressed target file, forces an attempt to analyze the uncompressed @@ -15966,9 +16123,9 @@ gzip -cd patchXX.gz | patch -p0 mail user agents (such as pine or kmail) normally handle this automatically, these particular - utilities permit manipulating such attachments manually - from the command line or in a batch by means of a shell - script. + utilities permit manipulating such attachments manually from + the command line or in batch + processing mode by means of a shell script. @@ -16805,13 +16962,15 @@ wget -r ftp://ftp.xyz24.net/~bozo/project_files/ -O $SAVEFILE Updating FC4 &fc4upd; + + See also . - Using rcp, Using rcp, rsync, and similar utilities with security implications in a shell script may not be advisable. Consider, instead, using ssh, scp, - or an expect script. + or an expect script. @@ -17661,6 +17820,36 @@ done Typically, one process writes to the FIFO, and the other reads from it. See . + + + #!/bin/bash +# This short script by Omair Eshkenazi. +# Used in ABS Guide with permission (thanks!). + +mkfifo pipe1 +mkfifo pipe2 + +(cut -d' ' -f1 | tr "a-z" "A-Z") >pipe2 <pipe1 & +ls -l | tr -s ' ' | cut -d' ' -f3,9- | tee pipe1 | +cut -d' ' -f2 | paste - pipe2 + +rm -f pipe1 +rm -f pipe2 + +# No need to kill background processes when script terminates (why not?). + +exit $? + +Now, invoke the script and explain the output: +sh mkfifo-example.sh + +4830.tar.gz BOZO +pipe1 BOZO +pipe2 BOZO +mkfifo-example.sh BOZO +Mixed.msg BOZO + + @@ -18048,7 +18237,7 @@ esac soundfile.au changes a WAV sound file into a (Sun audio format) AU sound file. - Shell scripts are ideally suited for batch processing + Shell scripts are ideally suited for batch-processing sox operations on sound files. For examples, see the Linux Radio @@ -19268,6 +19457,46 @@ exit 0 + In a script, you can use stat to extract + information about files (and filesystems) and set variables + accordingly. + + + #!/bin/bash +# fileinfo2.sh + +# Per suggestion of Joël Bourquard and . . . +# http://www.linuxquestions.org/questions/showthread.php?t=410766 + + +FILENAME=testfile.txt +file_name=$(stat -c%n "$FILENAME") # Same as "$FILENAME" of course. +file_owner=$(stat -c%U "$FILENAME") +file_size=$(stat -c%s "$FILENAME") +# Certainly easier than using "ls -l $FILENAME" +#+ and then parsing with sed. +file_inode=$(stat -c%i "$FILENAME") +file_type=$(stat -c%F "$FILENAME") +file_access_rights=$(stat -c%A "$FILENAME") + +echo "File name: $file_name" +echo "File owner: $file_owner" +echo "File size: $file_size" +echo "File inode: $file_inode" +echo "File type: $file_type" +echo "File access rights: $file_access_rights" + +exit 0 + +sh fileinfo2.sh + +File name: testfile.txt +File owner: bozo +File size: 418 +File inode: 1730378 +File type: regular file +File access rights: -rw-rw-r-- + @@ -19323,6 +19552,12 @@ exit 0 unix 3 [ ] STREAM CONNECTED 4513 . . . + A netstat -lptu shows sockets that are listening + to ports, and the associated processes. This can be useful + for determining whether a computer has been hacked or + compromised. + @@ -21007,7 +21242,7 @@ then #+ Prevent other programs & scripts #!/bin/bash # This script is for illustrative purposes only. -# Run it at your own peril -- it *will* freeze your system. +# Run it at your own peril -- it WILL freeze your system. while true # Endless loop. do @@ -22265,7 +22500,7 @@ echo a111b | gawk '/a1+b/' Here Documents - Aldous Huxley, Island + Aldous Huxley, Island Here and now, boys. @@ -22575,6 +22810,24 @@ echo "This line had better not echo." # Follows an 'exit' command.stdin of COMMAND. + As a simple example, consider this alternative to the echo-grep construction. + + + # Instead of: +if echo "$VAR" | grep -q txt # if [[ $VAR = *txt* ]] +# etc. + +# Try: +if grep -q "txt" <<< "$VAR" +then + echo "$VAR contains the substring sequence \"txt\"" +fi +# Thank you, Sebastian Kaminski, for the suggestion. + + + Or, in combination with read: + String="This is a string of words." @@ -23011,27 +23264,83 @@ exec 3>&- # Now close it for the remainder of the s - Running a shell script launches another instance of the - command processor. Just as your commands are interpreted at the - command line prompt, similarly does a script batch process a list - of commands in a file. Each shell script running is, in effect, - a subprocess of the parent shell, - the one that gives you the prompt at the console or in an - xterm window. - - A shell script can also launch subprocesses. These - subshells let the script do - parallel processing, in effect executing multiple subtasks - simultaneously. + Running a shell script launches a new process, a + subshell. + Definition: A subshell is a + process launched by a shell (or shell + script). + + + A subshell is a separate instance of the command processor + -- the shell that gives you the prompt at + the console or in an xterm window. Just + as your commands are interpreted at the command line prompt, + similarly does a script batch-process a list of + commands. Each shell script running is, in effect, a subprocess + (child process) of the parent shell. + + + A shell script can itself launch subprocesses. These + subshells let the script do + parallel processing, in effect executing multiple subtasks + simultaneously. + + + + #!/bin/bash +# subshell-test.sh + +( +# Inside parentheses, and therefore a subshell . . . +while [ 1 ] # Endless loop. +do + echo "Subshell running . . ." +done +) + +# Script will run forever, +#+ or at least until terminated by a Ctl-C. + +exit $? # End of script (but will never get here). + + + +Now, run the script: +sh subshell-test.sh + +And, while the script is running, from a different xterm: +ps -ef | grep subshell-test.sh + +UID PID PPID C STIME TTY TIME CMD +500 2698 2502 0 14:26 pts/4 00:00:00 sh subshell-test.sh +500 2699 2698 21 14:26 pts/4 00:00:24 sh subshell-test.sh + + ^^^^ + +Analysis: +PID 2698, the script, launched PID 2699, the subshell. + +Note: The "UID ..." line would be filtered out by the "grep" command, +but is shown here for illustrative purposes. + + + In general, an external command in a script forks - off a subprocess, whereas a Bash a subprocess, + An external command invoked with an exec does not + (usually) fork off a subprocess / subshell. + whereas a Bash builtin does not. For this reason, builtins execute more quickly than their external command equivalents. - + + <anchor id="subshellparens1">Command List in @@ -23047,12 +23356,12 @@ exec 3>&- # Now close it for the remainder of the s </varlistentry> </variablelist> - <note><para><anchor id="parvis">Variables in a subshell are - <emphasis>not</emphasis> visible outside the block of code - in the subshell. They are not accessible to the <link - linkend="forkref">parent process</link>, to the shell - that launched the subshell. These are, in effect, <link - linkend="localref">local variables</link>.</para></note> + <para><anchor id="parvis">Variables in a subshell are + <emphasis>not</emphasis> visible outside the block of code + in the subshell. They are not accessible to the <link + linkend="forkref">parent process</link>, to the shell + that launched the subshell. These are, in effect, <link + linkend="localref">local variables</link>.</para> <example id="subshell"> <title>Variable scope in a subshell @@ -23060,7 +23369,28 @@ exec 3>&- # Now close it for the remainder of the s See also . - + + + + While the $BASH_SUBSHELL + internal variable indicates the nesting level of a + subshell, the $SHLVL + variable shows no change within + a subshell. + + +echo " \$BASH_SUBSHELL outside subshell = $BASH_SUBSHELL" # 0 + ( echo " \$BASH_SUBSHELL inside subshell = $BASH_SUBSHELL" ) # 1 + ( ( echo " \$BASH_SUBSHELL inside nested subshell = $BASH_SUBSHELL" ) ) # 2 +# ^ ^ *** nested *** ^ ^ + +echo + +echo " \$SHLVL outside subshell = $SHLVL" # 3 +( echo " \$SHLVL inside subshell = $SHLVL" ) # 3 (No change!) + + + + Directory changes made in a subshell do not carry over to the parent shell. @@ -23084,13 +23414,18 @@ COMMAND3 shift 5 COMMAND4 COMMAND5 - exit 3 # Only exits the subshell. + exit 3 # Only exits the subshell! ) # The parent shell has not been affected, and the environment is preserved. COMMAND6 COMMAND7 - One application of this is testing whether a variable is defined. + As seen here, the exit + command only terminates the subshell in which it is running, + not the parent shell or script. + + One application of such a dedicated environment + is testing whether a variable is defined. if (set -u; : $variable) 2> /dev/null then echo "Variable is set." @@ -23101,9 +23436,9 @@ fi # Variable has been set in current script, # Could also be written [[ ${variable-x} != x || ${variable-y} != y ]] # or [[ ${variable-x} != x$variable ]] # or [[ ${variable+x} = x ]] -# or [[ ${variable-x} != x ]] +# or [[ ${variable-x} != x ]] - Another application is checking for a lock file: + Another application is checking for a lock file: if (set -C; : > lock_file) 2> /dev/null then : # lock_file didn't exist: no user running the script @@ -23116,6 +23451,7 @@ fi #+ with modifications by Paulo Marcel Coelho Aragao. + + Processes may execute in parallel within different subshells. This permits breaking a complex task into subcomponents @@ -23148,6 +23484,7 @@ fi { command1; command2; command3; . . . commandN; } + @@ -23480,6 +23817,18 @@ x=x < <(:) + + A function may be compacted into a single + line. + + fun () { echo "This is a function"; echo; } + + In this case, however, a semicolon + must follow the final command in the function. + + fun () { echo "This is a function"; echo } # Error! + + Functions are called, triggered, simply by invoking their names. @@ -23569,7 +23918,18 @@ NO_EXIT=1 # Will enable function definition below. exit # Invokes "exit ()" function, not "exit" builtin. -# Thanks, S.C. + + +# Or, similarly: +filename=file1 + +[ -f "$filename" ] && +foo () { rm -f "$filename"; echo "File "$filename" deleted."; } || +foo () { echo "File "$filename" not found."; touch bar; } + +foo + +# Thanks, S.C. and Christopher Head @@ -24764,6 +25124,23 @@ exit 0 bash$ cat /proc/apm 1.16 1.2 0x03 0x01 0xff 0x80 -1% -1 ? + + + +bash$ cat /proc/acpi/battery/BAT0/info +present: yes + design capacity: 43200 mWh + last full capacity: 36640 mWh + battery technology: rechargeable + design voltage: 10800 mV + design capacity warning: 1832 mWh + design capacity low: 200 mWh + capacity granularity 1: 1 mWh + capacity granularity 2: 1 mWh + model number: IBM-02K6897 + serial number: 1133 + battery type: LION + OEM info: Panasonic @@ -24810,6 +25187,23 @@ then fi + + It is even possible to control certain peripherals with commands + sent to the /proc directory. + + + root# echo on > /proc/acpi/ibm/light + + + This turns on the Thinklight in certain models + of IBM/Lenovo Thinkpads. + + Of course, caution is advised when writing to /proc. + + + + The /proc directory contains subdirectories with unusual numerical names. Every one of these names maps to the Uses of /dev/zero Like /dev/null, - /dev/zero is a pseudo file, but - it actually produces a stream of nulls (binary zeros, - not the ASCII kind). Output written to it disappears, - and it is fairly difficult to actually read the nulls from - /dev/zero, though it can be done with - od or a hex editor. The chief + /dev/zero is a pseudo device file, but + it actually produces a stream of nulls + (binary zeros, not the ASCII + kind). Output written to /dev/zero + disappears, and it is fairly difficult to actually read + the nulls from there, though it can be done with od or a hex editor. The chief use for /dev/zero is in creating an - initialized dummy file of specified length intended as a + initialized dummy file of predetermined length intended as a temporary swap file. @@ -26739,6 +27134,50 @@ grep "$word" "$file" + + + + You have a problem that you want to solve by writing a Bash + script. Unfortunately, you don't know quite where to start. + One method is to plunge right in and code those parts + of the script that come easily, and write the hard parts as + pseudo-code. + + #!/bin/bash + +ARGCOUNT=1 # Need name as argument. +E_WRONGARGS=65 + +if [ number-of-arguments is-not-equal-to "$ARGCOUNT" ] +# ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ +# Can't figure out how to code this . . . +#+ . . . so write it in pseudo-code. + +then + echo "Usage: name-of-script name" + # ^^^^^^^^^^^^^^ More pseudo-code. + exit $E_WRONGARGS +fi + +. . . + +exit 0 + + +# Later on, substitute working code for the pseudo-code. + +# Line 6 becomes: +if [ $# -ne "$ARGCOUNT" ] + +# Line 12 becomes: + echo "Usage: `basename $0` name" + + For an example of using pseudo-code, see the Square Root exercise. + + + + To keep a record of which user scripts have run @@ -27315,7 +27754,7 @@ fi For security purposes, it may be necessary to render a script unreadable. If only there were a utility to create a stripped binary executable from a script. Francisco Rosales' shc - + url="http://www.datsi.fi.upm.es/~frosal/sources/">shc -- generic shell script compiler does exactly that. Unfortunately, according to Who is this guy anyhow? The author claims no credentials or special qualifications, + In fact, the author is a school dropout and has + no credentials or qualifications. other than a compulsion to write. - Those who can, do. Those who can't... get an + Those who can, do. Those who can't . .. get an MCSE. This book is somewhat of a departure from his other major work, @@ -27754,8 +28195,8 @@ echo $a # 6 FORTRAN IV on a CDC 3800, but is not the least bit nostalgic for those days. - Living in a secluded desert community with wife and dog, - he cherishes human frailty. + Living in a secluded desert community with wife and orange cat, + he cherishes human frailty, especially his own. @@ -27967,16 +28408,16 @@ echo $a # 6 Chris Martin, Lee Maschmeyer, Bruno Haible, Wilbert Berendsen, Sebastien Godard, Bjön Eriksson, John MacDonald, Joshua Tschida, Troy Engel, Manfred Schwarb, Amit Singh, Bill Gradwohl, - David Lombard, Jason Parker, Steve Parker, Bruce W. Clare, - William Park, Vernia Damiano, Mihai Maties, Jeremy Impson, - Ken Fuchs, Frank Wang, Sylvain Fourmanoit, Matthew Walker, + David Lombard, Jason Parker, Steve Parker, Bruce W. Clare, William + Park, Vernia Damiano, Mihai Maties, Jeremy Impson, Ken Fuchs, + Frank Wang, Sylvain Fourmanoit, Matthew Sage, Matthew Walker, Kenny Stauffer, Filip Moritz, Andrzej Stefanski, Daniel Albers, Stefano Palmeri, Nils Radtke, Jeroen Domburg, Alfredo Pironti, Phil Braham, Bruno de Oliveira Schneider, Stefano Falsetto, - Chris Morgan, Walter Dnes, Linc Fessenden, Michael Iatrou, - Pharis Monalo, Jesse Gough, Fabian Kreutz, Mark Norman, Harald - Koenig, Peter Knowles, Francisco Lobo, Mariusz Gniazdowski, - Tedman Eng, Jochen DeSmet, Oliver Beckstein, Achmed Darwish, + Chris Morgan, Walter Dnes, Linc Fessenden, Michael Iatrou, Pharis + Monalo, Jesse Gough, Fabian Kreutz, Mark Norman, Harald Koenig, + Peter Knowles, Francisco Lobo, Mariusz Gniazdowski, Tedman Eng, + Jochen DeSmet, Oliver Beckstein, Achmed Darwish, Richard Neill, Omair Eshkenazi, Andreas Kühne, and David Lawyer (himself an author of four HOWTOs). @@ -28740,7 +29181,8 @@ echo $a # 6 19142fd4db9e/152249faf7593d14">original International Movie Database (IMDB) reader polling scripts, which nicely illustrate the use of awk for string parsing. + linkend="awkref">awk for string parsing. Unfortunately, + the URL link no longer works. --- @@ -29046,6 +29488,16 @@ echo $a # 6 &bashpodder; + + Nightly backup to a firewire HD + &nightlybackup; + + + + An expanded <command>cd</command> command + &cdll; + + To end this section, a review of the basics . . . and more. @@ -29053,10 +29505,6 @@ echo $a # 6 &basicsreviewed; - - An expanded <command>cd</command> command - &cdll; - @@ -29984,6 +30432,13 @@ pattern=BEGIN but retaining the blank line(s) between paragraphs. + The usual delimiter that sed uses is + /. However, sed allows other + delimiters, such as %. This is useful when + / is part of a replacement string, as in a file pathname. + See and . + A quick way to double-space a text file is sed G filename. @@ -30096,6 +30551,7 @@ awk '{print $1 $5 $6}' $filename + @@ -31623,24 +32079,45 @@ exit 0 --- - A reader sent in the following code snippet. + A reader sent in the following code snippet. + while read LINE do echo $LINE done < `tail -f /var/log/messages` + - He wished to write a script tracking changes to the system log - file, /var/log/messages. Unfortunately, - the above code block hangs and does nothing - useful. Why? Fix this so it does work. (Hint: - rather than redirecting the - stdin of the loop, try a pipe.) + He wished to write a script tracking changes to the system log + file, /var/log/messages. Unfortunately, + the above code block hangs and does nothing + useful. Why? Fix this so it does work. (Hint: + rather than redirecting the + stdin of the loop, try a pipe.) --- + Analyze the following one-liner (here + split into two lines for clarity) contributed by Rory + Winston: + + + export SUM=0; for f in $(find src -name "*.java"); +do export SUM=$(($SUM + $(wc -l $f | awk '{ print $1 }'))); done; echo $SUM + + + Hint: First, break the script up into bite-sized + sections. Then, carefully examine its use of double-parentheses arithmetic, + the export command, + the find command, the + wc command, and awk. + + --- + Analyze , and reorganize it in a simplified and more logical style. See how many of the variables @@ -31681,6 +32158,17 @@ done < `tail -f /var/log/messages` + + Self-reproducing Script + + Write a script that backs itself up, that is, copies + itself to a file named backup.sh. + Hint: Use the cat command + and the appropriate positional + parameter. + + + Converting for loops to while and + + Cross Reference + + Write a script that generates a + cross-reference + (concordance) on a target file. + The output will be a listing of all word occurrences in + the target file, along with the line numbers in which + each word occurs. Traditionally, linked + list constructs would be used in such + applications. Therefore, you should investigate arrays in the course of + this exercise. is probably + not a good place to start. + + + + + Square Root + + Write a script to calculate square roots of numbers + using Newton's Method. + + The algorithm for this, expressed as a snippet of Bash + pseudo-code is: + + # (Isaac) Newton's Method for speedy extraction +#+ of square roots. + +guess = $argument +# $argument is the number to find the square root of. +# $guess is each successive calculated "guess" -- or trial solution -- +#+ of the square root. +# Our first "guess" at a square root is the argument itself. + +oldguess = 0 +# $oldguess is the previous $guess. + +tolerance = .000001 +# To how close a tolerance we wish to calculate. + +loopcnt = 0 +# Let's keep track of how many times through the loop. +# Some arguments will require more loop iterations than others. + + +while [ ABS( $guess $oldguess ) -gt $tolerance ] +# ^^^^^^^^^^^^^^^^^^^^^^^ Fix up syntax, of course. + +# "ABS" is a (floating point) function to find the absolute value + of the difference between the two terms. +# So, as long as difference between current and previous +#+ trial solution (guess) exceeds tolerance, keep looping. + +do + oldguess = $guess # Update $oldguess to previous $guess. + +# ======================================================= + guess = ( $oldguess + ( $argument / $oldguess ) ) / 2.0 +# = 1/2 ( ($oldguess **2 + $argument) / $oldguess ) +# that is, "averaging out" the square of the +#+ trial solution and the argument +#+ (in effect, splitting the error in half). +# This converges on an accurate solution +#+ with surprisingly few loop iterations . . . +#+ for arguments > $tolerance, of course. +# ======================================================= + + (( loopcnt++ ) # Update loop counter. +done + + It's a simple enough recipe, and + should be easy enough to convert into a + working Bash script. The problem, though, is that Bash + has no native support for + floating point numbers. So, the script writer needs + to use bc or possibly awk to convert the numbers and do + the calculations. It gets rather messy . . . + + + + + + Logging File Accesses @@ -32173,7 +32746,7 @@ Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612 For some ideas, see and . - Optional: Write a script that searches through a batch of + Optional: Write a script that searches through a list of e-mail messages and deletes the spam according to specified filters. @@ -32209,11 +32782,19 @@ Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612 Convert a text file to Morse code. Each character of the text file will be represented as a corresponding Morse code group of dots and dashes (underscores), separated by - whitespace from the next. For example, script - ===> ... _._. ._. .. .__. _. + whitespace from the next. For example: + Invoke the "morse.sh" script with "script" +as an argument to convert to Morse. + + +$ sh morse.sh script + +... _._. ._. .. .__. _ +s c r i p t + Hex Dump @@ -32222,6 +32803,10 @@ Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612 tabular fields, with the first field showing the address, each of the next 8 fields a 4-byte hex number, and the final field the ASCII equivalent of the previous 8 fields. + The obvious followup to this is to extend the hex dump + script into a disassembler. Using a lookup table, or some other + clever gimmick, convert the hex values into 80x86 op + codes. @@ -32273,7 +32858,7 @@ Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612 - Word Ladders + Word Ladders A word ladder is a sequence of words, with each successive word in the sequence differing from @@ -32284,14 +32869,16 @@ Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612 vase: - mark --> park --> part --> past --> vast --> vase + +mark --> park --> part --> past --> vast --> vase + ^ ^ ^ ^ ^ - Write a script that solves word ladder - puzzles. Given a starting and an ending word, - the script will list all intermediate steps in the - ladder. Note that all - words in the sequence must be legal. + Write a script that solves word ladder puzzles. Given + a starting and an ending word, the script will list all + intermediate steps in the ladder. Note + that all words in the sequence must + be legitimate dictionary words. @@ -32708,6 +33295,11 @@ fairly detailed rundown on the Playfair Cipher and its solution methods.18 Jun 2006 WINTERBERRY release: Major reorganization. + + + 08 Oct 2006 + WAXBERRY release: Minor update. + @@ -32719,7 +33311,7 @@ fairly detailed rundown on the Playfair Cipher and its solution methods.Mirror Sites + url="http://thegrendel.150m.com/abs-guide-4.1.tar.bz2"> The latest update of this document, as an archived tarball including both the SGML source and rendered HTML, may @@ -32778,7 +33370,12 @@ fairly detailed rundown on the Playfair Cipher and its solution methods.The Advanced Bash Scripting Guide is copyright 2000, by Mendel Cooper. The author also asserts - copyright on all previous versions of this document. + copyright on all previous versions of this document. + The author intends that this book be released + into the Public Domain after a period of 14 years, that is, in 2014. + In the early years of the American republic this was the duration + statutorily granted to a copyrighted work. + This blanket copyright recognizes and protects the rights of all contributors to this document. @@ -32876,6 +33473,7 @@ C. Distribution of the work or derivative of the work in any standard OSX is a trademark registered to Apple, Inc. Yahoo is a trademark registered to Yahoo, Inc. Pentium is a trademark registered to Intel, Inc. + Thinkpad is a trademark registered to Lenovo, Inc. Scrabble is a trademark registered to Hasbro, Inc. All other commercial trademarks mentioned in the body of this work are registered to their respective owners. diff --git a/LDP/guide/docbook/abs-guide/cannon.sh b/LDP/guide/docbook/abs-guide/cannon.sh index 4909bd68..9e859922 100644 --- a/LDP/guide/docbook/abs-guide/cannon.sh +++ b/LDP/guide/docbook/abs-guide/cannon.sh @@ -44,7 +44,7 @@ PMULTIPLIER=4.0 # Scaling factor to approximate PI. get_random () { -SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }') +SEED=$(head -n 1 /dev/urandom | od -N 1 | awk '{ print $2 }') RANDOM=$SEED # From "seeding-random.sh" #+ example script. let "rnum = $RANDOM % $DIMENSION" # Range less than 10000. diff --git a/LDP/guide/docbook/abs-guide/days-between.sh b/LDP/guide/docbook/abs-guide/days-between.sh index abd8d846..6144f3d1 100644 --- a/LDP/guide/docbook/abs-guide/days-between.sh +++ b/LDP/guide/docbook/abs-guide/days-between.sh @@ -61,7 +61,7 @@ strip_leading_zero () # Better to strip possible leading zero(s) day_index () # Gauss' Formula: -{ # Days from Jan. 3, 1600 to date passed as param. +{ # Days from Mar. 1, 1600 to date passed as param. day=$1 month=$2 @@ -80,7 +80,7 @@ day_index () # Gauss' Formula: let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr + $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM" # For an in-depth explanation of this algorithm, see - #+ http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm + #+ http://weblogs.asp.net/pgreborio/archive/2005/01/06/347968.aspx echo $Days diff --git a/LDP/guide/docbook/abs-guide/ex2.sh b/LDP/guide/docbook/abs-guide/ex2.sh index 0e9af4d1..81c5a591 100644 --- a/LDP/guide/docbook/abs-guide/ex2.sh +++ b/LDP/guide/docbook/abs-guide/ex2.sh @@ -67,8 +67,8 @@ fi # Doublecheck if in right directory, before messing with log file. -tail -$lines messages > mesg.temp # Saves last section of message log file. -mv mesg.temp messages # Becomes new log directory. +tail -n $lines messages > mesg.temp # Saves last section of message log file. +mv mesg.temp messages # Becomes new log directory. # cat /dev/null > messages diff --git a/LDP/guide/docbook/abs-guide/ex25.sh b/LDP/guide/docbook/abs-guide/ex25.sh index ccfc4fb1..00edf220 100644 --- a/LDP/guide/docbook/abs-guide/ex25.sh +++ b/LDP/guide/docbook/abs-guide/ex25.sh @@ -4,6 +4,8 @@ var0=0 LIMIT=10 while [ "$var0" -lt "$LIMIT" ] +# ^ ^ +# Spaces, because these are "test-brackets" . . . do echo -n "$var0 " # -n suppresses newline. # ^ Space, to separate printed out numbers. diff --git a/LDP/guide/docbook/abs-guide/ex41.sh b/LDP/guide/docbook/abs-guide/ex41.sh index 5fd1872d..f6087c7c 100644 --- a/LDP/guide/docbook/abs-guide/ex41.sh +++ b/LDP/guide/docbook/abs-guide/ex41.sh @@ -12,7 +12,7 @@ LINES=5 ( date; uname -a ) >>logfile # Time and machine name echo --------------------------------------------------------------------- >>logfile -tail -$LINES /var/log/messages | xargs | fmt -s >>logfile +tail -n $LINES /var/log/messages | xargs | fmt -s >>logfile echo >>logfile echo >>logfile @@ -25,7 +25,7 @@ exit 0 #+ may give xargs indigestion. # # He suggests the following substitution for line 15: -# tail -$LINES /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile +# tail -n $LINES /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile diff --git a/LDP/guide/docbook/abs-guide/ex52.sh b/LDP/guide/docbook/abs-guide/ex52.sh index 967a08c7..ad17f0a2 100644 --- a/LDP/guide/docbook/abs-guide/ex52.sh +++ b/LDP/guide/docbook/abs-guide/ex52.sh @@ -5,8 +5,8 @@ lines=35 # Allow 35 lines for the header (very generous). for File in * # Test all the files in $PWD. do - search1=`head -$lines $File | grep begin | wc -w` - search2=`tail -$lines $File | grep end | wc -w` + search1=`head -n $lines $File | grep begin | wc -w` + search2=`tail -n $lines $File | grep end | wc -w` # Uuencoded files have a "begin" near the beginning, #+ and an "end" near the end. if [ "$search1" -gt 0 ] diff --git a/LDP/guide/docbook/abs-guide/ex73.sh b/LDP/guide/docbook/abs-guide/ex73.sh index c54820de..c5c64249 100644 --- a/LDP/guide/docbook/abs-guide/ex73.sh +++ b/LDP/guide/docbook/abs-guide/ex73.sh @@ -37,11 +37,22 @@ then fi +###################################################################### echo "Creating swap file of size $blocks blocks (KB)." dd if=/dev/zero of=$FILE bs=$BLOCKSIZE count=$blocks # Zero out file. - mkswap $FILE $blocks # Designate it a swap file. swapon $FILE # Activate swap file. +# Note that if one or more of these commands fails, +#+ then it could cause nasty problems. +###################################################################### + +# Exercise: +# Rewrite the above block of code so that if it does not execute +#+ successfully, then: +# 1) an error message is echoed to stderr, +# 2) all temporary files are cleaned up, and +# 3) the script exits in an orderly fashion with an +#+ appropriate error code. echo "Swap file created and activated." diff --git a/LDP/guide/docbook/abs-guide/findstring.sh b/LDP/guide/docbook/abs-guide/findstring.sh index 82b6d949..f8bf08e5 100644 --- a/LDP/guide/docbook/abs-guide/findstring.sh +++ b/LDP/guide/docbook/abs-guide/findstring.sh @@ -18,5 +18,5 @@ exit 0 # Exercise (easy): # --------------- -# Convert this script to taking command-line parameters +# Convert this script to take command-line parameters #+ for $directory and $fstring. diff --git a/LDP/guide/docbook/abs-guide/horserace.sh b/LDP/guide/docbook/abs-guide/horserace.sh index 9707801e..8a0e7f69 100644 --- a/LDP/guide/docbook/abs-guide/horserace.sh +++ b/LDP/guide/docbook/abs-guide/horserace.sh @@ -287,8 +287,8 @@ while [ $COL -lt $WINNING_POS ]; do done # Define old type and position of the "randomized horse". - HORSE_TYPE=`cat horse_${MOVE_HORSE}_position | tail -1` - COL=$(expr `cat horse_${MOVE_HORSE}_position | head -1`) + HORSE_TYPE=`cat horse_${MOVE_HORSE}_position | tail -n 1` + COL=$(expr `cat horse_${MOVE_HORSE}_position | head -n 1`) ADD_POS=1 # Check if the current position is an handicap position. @@ -317,7 +317,7 @@ while [ $COL -lt $WINNING_POS ]; do echo -ne '\E[30;42m' # Move the cursor to new horse position. - tput cup `expr $MOVE_HORSE + 5` `cat horse_${MOVE_HORSE}_position | head -1` + tput cup `expr $MOVE_HORSE + 5` `cat horse_${MOVE_HORSE}_position | head -n 1` # Draw the horse. $DRAW_HORSE @@ -351,7 +351,7 @@ echo -ne '\E[30;42m' echo -en '\E[5m' # Make the winning horse blink. -tput cup `expr $MOVE_HORSE + 5` `cat horse_${MOVE_HORSE}_position | head -1` +tput cup `expr $MOVE_HORSE + 5` `cat horse_${MOVE_HORSE}_position | head -n 1` $DRAW_HORSE # Disable blinking text. diff --git a/LDP/guide/docbook/abs-guide/hypotenuse.sh b/LDP/guide/docbook/abs-guide/hypotenuse.sh index 980f1795..107cc163 100644 --- a/LDP/guide/docbook/abs-guide/hypotenuse.sh +++ b/LDP/guide/docbook/abs-guide/hypotenuse.sh @@ -1,6 +1,6 @@ #!/bin/bash # hypotenuse.sh: Returns the "hypotenuse" of a right triangle. -# ( square root of sum of squares of the "legs") +# (square root of sum of squares of the "legs") ARGS=2 # Script needs sides of triangle passed. E_BADARGS=65 # Wrong number of arguments. @@ -13,11 +13,16 @@ fi AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } ' -# command(s) / parameters passed to awk +# command(s) / parameters passed to awk # Now, pipe the parameters to awk. -echo -n "Hypotenuse of $1 and $2 = " -echo $1 $2 | awk "$AWKSCRIPT" + echo -n "Hypotenuse of $1 and $2 = " + echo $1 $2 | awk "$AWKSCRIPT" +# ^^^^^^^^^^^^ +# An echo-and-pipe is an easy way of passing shell parameters to awk. exit 0 + +# Exercise: Rewrite this script using 'bc' rather than awk. +# Which method is more intuitive? diff --git a/LDP/guide/docbook/abs-guide/m4.sh b/LDP/guide/docbook/abs-guide/m4.sh index ac8adcb5..f7f1cb9d 100644 --- a/LDP/guide/docbook/abs-guide/m4.sh +++ b/LDP/guide/docbook/abs-guide/m4.sh @@ -5,7 +5,7 @@ string=abcdA01 echo "len($string)" | m4 # 7 echo "substr($string,4)" | m4 # A01 -echo "regexp($string,[0-1][0-1],\&Z)" | m4 # 01Z +echo "regexp($string,[0-1][0-1],\&Z)" | m4 # 01Z # Arithmetic echo "incr(22)" | m4 # 23 diff --git a/LDP/guide/docbook/abs-guide/mailbox_grep.sh b/LDP/guide/docbook/abs-guide/mailbox_grep.sh index 3b052748..8cda536f 100644 --- a/LDP/guide/docbook/abs-guide/mailbox_grep.sh +++ b/LDP/guide/docbook/abs-guide/mailbox_grep.sh @@ -3,7 +3,7 @@ #+ and slightly modified and commented by ABS Guide author. # Used in ABS Guide with permission. (Thank you!) -# This script will not run under Bash version < 3.0. +# This script will not run under Bash versions < 3.0. E_MISSING_ARG=67 diff --git a/LDP/guide/docbook/abs-guide/nightly-backup.sh b/LDP/guide/docbook/abs-guide/nightly-backup.sh new file mode 100644 index 00000000..5dc2f4a9 --- /dev/null +++ b/LDP/guide/docbook/abs-guide/nightly-backup.sh @@ -0,0 +1,359 @@ +#!/bin/bash +# nightly-backup.sh +# http://www.richardneill.org/source.php#nightly-backup-rsync +# Copyright (c) 2005 Richard Neill <backup@richardneill.org>. +# This is Free Software licensed under the GNU GPL. +# ==> Included in ABS Guide with script author's kind permission. +# ==> (Thanks!) + +# This does a backup from the host computer to a locally connected +#+ firewire HDD using rsync and ssh. +# It then rotates the backups. +# Run it via cron every night at 5am. +# This only backs up the home directory. +# If ownerships (other than the user's) should be preserved, +#+ then run the rsync process as root (and re-instate the -o). +# We save every day for 7 days, then every week for 4 weeks, +#+ then every month for 3 months. + +# See: http://www.mikerubel.org/computers/rsync_snapshots/ +#+ for more explanation of the theory. +# Save as: $HOME/bin/nightly-backup_firewire-hdd.sh + +# Known bugs: +# ---------- +# i) Ideally, we want to exclude ~/.tmp and the browser caches. + +# ii) If the user is sitting at the computer at 5am, +#+ and files are modified while the rsync is occurring, +#+ then the BACKUP_JUSTINCASE branch gets triggered. +# To some extent, this is a +#+ feature, but it also causes a "disk-space leak". + + + + + +##### BEGIN CONFIGURATION SECTION ############################################ +LOCAL_USER=rjn # User whose home directory should be backed up. +MOUNT_POINT=/backup # Mountpoint of backup drive. + # NO trailing slash! + # This must be unique (eg using a udev symlink) +SOURCE_DIR=/home/$LOCAL_USER # NO trailing slash - it DOES matter to rsync. +BACKUP_DEST_DIR=$MOUNT_POINT/backup/`hostname -s`.${LOCAL_USER}.nightly_backup +DRY_RUN=false #If true, invoke rsync with -n, to do a dry run. + # Comment out or set to false for normal use. +VERBOSE=false # If true, make rsync verbose. + # Comment out or set to false otherwise. +COMPRESS=false # If true, compress. + # Good for internet, bad on LAN. + # Comment out or set to false otherwise. + +### Exit Codes ### +E_VARS_NOT_SET=64 +E_COMMANDLINE=65 +E_MOUNT_FAIL=70 +E_NOSOURCEDIR=71 +E_UNMOUNTED=72 +E_BACKUP=73 +##### END CONFIGURATION SECTION ############################################## + + +# Check that all the important variables have been set: +if [ -z "$LOCAL_USER" ] || + [ -z "$SOURCE_DIR" ] || + [ -z "$MOUNT_POINT" ] || + [ -z "$BACKUP_DEST_DIR" ] +then + echo 'One of the variables is not set! Edit the file: $0. BACKUP FAILED.' + exit $E_VARS_NOT_SET +fi + +if [ "$#" != 0 ] # If command-line param(s) . . . +then # Here document(ation). + cat <<-ENDOFTEXT + Automatic Nightly backup run from cron. + Read the source for more details: $0 + The backup directory is $BACKUP_DEST_DIR . + It will be created if necessary; initialisation is no longer required. + + WARNING: Contents of $BACKUP_DEST_DIR are rotated. + Directories named 'backup.\$i' will eventually be DELETED. + We keep backups from every day for 7 days (1-8), + then every week for 4 weeks (9-12), + then every month for 3 months (13-15). + + You may wish to add this to your crontab using 'crontab -e' + # Back up files: $SOURCE_DIR to $BACKUP_DEST_DIR + #+ every night at 3:15 am + 15 03 * * * /home/$LOCAL_USER/bin/nightly-backup_firewire-hdd.sh + + Don't forget to verify the backups are working, + especially if you don't read cron's mail!" + ENDOFTEXT + exit $E_COMMANDLINE +fi + + +# Parse the options. +# ================== + +if [ "$DRY_RUN" == "true" ]; then + DRY_RUN="-n" + echo "WARNING:" + echo "THIS IS A 'DRY RUN'!" + echo "No data will actually be transferred!" +else + DRY_RUN="" +fi + +if [ "$VERBOSE" == "true" ]; then + VERBOSE="-v" +else + VERBOSE="" +fi + +if [ "$COMPRESS" == "true" ]; then + COMPRESS="-z" +else + COMPRESS="" +fi + + +# Every week (actually of 8 days) and every month, +#+ extra backups are preserved. +DAY_OF_MONTH=`date +%d` # Day of month (01..31). +if [ $DAY_OF_MONTH = 01 ]; then # First of month. + MONTHSTART=true +elif [ $DAY_OF_MONTH = 08 \ + -o $DAY_OF_MONTH = 16 \ + -o $DAY_OF_MONTH = 24 ]; then + # Day 8,16,24 (use 8, not 7 to better handle 31-day months) + WEEKSTART=true +fi + + + +# Check that the HDD is mounted. +# At least, check that *something* is mounted here! +# We can use something unique to the device, rather than just guessing +#+ the scsi-id by having an appropriate udev rule in +#+ /etc/udev/rules.d/10-rules.local +#+ and by putting a relevant entry in /etc/fstab. +# Eg: this udev rule: +# BUS="scsi", KERNEL="sd*", SYSFS{vendor}="WDC WD16", +# SYSFS{model}="00JB-00GVA0 ", NAME="%k", SYMLINK="lacie_1394d%n" + +if mount | grep $MOUNT_POINT >/dev/null; then + echo "Mount point $MOUNT_POINT is indeed mounted. OK" +else + echo -n "Attempting to mount $MOUNT_POINT..." + # If it isn't mounted, try to mount it. + sudo mount $MOUNT_POINT 2>/dev/null + + if mount | grep $MOUNT_POINT >/dev/null; then + UNMOUNT_LATER=TRUE + echo "OK" + # Note: Ensure that this is also unmounted + #+ if we exit prematurely with failure. + else + echo "FAILED" + echo -e "Nothing is mounted at $MOUNT_POINT. BACKUP FAILED!" + exit $E_MOUNT_FAIL + fi +fi + + +# Check that source dir exists and is readable. +if [ ! -r $SOURCE_DIR ] ; then + echo "$SOURCE_DIR does not exist, or cannot be read. BACKUP FAILED." + exit $E_NOSOURCEDIR +fi + + +# Check that the backup directory structure is as it should be. +# If not, create it. +# Create the subdirectories. +# Note that backup.0 will be created as needed by rsync. + +for ((i=1;i<=15;i++)); do + if [ ! -d $BACKUP_DEST_DIR/backup.$i ]; then + if /bin/mkdir -p $BACKUP_DEST_DIR/backup.$i ; then + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ No [ ] test brackets. Why? + echo "Warning: directory $BACKUP_DEST_DIR/backup.$i is missing," + echo "or was not initialised. (Re-)creating it." + else + echo "ERROR: directory $BACKUP_DEST_DIR/backup.$i" + echo "is missing and could not be created." + if [ "$UNMOUNT_LATER" == "TRUE" ]; then + # Before we exit, unmount the mount point if necessary. + cd + sudo umount $MOUNT_POINT && + echo "Unmounted $MOUNT_POINT again. Giving up." + fi + exit $E_UNMOUNTED + fi +fi +done + + +# Set the permission to 700 for security +#+ on an otherwise permissive multi-user system. +if ! /bin/chmod 700 $BACKUP_DEST_DIR ; then + echo "ERROR: Could not set permissions on $BACKUP_DEST_DIR to 700." + + if [ "$UNMOUNT_LATER" == "TRUE" ]; then + # Before we exit, unmount the mount point if necessary. + cd ; sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again. Giving up." + fi + + exit $E_UNMOUNTED +fi + +# Create the symlink: current -> backup.1 if required. +# A failure here is not critical. +cd $BACKUP_DEST_DIR +if [ ! -h current ] ; then + if ! /bin/ln -s backup.1 current ; then + echo "WARNING: could not create symlink current -> backup.1" + fi +fi + + +# Now, do the rsync. +echo "Now doing backup with rsync..." +echo "Source dir: $SOURCE_DIR" +echo -e "Backup destination dir: $BACKUP_DEST_DIR\n" + + +/usr/bin/rsync $DRY_RUN $VERBOSE -a -S --delete --modify-window=60 \ +--link-dest=../backup.1 $SOURCE_DIR $BACKUP_DEST_DIR/backup.0/ + +# Only warn, rather than exit if the rsync failed, +#+ since it may only be a minor problem. +# E.g., if one file is not readable, rsync will fail. +# This shouldn't prevent the rotation. +# Not using, e.g., `date +%a` since these directories +#+ are just full of links and don't consume *that much* space. + +if [ $? != 0 ]; then + BACKUP_JUSTINCASE=backup.`date +%F_%T`.justincase + echo "WARNING: the rsync process did not entirely succeed." + echo "Something might be wrong. Saving an extra copy at: $BACKUP_JUSTINCASE" + echo "WARNING: if this occurs regularly, a LOT of space will be consumed," + echo "even though these are just hard-links!" +fi + +# Save a readme in the backup parent directory. +# Save another one in the recent subdirectory. +echo "Backup of $SOURCE_DIR on `hostname` was last run on \ +`date`" > $BACKUP_DEST_DIR/README.txt +echo "This backup of $SOURCE_DIR on `hostname` was created on \ +`date`" > $BACKUP_DEST_DIR/backup.0/README.txt + +# If we are not in a dry run, rotate the backups. +[ -z "$DRY_RUN" ] && + + # Check how full the backup disk is. + # Warn if 90%. if 98% or more, we'll probably fail, so give up. + # (Note: df can output to more than one line.) + # We test this here, rather than before + #+ so that rsync may possibly have a chance. + DISK_FULL_PERCENT=`/bin/df $BACKUP_DEST_DIR | + tr "\n" ' ' | awk '{print $12}' | grep -oE [0-9]+ ` + echo "Disk space check on backup partition \ + $MOUNT_POINT $DISK_FULL_PERCENT% full." + if [ $DISK_FULL_PERCENT -gt 90 ]; then + echo "Warning: Disk is greater than 90% full." + fi + if [ $DISK_FULL_PERCENT -gt 98 ]; then + echo "Error: Disk is full! Giving up." + if [ "$UNMOUNT_LATER" == "TRUE" ]; then + # Before we exit, unmount the mount point if necessary. + cd; sudo umount $MOUNT_POINT && + echo "Unmounted $MOUNT_POINT again. Giving up." + fi + exit $E_UNMOUNTED + fi + + + # Create an extra backup. + # If this copy fails, give up. + if [ -n "$BACKUP_JUSTINCASE" ]; then + if ! /bin/cp -al $BACKUP_DEST_DIR/backup.0 $BACKUP_DEST_DIR/$BACKUP_JUSTINCASE + then + echo "ERROR: Failed to create extra copy \ + $BACKUP_DEST_DIR/$BACKUP_JUSTINCASE" + if [ "$UNMOUNT_LATER" == "TRUE" ]; then + # Before we exit, unmount the mount point if necessary. + cd ;sudo umount $MOUNT_POINT && + echo "Unmounted $MOUNT_POINT again. Giving up." + fi + exit $E_UNMOUNTED + fi + fi + + + # At start of month, rotate the oldest 8. + if [ "$MONTHSTART" == "true" ]; then + echo -e "\nStart of month. \ + Removing oldest backup: $BACKUP_DEST_DIR/backup.15" && + /bin/rm -rf $BACKUP_DEST_DIR/backup.15 && + echo "Rotating monthly,weekly backups: \ + $BACKUP_DEST_DIR/backup.[8-14] -> $BACKUP_DEST_DIR/backup.[9-15]" && + /bin/mv $BACKUP_DEST_DIR/backup.14 $BACKUP_DEST_DIR/backup.15 && + /bin/mv $BACKUP_DEST_DIR/backup.13 $BACKUP_DEST_DIR/backup.14 && + /bin/mv $BACKUP_DEST_DIR/backup.12 $BACKUP_DEST_DIR/backup.13 && + /bin/mv $BACKUP_DEST_DIR/backup.11 $BACKUP_DEST_DIR/backup.12 && + /bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 && + /bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 && + /bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9 + + # At start of week, rotate the second-oldest 4. + elif [ "$WEEKSTART" == "true" ]; then + echo -e "\nStart of week. \ + Removing oldest weekly backup: $BACKUP_DEST_DIR/backup.12" && + /bin/rm -rf $BACKUP_DEST_DIR/backup.12 && + + echo "Rotating weekly backups: \ + $BACKUP_DEST_DIR/backup.[8-11] -> $BACKUP_DEST_DIR/backup.[9-12]" && + /bin/mv $BACKUP_DEST_DIR/backup.11 $BACKUP_DEST_DIR/backup.12 && + /bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 && + /bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 && + /bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9 + + else + echo -e "\nRemoving oldest daily backup: $BACKUP_DEST_DIR/backup.8" && + /bin/rm -rf $BACKUP_DEST_DIR/backup.8 + + fi && + + # Every day, rotate the newest 8. + echo "Rotating daily backups: \ + $BACKUP_DEST_DIR/backup.[1-7] -> $BACKUP_DEST_DIR/backup.[2-8]" && + /bin/mv $BACKUP_DEST_DIR/backup.7 $BACKUP_DEST_DIR/backup.8 && + /bin/mv $BACKUP_DEST_DIR/backup.6 $BACKUP_DEST_DIR/backup.7 && + /bin/mv $BACKUP_DEST_DIR/backup.5 $BACKUP_DEST_DIR/backup.6 && + /bin/mv $BACKUP_DEST_DIR/backup.4 $BACKUP_DEST_DIR/backup.5 && + /bin/mv $BACKUP_DEST_DIR/backup.3 $BACKUP_DEST_DIR/backup.4 && + /bin/mv $BACKUP_DEST_DIR/backup.2 $BACKUP_DEST_DIR/backup.3 && + /bin/mv $BACKUP_DEST_DIR/backup.1 $BACKUP_DEST_DIR/backup.2 && + /bin/mv $BACKUP_DEST_DIR/backup.0 $BACKUP_DEST_DIR/backup.1 && + + SUCCESS=true + + +if [ "$UNMOUNT_LATER" == "TRUE" ]; then + # Unmount the mount point if it wasn't mounted to begin with. + cd ; sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again." +fi + + +if [ "$SUCCESS" == "true" ]; then + echo 'SUCCESS!' + exit 0 +fi + +# Should have already exited if backup worked. +echo 'BACKUP FAILED! Is this just a dry run? Is the disk full?) ' +exit $E_BACKUP diff --git a/LDP/guide/docbook/abs-guide/online.sh b/LDP/guide/docbook/abs-guide/online.sh index e9f51d57..496a23d4 100644 --- a/LDP/guide/docbook/abs-guide/online.sh +++ b/LDP/guide/docbook/abs-guide/online.sh @@ -28,7 +28,7 @@ echo while [ $TRUE ] #Endless loop. do - tail -$CHECK_LINES $LOGFILE> $TEMPFILE + tail -n $CHECK_LINES $LOGFILE> $TEMPFILE # Saves last 100 lines of system log file as temp file. # Necessary, since newer kernels generate many log messages at log on. search=`grep $KEYWORD $TEMPFILE` @@ -77,7 +77,7 @@ done CHECK_INTERVAL=1 -while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD" +while ! tail -n 1 "$LOGFILE" | grep -q "$KEYWORD" do echo -n . sleep $CHECK_INTERVAL done diff --git a/LDP/guide/docbook/abs-guide/ramdisk.sh b/LDP/guide/docbook/abs-guide/ramdisk.sh index 2b03716a..bd0b4a94 100644 --- a/LDP/guide/docbook/abs-guide/ramdisk.sh +++ b/LDP/guide/docbook/abs-guide/ramdisk.sh @@ -32,12 +32,16 @@ then #+ so no error if this script is run mkdir $MOUNTPT #+ multiple times. fi +############################################################################## dd if=/dev/zero of=$DEVICE count=$SIZE bs=$BLOCKSIZE # Zero out RAM device. # Why is this necessary? mke2fs $DEVICE # Create an ext2 filesystem on it. mount $DEVICE $MOUNTPT # Mount it. chmod 777 $MOUNTPT # Enables ordinary user to access ramdisk. # However, must be root to unmount it. +############################################################################## +# Need to test whether above commands succeed. Could cause problems otherwise. +# Exercise: modify this script to make it safer. echo "\"$MOUNTPT\" now available for use." # The ramdisk is now accessible for storing files, even by an ordinary user. diff --git a/LDP/guide/docbook/abs-guide/set-pos.sh b/LDP/guide/docbook/abs-guide/set-pos.sh index b7c29db9..af2872d8 100644 --- a/LDP/guide/docbook/abs-guide/set-pos.sh +++ b/LDP/guide/docbook/abs-guide/set-pos.sh @@ -8,6 +8,7 @@ set -- $variable first_param=$1 second_param=$2 shift; shift # Shift past first two positional params. +# shift 2 also works. remaining_params="$*" echo diff --git a/LDP/guide/docbook/abs-guide/subshell.sh b/LDP/guide/docbook/abs-guide/subshell.sh index c58a6c75..955abd71 100644 --- a/LDP/guide/docbook/abs-guide/subshell.sh +++ b/LDP/guide/docbook/abs-guide/subshell.sh @@ -3,21 +3,30 @@ echo +echo "We are outside the subshell." echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL" # Bash, version 3, adds the new $BASH_SUBSHELL variable. -echo +echo; echo outer_variable=Outer +global_variable= +# Define global variable for "storage" of +#+ value of subshell variable. ( +echo "We are inside the subshell." echo "Subshell level INSIDE subshell = $BASH_SUBSHELL" inner_variable=Inner -echo "From subshell, \"inner_variable\" = $inner_variable" -echo "From subshell, \"outer\" = $outer_variable" +echo "From inside subshell, \"inner_variable\" = $inner_variable" +echo "From inside subshell, \"outer\" = $outer_variable" + +global_variable="$inner_variable" # Will this allow "exporting" + #+ a subshell variable? ) -echo +echo; echo +echo "We are outside the subshell." echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL" echo @@ -29,10 +38,18 @@ else fi echo "From main body of shell, \"inner_variable\" = $inner_variable" -# $inner_variable will show as uninitialized +# $inner_variable will show as blank (uninitialized) #+ because variables defined in a subshell are "local variables". -# Is there any remedy for this? +# Is there a remedy for this? +echo "global_variable = "$global_variable"" # Why doesn't this work? + echo exit 0 + +# Question: +# -------- +# Once having exited a subshell, +#+ is there any way to reenter that very same subshell +#+ to modify or access the subshell variables? diff --git a/LDP/guide/docbook/abs-guide/unset.sh b/LDP/guide/docbook/abs-guide/unset.sh index 1c17a36f..be9174a4 100644 --- a/LDP/guide/docbook/abs-guide/unset.sh +++ b/LDP/guide/docbook/abs-guide/unset.sh @@ -8,4 +8,9 @@ unset variable # Unset. # Same effect as: variable= echo "(unset) variable = $variable" # $variable is null. +if [ -z "$variable" ] # Try a string-length test. +then + echo "\$variable has zero length." +fi + exit 0 diff --git a/LDP/guide/docbook/abs-guide/wf.sh b/LDP/guide/docbook/abs-guide/wf.sh index 2e18a256..daaa9dcd 100644 --- a/LDP/guide/docbook/abs-guide/wf.sh +++ b/LDP/guide/docbook/abs-guide/wf.sh @@ -57,4 +57,4 @@ exit 0 # 1) Add 'sed' commands to filter out other punctuation, #+ such as semicolons. # 2) Modify the script to also filter out multiple spaces and -# other whitespace. +#+ other whitespace.