3 Write programs that do one thing and do it well. Write programs
4 to work together. Write programs to handle text streams,
5 because that is a universal interface. -- Doug MacIlroy
9 SECTION(«CMD(«sh») versus CMD(«bash»)»)
11 - Bash scripts begin with a sha-bang: CMD(«#!/bin/bash»)
12 - sh is the POSIX defined shell specification.
13 - Bash is one implementation of the sh specification.
14 - /bin/sh links to the default shell of your system.
15 - This can be different from your user shell!
16 - Each shell has its idiosyncracies.
17 - Using a sha-bang pointing to bash is safer CMD(«#!/bin/bash»)
18 - < 10.3, the default Mac OS X shell is tcsh (bad!)
19 - Scripts need to be executable (chmod u+x script).
22 - Your current shell is stored the CMD($SHELL) environment variable.
23 Use echo to find out what it is.
24 - Similarly, find out your CMD($BASH_VERSION).
25 - Use readlink to find out what CMD(/bin/sh) points to in your system.
28 Explain why bash does not exit when you type CMD(«Ctrl+C») on the
33 - Variables are defined using CMD(«=») (no spaces!).
34 - Variables are read using CMD(«$»).
35 - Spaces are the enemy of variables. Spaces break variables.
36 - Double quotes CMD(«"») a the defense against spaces.
37 - braces (curly brackets) CMD(«{}») can also protect variables from
38 ambiguity. eg: CMD(«${foo}»)bar. They also group commands.
39 - Single quotes CMD(«'») are like double quotes, but are literal.
40 - Bash scripts have special variables:
41 - CMD(«$0»): script full path and name.
42 - CMD(«$1»): first command line argument.
43 - CMD(«$2»): second argument ... etc.
44 - CMD(«$#»): number of command line arguments.
45 - CMD(«$*»): list of arguments as a single string.
46 - CMD(«$@»): list of arguments as a delimited list.
47 - Parentheses CMD(«()») execute a command in a sub-shell.
48 - Double parentheses return the result of arithmetic expansion
49 (positive integer only).
53 - Write a simple script in which you define a variable with the string
54 "Hello World!". echo this variable without quotes, with single and
55 double quotes, and with braces (again with and without different
56 quotes). Become comfortable with the results.
57 - How do you return the results of a sub-shell ()?
58 - Write a simple script to add two positive integers.
59 - Write a simple script to add two positive integers supplied as
60 arguments to the script.
64 Write a script using your favorite editor. The script should display
65 the path to your home directory and the terminal type that you
71 - CMD(«[...]») is the POSIX sh test function.
72 - CMD(«[[...]]») is the Bash test function (more powerful).
73 - These tests are logical: they return TRUE or FALSE.
74 - Tests use logical operators.
76 - There are three types of operators: File, String and Integer.
77 - A few single file operators eg: CMD(«[[ -e somefile ]]»)
78 - CMD(«-e»): file exists
79 - CMD(«-s»): file not zero size
80 - CMD(«-d»): file is a directory
81 - CMD(«-r»): you have read permission
82 - CMD(«-O»): you are the owner of the file
83 - A few multiple file operators eg: CMD(«[[ file1 -nt file2 ]]»)
84 - CMD(«-nt»): first file newer than second
85 - CMD(«-ot»): first file older than second
86 - A few integer operators:
87 - CMD(«-eq»): equal to
88 - CMD(«-ne»): not equal to
89 - CMD(«-gt»): greater than
90 - CMD(«-ge»): greater than or equal to
91 - CMD(«-lt»): less than
92 - CMD(«-le»): less than or equal to
93 - A few string operators:
95 - CMD(«!=»): not equal to
96 - CMD(«=~»): regex match (Bash specific)
97 - CMD(«-z»): string is null (zero length)
98 - CMD(«-n»): string is not null (zero length)
99 - When you understand how these operators work, you will have a good
100 idea of the kinds of things you can do in Bash.
101 - Tests can be combined with CMD(«&&»): "and" and CMD(«||») "or".
104 Write a script that checks whether or not you own a file, and reports
105 back if you do not. (This is useful if you are working on multiple
106 user systems and need your script to remove many files.
109 SECTION(«Conditionals»)
111 - The most commonly used Bash conditional structure is: CMD(«if»)
112 ... CMD(«then») ... CMD(«fi»)
113 - A shorter version uses logic in place of if..then..fi. eg: CMD(«[[
114 test ]] && { execute if TRUE; also execute }»)
118 - Modify your calculator script to check for valid inputs:
120 - They should not have letters
121 - Write a script to check for a directory, and create it if it
123 - Write a script to remove a file specfied as an argument, but only
124 if you are its owner.
128 Write a script that takes exactly one argument, a directory name. If
129 the number of arguments is more or less than one, print a usage
130 message. If the argument is not a directory, print another message. For
131 the given directory, print the five biggest files and the five files
132 that were most recently modified.
137 - The most commonly used Bash loop structure is: CMD(for ... do
139 - The CMD(for) statement behaves a lot like a variable assignment.
140 - File globbing works: CMD(«for file in *.txt; do; done»)
141 - Sequences are also useful:
142 - CMD(«for num in {1..5}; do; echo $num; done 1 2 3 4 5»)
143 - CMD(«for num in {1..10..2}; do; echo $num; done 1 3 5 7 9»)
146 - Write a script that stores the results of a arithmetic in files
150 Come up with a for loop which prints the first 10 squares (1, 4,
154 SECTION(«Pipes and Here Strings»)
156 - Here strings are an alternative to conventional piping.
157 - Because they do not spawn a sub-shell, they retain variables in
158 the shell the script is running in. eg: instead of CMD(head file1 |
159 cut -f 1) write CMD(head | cut -f 1 <<< file1)
160 - They can be easier or more difficult to read, depending on your
164 Write a script that uses pipes, change it to use a Here string.
168 Tie all the above together with the following task:
170 Let's say that you want to perform an analysis by population
171 (k-means) cluster of some accessions (ecotypes). You want to
172 generate a separate bed file with the SNPs of the members of
173 each cluster (which you have previously calculated).
175 The relevant plink argument is: CMD(«--keep “$keeplist”») where
176 keeplist is a two column file specifying family and ecotype
177 (made for human data). We will just duplicate the ecotype
178 in the two columns. e.g.:
185 I have provided a comma-separated file of which cluster each ecotype
186 belongs to: CMD(«/tmp/cluster_course/admix_pop.csv») Take a look at
187 this file. You will see that it is a comma separated file with
188 ecotype ID numbers in the first column and the cluster assignment
191 Use the 1001_cluster_course files as your test dataset.
193 You suspect that your clusters might change, (in assignment
194 and number ), so you want to write a Bash script to generate
195 separate bed files for a given clustering.
199 - Your script will be called something like this:
201 sep_clust.sh all_snps_bedfile_root cluster_assignment.csv
203 - You will have a loop.
204 - You will generate a list of cluster numbers from the
205 CMD(«cluster_assignment.csv») file.
206 - The cluster file has a header! CMD(«tail -n+2») will skip to the
208 - CMD(«grep “something$”») matches CMD(«something») at the
210 - You will generate a “keep” list for each cluster and supply
212 - CMD(«cut») expects tab-delimited input, but ours is
213 comma-delimited. Use CMD(«-d ","»).
214 - The keep list needs the ecotypes printed twice per line. The easiest
215 thing to use in this case is CMD(«awk»):
217 awk -v OFS='\t' '{print $1, $1}'
219 - Here, CMD(«-v») changes an internal value (CMD(«OFS»), the
220 “Output Field Separator”), CMD(«\t») specifies the delimiter
221 (a tab), CMD(«{...}») is the command, and CMD(«print $1, $1»)
222 is the command to print column 1, column 1.
225 - CMD(«uniq») requires sorted input.
226 - CMD(«sort -n») specifies a numerical sort.
227 - Generate as few temporary files as possible.
231 If important data have been copied from one system to another,
232 one might want to check that both copies are identical. This is
233 fairly easy if they both live on the same system, but can be quite
234 tricky if they don't. For example, imagine files copied from an
235 NFS-mounted directory to the local hard drive.
238 <li> Write a bash script which checks that all files have been
239 copied and that all copies have the same size as the original. Hint:
240 You might want to check out <code>find</code>. Generate two almost
241 identical folders with <code> mkdir a b && touch {a,b}/{1..10} &&
242 rm b/4 && echo foo > a/7 </code> to try out the script. </li>
244 <li> Run <code>echo bar > b/7</code>. Will the script detect that
245 <code>a/7</code> and <code>b/7</code> are different? </li>
247 <li> Come up with a different script which runs <code>cmp</code>
248 to detect content changes. Analyze and compare the running time of
251 <li> Now suppose the two directories are stored on different
252 systems. Assume further that they are so large (or the network
253 so slow) that transferring the file contents over the network
254 would take too long. Argue how a cryptographic hash function can
255 be employed to detect content changes. Write a script which runs
256 <code>sha1sum</code> to implement this idea and analyze its running
262 <li> Script which prints file names and sizes of all regular files
263 in the given two directories and compares the result.
267 list_files() { (cd "$1" && find -type f -printf '%h/%f\t%s\n' | sort); }
268 (($# != 2)) && exit 1
269 { list_files "$1" && list_files "$2"; } | sort | uniq -u
273 <li> The above script will not look at file contents. Since
274 <code>a/7</code> and <code>b/7</code> have the same size and the same
275 base name, the script won't notice they are different. </li>
277 <li> Script which compares the contents of all regular files
278 in directory <code>$1</code> against the files in directory
283 (($# != 2)) && exit 1
284 { cd "$1" && find -type f; } | while read -r file; do
285 cmp "$1/$file" "$2/$file"
289 <li> The running time of the find command in first script is proportional
290 to the number of files, <code>n</code>. The sort command runs in time
291 proportional to <code>n * log(n</code>). Since the two commands run
292 in parallel, the total running time is the maximum of the two. In
293 practice, since the <code>find</code> command needs at least one
294 system call per file (<code>stat(2)</code>) to get the metadata, and the
295 kernel must load this information from disk, the running time of the
296 pipeline is dominated by the running time of <code>find</code>. Note
297 that it is independent of the file sizes. The running time of the
298 second script is proportional to the number of files, plus the sum of
299 all file sizes, since in the worst case <code>cmp</code> must read
300 all files completely to perform its task. In the common situation
301 where files are much bigger than a few hundred bytes, the sum of all
302 file sizes dominates the running time. The second script might easily
303 run orders of magnitute slower than the first. </li>
305 <li> Script which computes the sha1 hash of all regular files in
306 directory <code>$1</code> and checks the hashes against the files in
307 directory <code>$2</code> on host <code>$3</code>:
311 (($# != 3)) && exit 1
312 cd "$1" && find -type f -print0 \
314 | ssh "$3" "cd $2 && sha1sum -c"
317 The <code>sha1sum</code> command reads the full contents of each file,
318 so the running time is the same as for the script that executed
319 <code>cmp</code>. However, only the hashes but no contents are
320 transferred over the network, and the hashes are computated locally on
321 each system. Therefore, this approach performs best in practice. </li>
326 SECTION(«Substitution and Expansion»)
328 - expansion is performed on the command line after it has been split
330 - several kinds of expansion: tilde, brace, arithmetic, pathname,
331 parameter and variable, history
332 - command substitution
336 - Give an example for each type of expansion.
337 - Which expansions can change the number of words?
338 - Create a list of "words" with CMD(«apg -c 42 -n 10000 >
339 foo»). Transform each word to upper case, using the case-modification
340 operator CMD(«^^») as follows: CMD(«while read a; do echo ${a^^};
341 done < foo > bar»). Compare the running time of this command with (a)
342 CMD(«tr [a-z] [A-Z] < foo > bar») and (b) CMD(«while read a; do tr
343 [a-z] [A-Z] <<< "$a"; done < foo > bar»). Try to beat the fastest
344 implementation using your favorite tool (CMD(«sed»), CMD(«perl»),
346 - The command CMD(«find . -maxdepth 1 -mindepth 1 -type d») lists
347 all directories in the CWD. Describe an CMD(«ls») command which
349 - Scripts often contain code like CMD(«find . | while read f; do
350 something with "$f"; done»). While this code works for file names
351 which contain spaces or tab characters, it is not bullet-proof because
352 file names may also contain the newline character. The only character
353 that can not be part of a file name is the null character. This is
354 why CMD(«find(1)») has the CMD(«--print0») option to separate the
355 file names in its output by null characters rather than the newline
356 character. Find a way to make the CMD(«while») loop work when it
357 is fed a file list produced with CMD(«find --print0»). Check the
358 correctness of your command by using CMD(«printf 'file\n1\0file 2'»)
359 as the left hand side of the pipe.
363 - Write a shell script CMD(«varstate») which takes the name of a
364 variable as CMD(«$1») and determines whether the named variable
365 is (1) set to a non-empty string, (2) set to the empty string, or
366 (3) unset. Verify the correctness of your script with the following
368 - CMD(«foo=bar ./varstate foo # case (1)»)
369 - CMD(«foo= ./varstate foo # case (2)»)
370 - CMD(«unset foo; ./varstate foo # case (3)»)
375 - code block that implements a set of operations
376 - for tasks which repeat with only slight variations
377 - syntax: CMD(«f() {commands; }»)
378 - positional parameters (CMD(«$1»), CMD(«$2»), ...)
379 - special parameters: CMD(«$*»), CMD(«$@»), CMD(«$#»)
383 - Understand your smiley of the day (and run it if you are brave):
384 CMD(«:() { :& :& };:»)
385 - Write a function which checks whether the passed string is a
387 - Consider this simple function which returns its first argument:
388 CMD(«foo() { return $1; }»). Find the largest positive integer this
390 - Write a function which returns the sum of the first and the 10th
393 SECTION(«Arrays and Hashes»)
395 - bash-2: arrays, bash-4: hashes (associative arrays)
396 - zero-based, one-dimensional only
397 - three ways of assigning values
398 - negative parameters in arrays and string-extraction (bash-4.2)
402 - The following three array assignments are equivalent:
403 CMD(arr=(one two three)), CMD(«arr=([0]=one [1]=two [2]=three)»),
404 CMD(«arr[0]=one; arr[1]=two; arr[2]=three»). Discuss the pros and
405 cons of each version.
406 - Define an array with CMD(«arr=(one two three)»).
407 - Learn how to determine the number of elements that have been assigned
408 (three in this example).
409 - Convert all entries to upper case without iterating.
410 - Print all entries which do not contain an CMD(«"o"») character,
411 again without iterating (result: CMD(«three»)).
412 - Use arrays to write a bash script that lists itself, including
413 line numbers, and does not call any external command (CMD(«sed,
414 awk, perl, python, ...»)). Try to get rid of the loop in this
415 REFERENCE(«self-list.bash», «solution»),
416 - CMD(«rot13») is a simple "encryption" algorithm which shifts each
417 letter in the alphabet a-z by 13 characters and leaves non-letter
418 characters unchanged. That is, CMD(«a») maps to CMD(«n»),
419 CMD(«b») maps to CMD(«o»), ..., CMD(«m») maps to CMD(«z»),
420 CMD(«n») maps to CMD(«a»), and so on. Implement CMD(«rot13»)
421 using an associative array. Compare your solution with this
422 REFERENCE(«rot13.bash», «implementation») which reads from
423 stdin and writes to stdout. Verify that "encrypting" twice with
424 CMD(«rot13») is a no-op.
425 - Examine the CMD(BASH_VERSINFO) array variable to check whether the
426 running bash instance supports negative array indices.
427 - Write a bash script which reads from stdin and prints the last word
431 Bash-4.2 added support for negative array indices and string
432 extraction (count backward from the last element). Apply this feature
433 to print all but the first and last character of the last word of
436 The script below implements a loop which reads lines from stdin into
437 an array. In each interation of the loop we use CMD(«${arr[-1]}»)
438 to get the last word of the line. Substring expansion with -1 as the
439 offset value refers to the last character within the word.
442 # The -a option assigns the words of each line to an array variable.
443 while read -a arr; do
445 # If the input line contains only whitespace, there is
447 ((${#arr[@]} == 0)) && continue
449 # Negative array indices count back from the end of the
450 # array. In particular the index -1 references the last
451 # element. Hence ${arr[-1]} is the last word.
453 # To print the first and the last character of the last
454 # word, we use substring expansion:
455 # ${parameter:offset:length} expands to up to length
456 # characters of the value of parameter starting at the
457 # character specified by offset. As for array indices,
458 # negative offsets are allowed and instruct bash to use
459 # the value as an offset from the *end* of the value,
460 # with -1 being the last character.
462 # A negative offset must be separated from the colon by
463 # a space to avoid confusion with the :- expansion (use
464 # default values). For example, ${var:-1:1} expands to
465 # the string "1:1" if a is unset (and the value of var
467 echo "${arr[-1]: 0: 1} ${arr[-1]: -1: 1}"
479 - Run CMD(«sleep 10»), interrupt the command with CMD(«CTRL+C») and
480 examine CMD(«$?»). Hint: CMD(«trap -l») prints all signal numbers.
481 - The REFERENCE(«stale_tmpfile.bash», «script») below is flawed
482 in that it leaves a stale temporary file when interrupted with
483 CMD(«CTRL+C»). Fix this flaw by trapping CMD(«SIGINT»).
485 SECTION(«Shell Options»)
488 - _many_ options, some really weird ones
489 - two ways to set options: CMD(«set»), CMD(«shopt»)
490 - CMD(«set +option») _disables_ CMD(«option»)
491 - aim: Introduce examples for the most useful options
492 - CMD(«-x»): debugging
493 - CMD(«-u»): parameter expansion is treated as error for unset variables
494 - CMD(«-e»): exit on first error
495 - pipefail: Get _all_ exit codes of a pipeline
496 - nullglob: avoid common pitfalls with pathname expansion
497 - extglob: activate extended pattern matching features
501 - Find at least two bugs in the REFERENCE(«catch_the_bug.bash»,
502 «script») below. Run the script twice, once with
503 CMD(«bash catch_the_bug.bash») and once with CMD(«bash -x
504 catch_the_bug.bash»). Compare the output.
505 - There is a subtle bug in the the
506 REFERENCE(«HungarianCamelSquare.bash», «HungarianCamelSquare.bash»)
507 script below. Run the script with and without bash's CMD(«-u») option
508 and compare the error messages. Discuss whether it is reasonable to
509 add CMD(«set -u») to existing scripts.
510 - What's the exit code of the pipeline CMD(«/notthere | wc -l»)?
511 Run CMD(«set -o pipefail»), then repeat the command. Search the bash
512 man page for CMD(«pipefail») and learn about the CMD(«PIPESTATUS»)
513 array variable. Repeat the above command and examine the contents
514 of CMD(«PIPESTATUS»).
515 - Assume that CMD(«/etc») contains only "reasonable" file
516 names (without space or other "funny" characters). Yet the
517 REFERENCE(«count_config_files.bash», «count_config_files.bash»)
518 script is buggy. Point out the flaw _before_ you try it out, then run
519 it to confirm. Insert CMD(«shopt -s nullglob») before the loop and
520 run the script again. Search the bash manual page for CMD(«nullglob»)
524 The REFERENCE(«rm_tmp.bash», «rm_tmp.bash») script is seriously
525 flawed and would sooner or later create major grief if the CMD(«rm»)
526 command was not commented out. Find at least three bugs in it. Run
527 CMD(«bash rm_tmp.bash /notthere») and CMD(«bash -e rm_tmp.bash
528 /notthere») to see the CMD(«-e») option in action.
531 - If the CMD(«cd») command fails, the CMD(«rm») command will be
532 executed in the current directory. This can happen for several reasons:
533 - CMD(«$1») does not exist,
534 - CMD(«$1») is not a directory,
535 - The executing user has no permissions to change into CMD(«$1»),
536 - CMD(«$1») contains whitespace characters,
537 - CMD(«$1») is a directory on a network share which is currently
538 unavailable. This does not happen with NFS, but may happen with CIFS
539 (Microsoft's Common Internet File System).
540 - If no argument is given, the CMD(«rm») command will be executed
541 in the home directory.
542 - The CMD(«rm») command does not remove all files: filenames starting
543 with a dot will be omitted.
544 - If the directory contains more files than the maximal number of
545 arguments in a command line, the CMD(«rm») command fails. The limit
546 depends on the system, but is often as low as 32768.
547 - If the directory contains a file named CMD(«-r»), the directory
548 will be removed recursively.
549 - If CMD(«$1») is an empty directory, the command fails because
550 there is no file named CMD(«"*"»). See the CMD(«nullglob») shell
551 option if you don't know why.
552 - The command fails if CMD(«$1») contains subdirectories.
553 - Even the CMD(«echo») command is buggy: If there is a file
554 CMD(«-n»), it will be treated as an option to CMD(«echo»).
558 - Suppose you'd like to remove all leading occurences of the character
559 CMD(«"a"») from each input line. The script should read input lines
560 from CMD(«stdin») and write its output to CMD(«stdout»). For
561 example, the input line CMD(«aabba») should be transformed into
564 - Write a bash script that runs a suitable external command of
565 your choice (e.g., CMD(«sed»), CMD(«awk»), CMD(«perl») or
566 CMD(«python»)) for each input line.
567 - Come up with an alternative script that does not run any commands.
568 - Implement yet another version that uses extended globbing.
570 - Create a suitable input file with 100000 lines by running
571 CMD(«base64 < /dev/urandom | head -n 100000 > foo»). Test the
572 performance of the three implementations of the above script by
573 executing CMD(«time script < foo») and discuss the result.
575 - Bash script with external command:
580 sed -e 's/^a\+//' <<< "$line"
583 - Bash script without external command (note that CMD(«printf»)
590 while [[ "${line:$n:1}" == 'a' ]]; do
593 printf '%s\n' "${line:$n}"
596 - Bash script with extended globbing:
603 printf '%s\n' "${line/*(a)}"
607 - external command: 289s
608 - without external command, without extglob: 4s
611 - Discussion: External commands hurt plenty. Try to avoid them
612 inside of loops which execute many times. The extglob feature is
613 handy but is still twice as expensive than the open-coded version
614 which avoids pattern matching alltogether. Note that the simple
615 CMD(«sed -e 's/^a\+//' foo») also does the job, and is even two
616 orders of magnitude faster than the fastest bash version. However,
617 this approach is not very flexible, hence unsuitable for real world
618 applications which do more than just write the transformed string
622 SECTION(«Miscellaneous»)
628 - Indirect variable referencing (eval, ${!x}, nameref)
632 - Write a bash script which prints the username and login shell of
633 each user defined in CMD(«/etc/passwd»). Hint: Set CMD(«IFS»)
634 and employ the bash CMD(«read») builtin with suitable options to
635 read each line of CMD(«/etc/passwd») into an array. Compare your
636 solution with this REFERENCE(«print_login_shells.bash», «script»).
637 - Run CMD(«read -p "> " -ei "kill -9 -1" t; echo "you entered:
638 $t"») and note how it provides nice readline-based editing. Check
639 CMD(«bash») man page for other options to the CMD(«read») builtin,
640 like CMD(«-s») and CMD(«-t»).
641 - Run CMD(«ls ~/**/*.pdf»). Search the bash manual page for
642 CMD(«**») and CMD(«globstar») to understand the meaning of the
643 CMD(«**») pattern in pathname expansion. Next, run CMD(«shopt -s
644 globstar && ls ~/**/*.pdf») and marvel.
645 - Is there a way in bash to distinguish between undefined variables
646 and variables which have been set to the emtpy string? Hint: examine
647 the difference between CMD(«${x-42}») and CMD(«${x:-42}»).
648 - Setting the environment variable CMD(«PROMPT_COMMAND»)
649 to a function instructs bash to call this function prior to
650 issuing the prompt. Run CMD(«prompt_command() { PS1="$PWD > ";
651 }; PROMPT_COMMAND=prompt_command») to change your prompt. Modify
652 the function to replace the middle part of the path by '...' if
653 CMD(«$PWD») exceeds 10 characters.
654 - During parameter expansion, if the first character of a parameter
655 is an exclamation point (!), bash uses the value of the variable
656 formed from the rest of parameter as the name of the variable rather
657 than the value of the parameter itself. This is known as _indirect
658 expansion_. Run CMD(«a=42; x=a; echo ${!x}») to see the effect.
659 - Examine and run the REFERENCE(«minmax.bash», «minmax script»)
660 whose CMD(«minmax()») function is given the _name_ CMD(«X») of a
661 variable, and a sequence of positive integers. The function computes
662 the minimum and the maximum given value and sets the variables
663 CMD(«X_min») and CMD(«X_max») accordingly.
666 Get rid of the CMD(«eval») statement in the
667 REFERENCE(«minmax.bash», «minmax script») by passing
668 variables declared with CMD(«-n») to assign the CMD(«namref»)
669 attribute. Hint: search for (nameref) in the bash manual.
673 Read the CMD(«bashbug») manual page and discuss
674 under which circumstances one should file a bug report.
675 Download the source code of latest version of bash from
676 XREFERENCE(«ftp://ftp.gnu.org/pub/gnu/bash», «gnu ftp server»),
677 apply all patches found in the CMD(«bash-4.3-patches») subdirectory
678 and compile the package. Run the compiled executable and execute
679 CMD(«echo ${BASH_VERSINFO[@]}»).
682 SECTION(«Job Control»)
684 - suspend/resume selected processes
686 - aim: understand foreground/background jobs, Ctrl+Z, Ctrl+C,
688 - job <=> process group <=> pipeline (+descendants) <=> PGID
689 - (interactive) session := collection of process groups
690 - setsid() syscall creates new session, PGID := PID of calling process
691 - session leader: process which called setsid(), SID: PID of session
693 - terminal's current process group (TPGID)
694 - TPGID determines foreground PG = CMD(«{P: PGID(P) == TPGID}»)
698 - Examine all fields in the output of CMD(«ps j»).
699 - Assume a typical scenario with one background process and another
700 process running in the foreground. How many sessions are there? Which
701 of the three processes are session leaders? Determine all process
702 groups. Verify your result by running CMD(«sleep 100 & ps j»).
703 - What happens if a background process tries to read from
704 CMD(«stdin»)? Verify your answer by executing CMD(«cat &»).
705 - What happens if the session leader terminates while there are
706 still processes running in a background process group? To find out,
707 open a terminal, run CMD(«sleep 100&») and kill the session leader
708 (the shell) with CMD(«kill -9 $$»). Open another terminal and
709 execute CMD(«ps -aj») and examine the row that corresponds to the
710 CMD(«sleep») process.
711 - Look at how bash handles a pipeline by executing CMD(«ps xj |
713 - Verify that in the output of CMD(«ps j») the TPGID and the PID
714 columns coincide while the two columns differ if the command is run
715 in the background (CMD(«ps j &»)). Determine the foreground process
717 - Read the section on job control in the bash manual and make yourself
718 familiar with the various ways to refer to a job in bash (CMD(«%»),
719 CMD(«%n»), CMD(«%-,»), CMD(«%+»)).
723 SUBSECTION(«stale_tmpfile.bash»)
728 f=$(mktemp) || exit 1
729 echo "starting analysis, temporary file: $f"
731 echo "done, removing $f"
736 SUBSECTION(«self-list.bash»)
744 for ((i = 0; i < ${#a[@]}; i++)); do
745 echo "$((i + 1)): ${a[$i]}"
750 SUBSECTION(«rot13.bash»)
756 [a]=n [b]=o [c]=p [d]=q [e]=r [f]=s [g]=t [h]=u [i]=v [j]=w [k]=x
757 [l]=y [m]=z [n]=a [o]=b [p]=c [q]=d [r]=e [s]=f [t]=g [u]=h [v]=i
758 [w]=j [x]=k [y]=l [z]=m
761 while read -r line; do
762 for ((i =0; i < ${#line}; i++)); do
771 SUBSECTION(«catch_the_bug.bash»)
777 # no argument given, choose a random number instead
778 x=$(($RANDOM / 3276 + 1)) # between 1 an 10
782 echo "1/$x is approximately $((100 / $x))%"
786 SUBSECTION(«HungarianCamelSquare.bash»)
791 declare -i ThisVariableIsATemporaryCounter
793 for ((ThisVariableIsATemporaryCounter=0; ThisVariableIsATemporaryCounter < 10; ThisVariableIsATemporaryCounter++)); do
794 echo "$ThisVariableIsATemporaryCounter * $ThisVariableIsATemporaryCounter is $(($ThisVariableIsATenporaryCounter * $ThisVariableIsATemporaryCounter))"
799 SUBSECTION(«rm_tmp.bash»)
804 echo "removing all temporary files in $1"
811 SUBSECTION(«count_config_files.bash»)
817 files=(/etc/$c*.conf)
818 echo "There are ${#files[@]} config files in /etc that start with $c: ${files[@]}"
823 SUBSECTION(«print_login_shells.bash»)
828 while IFS=: read -ra a; do
829 echo "${a[0]} ${a[6]}"
834 SUBSECTION(«minmax.bash»)
847 (($1 < $min)) && min=$1
848 (($1 > $max)) && max=$1
858 local min="${var}_min" max="${var}_max"
860 echo "min: ${!min}, max: ${!max}"