Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 12. External Filters, Programs and Commands | Next |
The standard UNIX archiving utility. Originally a Tape ARchiving program, it has developed into a general purpose package that can handle all manner of archiving with all types of destination devices, ranging from tape drives to regular files to even stdout (see Example 3-4). GNU tar has been patched to accept various compression filters, such as tar czvf archive_name.tar.gz *, which recursively archives and gzips all files in a directory tree except dotfiles in the current working directory ($PWD). [1]
Some useful tar options:
-c create (a new archive)
-x extract (files from existing archive)
--delete delete (files from existing archive)
This option will not work on magnetic tape devices. |
-r append (files to existing archive)
-A append (tar files to existing archive)
-t list (contents of existing archive)
-u update archive
-d compare archive with specified filesystem
-z gzip the archive
(compress or uncompress, depending on whether combined with the -c or -x) option
-j bzip2 the archive
It may be difficult to recover data from a corrupted gzipped tar archive. When archiving important files, make multiple backups. |
Shell archiving utility. The files in a shell archive are concatenated without compression, and the resultant archive is essentially a shell script, complete with #!/bin/sh header, and containing all the necessary unarchiving commands. Shar archives still show up in Internet newsgroups, but otherwise shar has been pretty well replaced by tar/gzip. The unshar command unpacks shar archives.
Creation and manipulation utility for archives, mainly used for binary object file libraries.
The Red Hat Package Manager, or rpm utility provides a wrapper for source or binary archives. It includes commands for installing and checking the integrity of packages, among other things.
A simple rpm -i package_name.rpm usually suffices to install a package, though there are many more options available.
An rpm -qa gives a complete list of all installed rpm packages on a given system. An rpm -qa package_name lists only the package(s) corresponding to package_name.
|
This specialized archiving copy command (copy input and output) is rarely seen any more, having been supplanted by tar/gzip. It still has its uses, such as moving a directory tree.
Example 12-22. Using cpio to move a directory tree
1 #!/bin/bash 2 3 # Copying a directory tree using cpio. 4 5 ARGS=2 6 E_BADARGS=65 7 8 if [ $# -ne "$ARGS" ] 9 then 10 echo "Usage: `basename $0` source destination" 11 exit $E_BADARGS 12 fi 13 14 source=$1 15 destination=$2 16 17 find "$source" -depth | cpio -admvp "$destination" 18 # Read the man page to decipher these cpio options. 19 20 exit 0 |
This command extracts a cpio archive from an rpm one.
Example 12-23. Unpacking an rpm archive
1 #!/bin/bash 2 # de-rpm.sh: Unpack an 'rpm' archive 3 4 : ${1?"Usage: `basename $0` target-file"} 5 # Must specify 'rpm' archive name as an argument. 6 7 8 TEMPFILE=$$.cpio # Tempfile with "unique" name. 9 # $$ is process ID of script. 10 11 rpm2cpio < $1 > $TEMPFILE # Converts rpm archive into cpio archive. 12 cpio --make-directories -F $TEMPFILE -i # Unpacks cpio archive. 13 rm -f $TEMPFILE # Deletes cpio archive. 14 15 exit 0 16 17 # Exercise: 18 # Add check for whether 1) "target-file" exists and 19 #+ 2) it is really an rpm archive. 20 # Hint: parse output of 'file' command. |
The standard GNU/UNIX compression utility, replacing the inferior and proprietary compress. The corresponding decompression command is gunzip, which is the equivalent of gzip -d.
The zcat filter decompresses a gzipped file to stdout, as possible input to a pipe or redirection. This is, in effect, a cat command that works on compressed files (including files processed with the older compress utility). The zcat command is equivalent to gzip -dc.
On some commercial UNIX systems, zcat is a synonym for uncompress -c, and will not work on gzipped files. |
See also Example 7-7.
An alternate compression utility, usually more efficient (but slower) than gzip, especially on large files. The corresponding decompression command is bunzip2.
Newer versions of tar have been patched with bzip2 support. |
This is an older, proprietary compression utility found in commercial UNIX distributions. The more efficient gzip has largely replaced it. Linux distributions generally include a compress workalike for compatibility, although gunzip can unarchive files treated with compress.
The znew command transforms compressed files into gzipped ones. |
Yet another compression utility, a filter that works only on sorted ASCII word lists. It uses the standard invocation syntax for a filter, sq < input-file > output-file. Fast, but not nearly as efficient as gzip. The corresponding uncompression filter is unsq, invoked like sq.
The output of sq may be piped to gzip for further compression. |
Cross-platform file archiving and compression utility compatible with DOS pkzip.exe. "Zipped" archives seem to be a more acceptable medium of exchange on the Internet than "tarballs".
These Linux utilities permit unpacking archives compressed with the DOS arc.exe, arj.exe, and rar.exe programs.
A utility for identifying file types. The command file file-name will return a file specification for file-name, such as ascii text or data. It references the magic numbers found in /usr/share/magic, /etc/magic, or /usr/lib/magic, depending on the Linux/UNIX distribution.
The -f option causes file to run in batch mode, to read from a designated file a list of filenames to analyze. The -z option, when used on a compressed target file, forces an attempt to analyze the uncompressed file type.
bash$ file test.tar.gz test.tar.gz: gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix bash file -z test.tar.gz test.tar.gz: GNU tar archive (gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix) |
Example 12-24. Stripping comments from C program files
1 #!/bin/bash 2 # strip-comment.sh: Strips out the comments (/* COMMENT */) in a C program. 3 4 E_NOARGS=65 5 E_ARGERROR=66 6 E_WRONG_FILE_TYPE=67 7 8 if [ $# -eq "$E_NOARGS" ] 9 then 10 echo "Usage: `basename $0` C-program-file" >&2 # Error message to stderr. 11 exit $E_ARGERROR 12 fi 13 14 # Test for correct file type. 15 type=`eval file $1 | awk '{ print $2, $3, $4, $5 }'` 16 # "file $1" echoes file type... 17 # then awk removes the first field of this, the filename... 18 # then the result is fed into the variable "type". 19 correct_type="ASCII C program text" 20 21 if [ "$type" != "$correct_type" ] 22 then 23 echo 24 echo "This script works on C program files only." 25 echo 26 exit $E_WRONG_FILE_TYPE 27 fi 28 29 30 # Rather cryptic sed script: 31 #-------- 32 sed ' 33 /^\/\*/d 34 /.*\/\*/d 35 ' $1 36 #-------- 37 # Easy to understand if you take several hours to learn sed fundamentals. 38 39 40 # Need to add one more line to the sed script to deal with 41 #+ case where line of code has a comment following it on same line. 42 # This is left as a non-trivial exercise. 43 44 # Also, the above code deletes lines with a "*/" or "/*", 45 # not a desirable result. 46 47 exit 0 48 49 50 # ---------------------------------------------------------------- 51 # Code below this line will not execute because of 'exit 0' above. 52 53 # Stephane Chazelas suggests the following alternative: 54 55 usage() { 56 echo "Usage: `basename $0` C-program-file" >&2 57 exit 1 58 } 59 60 WEIRD=`echo -n -e '\377'` # or WEIRD=$'\377' 61 [[ $# -eq 1 ]] || usage 62 case `file "$1"` in 63 *"C program text"*) sed -e "s%/\*%${WEIRD}%g;s%\*/%${WEIRD}%g" "$1" \ 64 | tr '\377\n' '\n\377' \ 65 | sed -ne 'p;n' \ 66 | tr -d '\n' | tr '\377' '\n';; 67 *) usage;; 68 esac 69 70 # This is still fooled by things like: 71 # printf("/*"); 72 # or 73 # /* /* buggy embedded comment */ 74 # 75 # To handle all special cases (comments in strings, comments in string 76 # where there is a \", \\" ...) the only way is to write a C parser 77 # (lex or yacc perhaps?). 78 79 exit 0 |
which command-xxx gives the full path to "command-xxx". This is useful for finding out whether a particular command or utility is installed on the system.
$bash which rm
/usr/bin/rm |
Similar to which, above, whereis command-xxx gives the full path to "command-xxx", but also to its manpage.
$bash whereis rm
rm: /bin/rm /usr/share/man/man1/rm.1.bz2 |
whatis filexxx looks up "filexxx" in the whatis database. This is useful for identifying system commands and important configuration files. Consider it a simplified man command.
$bash whatis whatis
whatis (1) - search the whatis database for complete words |
Example 12-25. Exploring /usr/X11R6/bin
1 #!/bin/bash 2 3 # What are all those mysterious binaries in /usr/X11R6/bin? 4 5 DIRECTORY="/usr/X11R6/bin" 6 # Try also "/bin", "/usr/bin", "/usr/local/bin", etc. 7 8 for file in $DIRECTORY/* 9 do 10 whatis `basename $file` # Echoes info about the binary. 11 done 12 13 exit 0 14 # You may wish to redirect output of this script, like so: 15 # ./what.sh >>whatis.db 16 # or view it a page at a time on stdout, 17 # ./what.sh | less |
See also Example 10-3.
Show a detailed directory listing. The effect is similar to ls -l.
This is one of the GNU fileutils.
bash$ vdir total 10 -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.xrolo -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.xrolo.bak -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.xrolo bash ls -l total 10 -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.xrolo -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.xrolo.bak -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.xrolo |
The locate command searches for files using a database stored for just that purpose. The slocate command is the secure version of locate (which may be aliased to slocate).
$bash locate hickson
/usr/lib/xephem/catalogs/hickson.edb |
Disclose the file that a symbolic link points to.
bash$ readlink /usr/bin/awk ../../bin/gawk |
Use the strings command to find printable strings in a binary or data file. It will list sequences of printable characters found in the target file. This might be handy for a quick 'n dirty examination of a core dump or for looking at an unknown graphic image file (strings image-file | more might show something like JFIF, which would identify the file as a jpeg graphic). In a script, you would probably parse the output of strings with grep or sed. See Example 10-7 and Example 10-9.
Example 12-26. An "improved" strings command
1 #!/bin/bash 2 # wstrings.sh: "word-strings" (enhanced "strings" command) 3 # 4 # This script filters the output of "strings" by checking it 5 #+ against a standard word list file. 6 # This effectively eliminates all the gibberish and noise, 7 #+ and outputs only recognized words. 8 9 # ================================================================= 10 # Standard Check for Script Argument(s) 11 ARGS=1 12 E_BADARGS=65 13 E_NOFILE=66 14 15 if [ $# -ne $ARGS ] 16 then 17 echo "Usage: `basename $0` filename" 18 exit $E_BADARGS 19 fi 20 21 if [ ! -f "$1" ] # Check if file exists. 22 then 23 echo "File \"$1\" does not exist." 24 exit $E_NOFILE 25 fi 26 # ================================================================= 27 28 29 MINSTRLEN=3 # Minimum string length. 30 WORDFILE=/usr/share/dict/linux.words # Dictionary file. 31 # May specify a different 32 #+ word list file 33 #+ of format 1 word per line. 34 35 36 wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \ 37 tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '` 38 39 # Translate output of 'strings' command with multiple passes of 'tr'. 40 # "tr A-Z a-z" converts to lowercase. 41 # "tr '[:space:]'" converts whitespace characters to Z's. 42 # "tr -cs '[:alpha:]' Z" converts non-alphabetic characters to Z's, 43 #+ and squeezes multiple consecutive Z's. 44 # "tr -s '\173-\377' Z" converts all characters past 'z' to Z's 45 #+ and squeezes multiple consecutive Z's, 46 #+ which gets rid of all the weird characters that the previous 47 #+ translation failed to deal with. 48 # Finally, "tr Z ' '" converts all those Z's to whitespace, 49 #+ which will be seen as word separators in the loop below. 50 51 # Note the technique of feeding the output of 'tr' back to itself, 52 #+ but with different arguments and/or options on each pass. 53 54 55 for word in $wlist # Important: 56 # $wlist must not be quoted here. 57 # "$wlist" does not work. 58 # Why? 59 do 60 61 strlen=${#word} # String length. 62 if [ "$strlen" -lt "$MINSTRLEN" ] # Skip over short strings. 63 then 64 continue 65 fi 66 67 grep -Fw $word "$WORDFILE" # Match whole words only. 68 69 done 70 71 72 exit 0 |
diff: flexible file comparison utility. It compares the target files line-by-line sequentially. In some applications, such as comparing word dictionaries, it may be helpful to filter the files through sort and uniq before piping them to diff. diff file-1 file-2 outputs the lines in the files that differ, with carets showing which file each particular line belongs to.
The --side-by-side option to diff outputs each compared file, line by line, in separate columns, with non-matching lines marked. The -c and -u options likewise make the output of the command easier to interpret.
There are available various fancy frontends for diff, such as spiff, wdiff, xdiff, and mgdiff.
The diff command returns an exit status of 0 if the compared files are identical, and 1 if they differ. This permits use of diff in a test construct within a shell script (see below). |
A common use for diff is generating difference files to be used with patch The -e option outputs files suitable for ed or ex scripts.
patch: flexible versioning utility. Given a difference file generated by diff, patch can upgrade a previous version of a package to a newer version. It is much more convenient to distribute a relatively small "diff" file than the entire body of a newly revised package. Kernel "patches" have become the preferred method of distributing the frequent releases of the Linux kernel.
1 patch -p1 <patch-file 2 # Takes all the changes listed in 'patch-file' 3 # and applies them to the files referenced therein. 4 # This upgrades to a newer version of the package. |
Patching the kernel:
1 cd /usr/src 2 gzip -cd patchXX.gz | patch -p0 3 # Upgrading kernel source using 'patch'. 4 # From the Linux kernel docs "README", 5 # by anonymous author (Alan Cox?). |
The diff command can also recursively compare directories (for the filenames present).
|
Use zdiff to compare gzipped files. |
An extended version of diff that compares three files at a time. This command returns an exit value of 0 upon successful execution, but unfortunately this gives no information about the results of the comparison.
bash$ diff3 file-1 file-2 file-3 ==== 1:1c This is line 1 of "file-1". 2:1c This is line 1 of "file-2". 3:1c This is line 1 of "file-3" |
Compare and/or edit two files in order to merge them into an output file. Because of its interactive nature, this command would find little use in a script.
The cmp command is a simpler version of diff, above. Whereas diff reports the differences between two files, cmp merely shows at what point they differ.
Like diff, cmp returns an exit status of 0 if the compared files are identical, and 1 if they differ. This permits use in a test construct within a shell script. |
Example 12-27. Using cmp to compare two files within a script.
1 #!/bin/bash 2 3 ARGS=2 # Two args to script expected. 4 E_BADARGS=65 5 E_UNREADABLE=66 6 7 if [ $# -ne "$ARGS" ] 8 then 9 echo "Usage: `basename $0` file1 file2" 10 exit $E_BADARGS 11 fi 12 13 if [[ ! -r "$1" || ! -r "$2" ]] 14 then 15 echo "Both files to be compared must exist and be readable." 16 exit $E_UNREADABLE 17 fi 18 19 cmp $1 $2 &> /dev/null # /dev/null buries the output of the "cmp" command. 20 # cmp -s $1 $2 has same result ("-s" silent flag to "cmp") 21 # Thank you Anders Gustavsson for pointing this out. 22 # 23 # Also works with 'diff', i.e., diff $1 $2 &> /dev/null 24 25 if [ $? -eq 0 ] # Test exit status of "cmp" command. 26 then 27 echo "File \"$1\" is identical to file \"$2\"." 28 else 29 echo "File \"$1\" differs from file \"$2\"." 30 fi 31 32 exit 0 |
Use zcmp on gzipped files. |
Versatile file comparison utility. The files must be sorted for this to be useful.
comm -options first-file second-file
comm file-1 file-2 outputs three columns:
column 1 = lines unique to file-1
column 2 = lines unique to file-2
column 3 = lines common to both.
The options allow suppressing output of one or more columns.
-1 suppresses column 1
-2 suppresses column 2
-3 suppresses column 3
-12 suppresses both columns 1 and 2, etc.
Strips the path information from a file name, printing only the file name. The construction basename $0 lets the script know its name, that is, the name it was invoked by. This can be used for "usage" messages if, for example a script is called with missing arguments:
1 echo "Usage: `basename $0` arg1 arg2 ... argn" |
Strips the basename from a filename, printing only the path information.
basename and dirname can operate on any arbitrary string. The argument does not need to refer to an existing file, or even be a filename for that matter (see Example A-8). |
Example 12-28. basename and dirname
1 #!/bin/bash 2 3 a=/home/bozo/daily-journal.txt 4 5 echo "Basename of /home/bozo/daily-journal.txt = `basename $a`" 6 echo "Dirname of /home/bozo/daily-journal.txt = `dirname $a`" 7 echo 8 echo "My own home is `basename ~/`." # Also works with just ~. 9 echo "The home of my home is `dirname ~/`." # Also works with just ~. 10 11 exit 0 |
Utility for splitting a file into smaller chunks. Usually used for splitting up large files in order to back them up on floppies or preparatory to e-mailing or uploading them.
These are utilities for generating checksums. A checksum is a number mathematically calculated from the contents of a file, for the purpose of checking its integrity. A script might refer to a list of checksums for security purposes, such as ensuring that the contents of key system files have not been altered or corrupted. For security applications, use the 128-bit md5sum (message digest checksum) command.
bash$ cksum /boot/vmlinuz 1670054224 804083 /boot/vmlinuz bash$ md5sum /boot/vmlinuz 0f43eccea8f09e0a0b2b5cf1dcf333ba /boot/vmlinuz |
Note that cksum also shows the size, in bytes, of the target file.
Example 12-29. Checking file integrity
1 #!/bin/bash 2 # file-integrity.sh: Checking whether files in a given directory 3 # have been tampered with. 4 5 E_DIR_NOMATCH=70 6 E_BAD_DBFILE=71 7 8 dbfile=File_record.md5 9 # Filename for storing records. 10 11 12 set_up_database () 13 { 14 echo ""$directory"" > "$dbfile" 15 # Write directory name to first line of file. 16 md5sum "$directory"/* >> "$dbfile" 17 # Append md5 checksums and filenames. 18 } 19 20 check_database () 21 { 22 local n=0 23 local filename 24 local checksum 25 26 # ------------------------------------------- # 27 # This file check should be unnecessary, 28 #+ but better safe than sorry. 29 30 if [ ! -r "$dbfile" ] 31 then 32 echo "Unable to read checksum database file!" 33 exit $E_BAD_DBFILE 34 fi 35 # ------------------------------------------- # 36 37 while read record[n] 38 do 39 40 directory_checked="${record[0]}" 41 if [ "$directory_checked" != "$directory" ] 42 then 43 echo "Directories do not match up!" 44 # Tried to use file for a different directory. 45 exit $E_DIR_NOMATCH 46 fi 47 48 if [ "$n" -gt 0 ] # Not directory name. 49 then 50 filename[n]=$( echo ${record[$n]} | awk '{ print $2 }' ) 51 # md5sum writes records backwards, 52 #+ checksum first, then filename. 53 checksum[n]=$( md5sum "${filename[n]}" ) 54 55 if [ "${record[n]}" = "${checksum[n]}" ] 56 then 57 echo "${filename[n]} unchanged." 58 else 59 echo "${filename[n]} : CHECKSUM ERROR!" 60 # File has been changed since last checked. 61 fi 62 63 fi 64 65 66 let "n+=1" 67 done <"$dbfile" # Read from checksum database file. 68 69 } 70 71 # =================================================== # 72 # main () 73 74 if [ -z "$1" ] 75 then 76 directory="$PWD" # If not specified, 77 else #+ use current working directory. 78 directory="$1" 79 fi 80 81 clear # Clear screen. 82 83 # ------------------------------------------------------------------ # 84 if [ ! -r "$dbfile" ] # Need to create database file? 85 then 86 echo "Setting up database file, \""$directory"/"$dbfile"\"."; echo 87 set_up_database 88 fi 89 # ------------------------------------------------------------------ # 90 91 check_database # Do the actual work. 92 93 echo 94 95 # You may wish to redirect the stdout of this script to a file, 96 #+ especially if the directory checked has many files in it. 97 98 # For a much more thorough file integrity check, 99 #+ consider the "Tripwire" package, 100 #+ http://sourceforge.net/projects/tripwire/. 101 102 exit 0 |
See also Example A-20 for a creative use of the md5sum command.
Securely erase a file by overwriting it multiple times with random bit patterns before deleting it. This command has the same effect as Example 12-42, but does it in a more thorough and elegant manner.
This is one of the GNU fileutils.
Advanced forensic technology may still be able to recover the contents of a file, even after application of shred. |
This utility encodes binary files into ASCII characters, making them suitable for transmission in the body of an e-mail message or in a newsgroup posting.
This reverses the encoding, decoding uuencoded files back into the original binaries.
Example 12-30. Uudecoding encoded files
1 #!/bin/bash 2 3 lines=35 # Allow 35 lines for the header (very generous). 4 5 for File in * # Test all the files in the current working directory... 6 do 7 search1=`head -$lines $File | grep begin | wc -w` 8 search2=`tail -$lines $File | grep end | wc -w` 9 # Uuencoded files have a "begin" near the beginning, 10 #+ and an "end" near the end. 11 if [ "$search1" -gt 0 ] 12 then 13 if [ "$search2" -gt 0 ] 14 then 15 echo "uudecoding - $File -" 16 uudecode $File 17 fi 18 fi 19 done 20 21 # Note that running this script upon itself fools it 22 #+ into thinking it is a uuencoded file, 23 #+ because it contains both "begin" and "end". 24 25 # Exercise: 26 # Modify this script to check for a newsgroup header. 27 28 exit 0 |
The fold -s command may be useful (possibly in a pipe) to process long uudecoded text messages downloaded from Usenet newsgroups. |
The mimencode and mmencode commands process multimedia-encoded e-mail attachments. Although 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.
At one time, this was the standard UNIX file encryption utility. [2] Politically motivated government regulations prohibiting the export of encryption software resulted in the disappearance of crypt from much of the UNIX world, and it is still missing from most Linux distributions. Fortunately, programmers have come up with a number of decent alternatives to it, among them the author's very own cruft (see Example A-5).
Create a temporary file with a "unique" filename.
1 PREFIX=filename 2 tempfile=`mktemp $PREFIX.XXXXXX` 3 # ^^^^^^ Need at least 6 placeholders 4 #+ in the filename template. 5 echo "tempfile name = $tempfile" 6 # tempfile name = filename.QA2ZpY 7 # or something similar... |
Utility for building and compiling binary packages. This can also be used for any set of operations that is triggered by incremental changes in source files.
The make command checks a Makefile, a list of file dependencies and operations to be carried out.
Special purpose file copying command, similar to cp, but capable of setting permissions and attributes of the copied files. This command seems tailormade for installing software packages, and as such it shows up frequently in Makefiles (in the make install : section). It could likewise find use in installation scripts.
This utility, written by Benjamin Lin and collaborators, converts DOS-formatted text files (lines terminated by CR-LF) to UNIX format (lines terminated by LF only), and vice-versa.
The ptx [targetfile] command outputs a permuted index (cross-reference list) of the targetfile. This may be further filtered and formatted in a pipe, if necessary.
Pagers that display a text file or stream to stdout, one screenful at a time. These may be used to filter the output of a script.
[1] | A tar czvf archive_name.tar.gz * will include dotfiles in directories below the current working directory. This is an undocumented GNU tar "feature". |
[2] | This is a symmetric block cipher, used to encrypt files on a single system or local network, as opposed to the "public key" cipher class, of which pgp is a well-known example. |