home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / unix / volume27 / qt / part01 < prev    next >
Encoding:
Text File  |  1993-10-18  |  68.9 KB  |  1,961 lines

  1. Newsgroups: comp.sources.unix
  2. From: john@johncon.com (John Conover)
  3. Subject: v27i075: qt - full-text retrieval program, Part01/01
  4. Message-id: <1.750984870.12686@gw.home.vix.com>
  5. Sender: unix-sources-moderator@gw.home.vix.com
  6. Approved: vixie@gw.home.vix.com
  7.  
  8. Submitted-By: john@johncon.com (John Conover)
  9. Posting-Number: Volume 27, Issue 75
  10. Archive-Name: qt/part01
  11.  
  12. Qt stands for Query Text, a text information retrieval system. Qt
  13. creates, maintains, and queries a full text database. The database
  14. file system is organized as an inverted index. The program is written
  15. as a single script, in Bourne Shell, and permits simple natural
  16. language queries.
  17.  
  18. Environment: Unix, SysV. rel. 4.x, R6000, DEC ALPHA 3000, others.
  19.  
  20.     john@johncon.com
  21.  
  22. #!/bin/sh
  23. # This is a shell archive (produced by shar 3.49)
  24. # To extract the files from this archive, save it to a file, remove
  25. # everything above the "!/bin/sh" line above, and type "sh file_name".
  26. #
  27. # made 09/16/1993 02:58 UTC by john@johncon
  28. # Source directory /home/john
  29. #
  30. # existing files will NOT be overwritten unless -c is specified
  31. #
  32. # This shar contains:
  33. # length  mode       name
  34. # ------ ---------- ------------------------------------------
  35. #  65404 -rwxrw-rw- qt/qt
  36. #   1844 -rw-r--r-- qt/README
  37. #
  38. # ============= qt/qt ==============
  39. if test ! -d 'qt'; then
  40.     echo 'x - creating directory qt'
  41.     mkdir 'qt'
  42. fi
  43. if test -f 'qt/qt' -a X"$1" != X"-c"; then
  44.     echo 'x - skipping qt/qt (File already exists)'
  45. else
  46. echo 'x - extracting qt/qt (Text)'
  47. sed 's/^X//' << 'SHAR_EOF' > 'qt/qt' &&
  48. #!/bin/sh
  49. #
  50. VERSION="qt - Version 0.1. (qt -h, for description and help.)"
  51. #
  52. # Qt stands for Query Text, a text information retrieval system. Qt
  53. # creates, maintains, and queries a full text database. The database
  54. # file system is organized as an inverted index. The program is written
  55. # as a single script, in Bourne Shell, and permits simple natural
  56. # language queries.
  57. #
  58. # As a simple application example, this program can be used to search
  59. # the "catman" pages for a command that performs a specific function,
  60. # even though the command's name is not known-e.g., if you knew what
  61. # you wanted to do, you could find the command that would do it.
  62. #
  63. # The program, qt, is free software, and can be redistributed and/or
  64. # modified, without any restrictions. It is distributed with no
  65. # warranty of any kind, implied or otherwise.  Specifically, there is
  66. # no warranty of fitness for any particular purpose and/or
  67. # merchantability.
  68. #
  69. # Comments and/or bug reports should be addressed to:
  70. #
  71. #    john@johncon.com (John Conover)
  72. #
  73. # Known caveats: There is no concurrency control-it would be
  74. # ill-advised to use this program as a concurrent application.
  75. # Additionally, the natural language query does not support grouping
  76. # operators.
  77. #
  78. # For a quick start, execute qt -h for help, which may be re-directed to
  79. # stdio. At the "tail -23" of this help file are some simple commands to
  80. # evaluate this script.
  81. #
  82. # Installation:
  83. #
  84. # The comments in this script are verbose, and should be stripped prior
  85. # to any installation with something like:
  86. #
  87. #    sed '/^ *#/d;/^$/d' qt > qt.new
  88. #
  89. # and installing qt.new as qt in the executable path. Likewise,
  90. # possibly, the function, help(), should be eliminated.     The function,
  91. # find_program(), is not efficient and should be eliminated, by hard
  92. # coding the paths to the various programs in your system.  There are
  93. # tab characters used in this script, (which are referenced as the
  94. # variable, "${TAB}") requiring that the script be saved with tabs.
  95. #
  96. # Applicability:
  97. #
  98. # Applicability of qt varies on complexity of search, size of database,
  99. # speed of host environment, etc., however, as some general guidelines:
  100. #
  101. #    1) For text files with a total size of less than 5 MB,
  102. #    standard egrep(1) queries of the text files will probably
  103. #    prove adequate.
  104. #
  105. #    2) For text files with a total size of 5 MB to 50 MB, qt seems
  106. #    adequate for most queries. The significant issue is that,
  107. #    although the retrieval execution times are probably adequate
  108. #    with qt, the database write times are not impressive.
  109. #
  110. #    3) For text files with a total size that is larger than 50 MB,
  111. #    or where concurrency is an issue, it would be appropriate to
  112. #    consider one of the alternatives listed in "Related
  113. #    information retrieval software:," below.
  114. #
  115. # References:
  116. #
  117. #    1) "Information Retrieval, Data Structures & Algorithms,"
  118. #    William B. Frakes, Ricardo Baeza-Yates, Editors, Prentice
  119. #    Hall, Englewood Cliffs, New Jersey 07632, 1992, ISBN
  120. #    0-13-463837-9.
  121. #
  122. #    The sources for the many of the algorithms presented in 1) are
  123. #    available by ftp, ftp.vt.edu:/pub/reuse/ircode.tar.Z
  124. #
  125. #    2) "Text Information Retrieval Systems," Charles T. Meadow,
  126. #    Academic Press, Inc, San Diego, 1992, ISBN 0-12-487410-X.
  127. #
  128. #    3) "Full Text Databases," Carol Tenopir, Jung Soon Ro,
  129. #    Greenwood Press, New York, 1990, ISBN 0-313-26303-5.
  130. #
  131. #    4) "Text and Context, Document Processing and Storage," Susan
  132. #    Jones, Springer-Verlag, New York, 1991, ISBN 0-387-19604-8.
  133. #
  134. #    5) ftp think.com:/wais/wais-corporate-paper.text
  135. #
  136. #    6) ftp cs.toronto.edu:/pub/lq-text.README.1.10
  137. #
  138. #    7) "Unix Shell Programming," Lowell Jay Arthur, John Wiley &
  139. #    Sons, Inc., New York, 1990, ISBN 0-471-51820-4.
  140. #
  141. # Related information retrieval software:
  142. #
  143. #    1) Wais, available by ftp, think.com:/wais/wais-8-b5.1.tar.Z
  144. #
  145. #    2) lq-text, available by ftp, cs.toronto.edu:
  146. #    /pub/lq-text1.10.tar.Z
  147. #
  148. # This script uses the Unix concept of a simple flat text file as a
  149. # database, operated on by the various utilities native to the Unix
  150. # system. The flat text file's organization is exactly one record for
  151. # each file that contains at least one instance of a specific word.
  152. # Each record in the flat text file has exactly two fields. These two
  153. # fields are the word, followed by a single "${TAB}" field delimiter,
  154. # and the file name containing the word. The record sequence in the
  155. # flat text file is the ASCII collated sequence of the word field.
  156. #
  157. # This organization of flat text file is an inverted index database,
  158. # ie., the file names of files that contain a specific word can be
  159. # found, using a binary search program, (like the native Unix program,
  160. # "look.") The inverted index file records can be created by parsing
  161. # the words in textural documents (perhaps using the native Unix
  162. # programs, "tr" and "sed,") and concatenating these words with a
  163. # single "${TAB}" and the file name of the file containing the word.
  164. # These records can then be sorted into ASCII collation sequence,
  165. # (using, for example the Unix program, "sort" -u.) Obviously, two
  166. # sorted inverted index databases could be combined with the "sort"
  167. # -um, command.     File names could be removed from the inverted index
  168. # database with the "egrep" -v "*${TAB}filename" command, and words
  169. # removed with the "egrep" -v "^word${TAB}" command, and so on. This
  170. # script uses only a few of the native Unix programs to construct an
  171. # inverted index database system.
  172. #
  173. # The functions contained in this script:
  174. #
  175. #    1) find_program(), find if a program exists.
  176. #    2) help(), help.
  177. #    3) read_index (), query the inverted index file for word(s).
  178. #    4) write_index(), index the words in the files.
  179. #    5) update_database(), update the database.
  180. #    6) parse_word(), lexical analysis.
  181. #    7) remove_words(), remove words from the inverted index.
  182. #    8) remove_files(), remove files from the inverted index.
  183. #    9) relevance_count(), relevance count.
  184. #    10) relevance_proximity(), proximity retrieval.
  185. #
  186. # The functions, remove_words(), remove_files(), relevance_count() and
  187. # relevance_proximity() are included to serve as templates for further
  188. # applications. Probably, in the interest of generality, they should
  189. # not be included in the program since they can be completely
  190. # implemented as external scripts, aliases, or pipes from the output of
  191. # qt.
  192. #
  193. # This program will create and query inverted index files that index
  194. # the words in text files. These indices are useful in information
  195. # retrieval systems. The inverted index files are, typically, about the
  196. # same size of the text files, and do not require the text files to be
  197. # present for query operations. The query functions, typically, consist
  198. # of boolean operations on word searches. The output of the query is,
  199. # typically, a list of the file names that contain the queried word(s).
  200. #
  201. # The read synopsis is:
  202. #
  203. #    qt [-e] [-r | -rc | -rp] [-f index_name] word1 [op1] word2 [op2] ...
  204. #
  205. # where word1-word2 ... are the words to be queried in the inverted
  206. # index, and op1-op2 ... are the operations to be performed on the set
  207. # of file names that contain these words. The word/operation arguments
  208. # consist of pairs of search words, and boolean operators, with a left
  209. # to right operational precedence.
  210. #
  211. # Thus if A, B, and C are words, then the query:
  212. #
  213. #    A and B or C not D
  214. #
  215. # would specify that all file names containing word A should be found,
  216. # then all the file names containing word B should be found, and only
  217. # those file names that contain words A and B should be added to those
  218. # file names containing word C, and then if these file names do not
  219. # contain word D, they are output.
  220. #
  221. # Logical "or'ing" is implicit, thus:
  222. #
  223. #    A B C
  224. #
  225. # is identical to:
  226. #
  227. #    A or B or C
  228. #
  229. # Obviously, the keywords, "and," "not," and "or," may not be queried
  230. # for, when using the implicit "or" query constructs.
  231. #
  232. # If the "-e" option is specified, then "exact match" queries will be
  233. # performed, otherwise, a "partial key" type of search will be
  234. # performed, which is the default. It is recommended that the "-e"
  235. # option be used if the query involves any boolean operations.
  236. #
  237. # If the "-r" option is specified, then the words being queried for
  238. # will be output before the list file names that contain the queried
  239. # words.  This output format is compatible with egrep(1), and is useful
  240. # in doing "relevance feedback" searches.
  241. #
  242. # If the "-rc" option is specified, then the count of records in a file
  243. # that contain match(s) will be output with the file name containing
  244. # the matches.    This provides the system with a remedial "relevance
  245. # feedback" capability. The original text files that were used to
  246. # construct the inverted index file must be available in the system to
  247. # use this option.
  248. #
  249. # If the "-rp" option is specified, then the records in a file that
  250. # contain match(s) will be output after the file name containing the
  251. # matches. This output format provides the system with a remedial
  252. # "permuted index" type of "proximity retrieval." The original text
  253. # files that were used to construct the inverted index must be
  254. # available in the system to use this option.
  255. #
  256. # The write synopsis is:
  257. #
  258. #    qt -w [-f index_name] [-1 | ... | -8] file1 file2 ...
  259. #
  260. # where file1 file2 ... are the file names that contain words that are
  261. # to be added to the inverted index, or:
  262. #
  263. #    qt -w [-f index_name] [-1 | ... | -8] < file_list
  264. #
  265. # where file_list is the name of a file that contains a list of file
  266. # names, one file name per record, that contain words that are to be
  267. # added to the inverted index.
  268. #
  269. # It is recommended that file names contain the absolute path to the
  270. # system's root directory.
  271. #
  272. # If the inverted index file does not exist, then it will be created,
  273. # and contain an index to all of the words in the input files. If the
  274. # inverted index file exists, then the indices of all words in the
  275. # input files will be added, incrementally. Instances of words and
  276. # filename pairs will be unique in the inverted index.
  277. #
  278. # The "-w" option, specifies that write operations will be performed,
  279. # and is a mandatory option, to be used if and only if write operations
  280. # are desired.
  281. #
  282. # The "-f index_name," optionally, specifies the inverted index file's
  283. # name. If the "-f" option is not specified, the inverted index file
  284. # name will default to "qt.index."
  285. #
  286. # The lexical analyzer level is specified by, "-1", through "-8". If
  287. # none are specified, the default, "-4", will be used. The lexical
  288. # analyzers with larger numbers are, generally, more sophisticated
  289. # about the words that are placed in the inverted index. The lexical
  290. # analyzers available are:
  291. #
  292. #    1) Parses words and numbers. All other characters are omitted.
  293. #    Capitalization is preserved. Probably the best choice if
  294. #    non-word searches are important.
  295. #
  296. #    2) Like 1) above, but the '_' character is recognized. This
  297. #    parser seems to work well with "C" program source files.
  298. #
  299. #    3) Like 1) above, but, only words of more than two characters
  300. #    are placed in the inverted index file. If capitalization is
  301. #    considered important in the search criteria, then this seems
  302. #    to be the best choice.
  303. #
  304. #    4) Like 1) above, but, capitalization is ignored. For general
  305. #    text where all words and numbers are considered significant,
  306. #    this seems to be the best choice. Also seems a good choice for
  307. #    "catman" pages.     Queries should be in lowercase.
  308. #
  309. #    5) Like 3) above, but capitalization is ignored. For general
  310. #    text, this seems to be the best choice. Queries should be in
  311. #    lowercase.
  312. #
  313. #    6) Like 4) above, but words containing only numbers are
  314. #    omitted from the inverted index file. For text containing only
  315. #    words, this seems to be the best choice. Queries should be in
  316. #    lowercase.
  317. #
  318. #    7) Like 4) above, but does not include Unix mail headers in
  319. #    the inverted index file. Each email should be in a separate
  320. #    file, as opposed to concatenated into folders. This seems to
  321. #    be the best choice for Unix mail files, if the header
  322. #    information is not desirable.
  323. #
  324. #    8) Like 4) above, but deletes TeX and/or LaTeX commands from
  325. #    the inverted index file. This seems to be the best choice for
  326. #    TeX and LaTeX documents.
  327. #
  328. # The more sophisticated the parser, the smaller the size of the
  329. # inverted index file. Multiple runs can be made, using the different
  330. # parsers, to store words in the inverted index. For example, using
  331. # parsers 3) and 4) would place both the capitalized and
  332. # non-capitalized words in the index.  This would not duplicate any
  333. # words already in the index-only add the words that were different.
  334. #
  335. # The remove words synopsis is:
  336. #
  337. #    qt -w -dw [-f index_name] word1 word2 ...
  338. #
  339. # where word1 word2 ... are the words that are to be deleted from the
  340. # inverted index file, and may be a regular expressions-no '^' or '$'
  341. # characters should be used, unless they are escaped. The "-w" option
  342. # is mandatory.
  343. #
  344. # The remove files synopsis is:
  345. #
  346. #    qt -w -df [-f index_name] file1 file2 ...
  347. #
  348. # where file1 file2 ... are the file names that are to be deleted from
  349. # inverted index. The file names to be deleted from the inverted index
  350. # file may be regular expressions-no '^' or '$' characters should be
  351. # used, unless they are escaped. The "-w" option is mandatory.
  352. #
  353. # The version synopsis is:
  354. #
  355. #    qt -v
  356. #
  357. # which will print the version number of qt.
  358. #
  359. # The help synopsis is:
  360. #
  361. #    qt -h
  362. #
  363. # which will list a synopsis of the command semantics.
  364. #
  365. # A common example of writing an inverted index file would be:
  366. #
  367. #    find /dir1/dir2 -type f -print | qt -w
  368. #
  369. # which would recursively descend through the directory hierarchy, and
  370. # create an inverted index of all of the words in all of the files in
  371. # all of the directories, starting with /dir1/dir2.
  372. #
  373. # A common example of retrieving information from an inverted index
  374. # file would be:
  375. #
  376. #    more +/word `qt word`
  377. #
  378. # where the "more" program would page through the documents that
  379. # contain "word," advancing to the next instance every time the 'n' key
  380. # is depressed.
  381. #
  382. # A common example of relevance determination in retrieving information
  383. # from an inverted index file would be:
  384. #
  385. #    egrep -ic `qt -r word` | sort -n -r -t: +1
  386. #
  387. # which would print the file(s) that contain "word," with the count of
  388. # the instances of records that contain "word" in each of the file(s).
  389. #
  390. # Since the inverted index file constitutes a database system, care of
  391. # how this file is manipulated is important. The general procedure used
  392. # in this script is as follows:
  393. #
  394. #    1) When this script commences execution, a test is made for
  395. #    the existence of a backup of an original inverted index file,
  396. #    which was created in step 3), below. (Presumably, this file
  397. #    was left by failed attempt(s) of step(s) 3), 4), or 5), by a
  398. #    prior, unsuccessful, execution of this script.) If the backup
  399. #    file exists, it is unconditionally moved, via the "mv"
  400. #    command, to be the current, original database. If this
  401. #    operation is successful, then step 2) is executed, if not, the
  402. #    script aborts.
  403. #
  404. #    2) After any original inverted index file backup is restored,
  405. #    all write operations to the database are written to a
  406. #    temporary file-including duplication of any required data from
  407. #    the current inverted index file. This temporary file will
  408. #    become the new inverted index file. If these operations are
  409. #    successful, then step 3) is executed, if not, the script
  410. #    aborts.
  411. #
  412. #    3) After all write operations are completed, the original
  413. #    inverted index file is backed up, using the "mv" command. If
  414. #    this operation successful, then step 4) is executed, if not,
  415. #    the script aborts.
  416. #
  417. #    4) After the original inverted index file has been backed up,
  418. #    the temporary inverted index file is moved, using the "mv"
  419. #    command, as the new inverted index file. If this operation is
  420. #    successful, then step 5) is executed, if not, the script
  421. #    aborts.
  422. #
  423. #    5) After the temporary inverted index file has been moved, the
  424. #    original inverted index file backup is removed. If this
  425. #    operation is successful, the script exits normally, if not,
  426. #    the script aborts.
  427. #
  428. # Note that the vulnerability is in steps 3), 4) and 5). If step 3)
  429. # fails, then the the original inverted index is still intact, and
  430. # there is no backup (or need for one.) If step 4) fails, then there is
  431. # a backup and it will restored in step 1). If step 5) fails, then
  432. # there is a backup and it will, also, be restored in step 1),
  433. # (inadvertently destroying the new inverted index file.) Note,
  434. # additionally, that steps 3), 4), and 5) are "low risk" operations,
  435. # (two "mv" and one "rm" operation,) and executed sequentially, with no
  436. # intervening program steps.
  437. X
  438. # Function to find the programs used in this script. The arguments are
  439. # the choice of paths to a program, in precedence of your first choice,
  440. # second choice, and so on.
  441. #
  442. # Note that this function is not efficient. During installation, the
  443. # paths should be hard coded, and this function removed.
  444. X
  445. find_program()
  446. {
  447. X    # If no arguments, exit.
  448. X
  449. X    if [ "$#" -eq "0" ]; then
  450. X    ${ECHO} "No program name specified, aborting." 1>&2
  451. X    exit 1
  452. X    fi
  453. X
  454. X    # Save the first argument's basename for error reporting.
  455. X
  456. X    program_base=`basename $1`
  457. X
  458. X    # For each argument, test if the file name exists, and is
  459. X    # executable.
  460. X
  461. X    while [ "$#" -ne "0" ]
  462. X    do
  463. X    if [ -x "$1" ]; then
  464. X        ${ECHO} "$1"
  465. X        return
  466. X    fi
  467. X    shift
  468. X    done
  469. X
  470. X    # None of the file name arguments were found, exit with the error.
  471. X
  472. X    ${ECHO} "Program not found, $program_base, aborting." 1>&2
  473. X    exit 1
  474. }
  475. X
  476. # Assume an echo is in the path for find_program().
  477. X
  478. ECHO=echo
  479. X
  480. CAT=`find_program /usr/bin/cat`
  481. CP=`find_program /usr/bin/cp`
  482. X
  483. # For SunOS 4.1.x, use the SysV version of echo, in /usr/5bin/echo, all
  484. # others use /usr/bin/echo.
  485. X
  486. ECHO=`find_program /usr/5bin/echo /usr/bin/echo`
  487. EGREP=`find_program /usr/bin/egrep`
  488. JOIN=`find_program /usr/bin/join`
  489. X
  490. # For SysV Rel. 4.x, the look program resides in /usr/ucb/look, all
  491. # others use /usr/bin/look.
  492. X
  493. LOOK=`find_program /usr/bin/look /usr/ucb/look`
  494. MV=`find_program /usr/bin/mv`
  495. RM=`find_program /usr/bin/rm`
  496. SED=`find_program /usr/bin/sed`
  497. SORT=`find_program /usr/bin/sort`
  498. X
  499. # For the DEC ALPHA 3000, the sync program resides in /usr/sbin/sync,
  500. # all others use /usr/bin/sync.
  501. X
  502. SYNC=`find_program /usr/bin/sync /usr/sbin/sync`
  503. X
  504. # For SunOS 4.1.x, use the SysV version of tr, in /usr/5bin/tr, all
  505. # others use /usr/bin/tr.
  506. X
  507. TR=`find_program /usr/5bin/tr /usr/bin/tr`
  508. UNIQ=`find_program /usr/bin/uniq`
  509. X
  510. # Default inverted index file name.
  511. X
  512. DB_NAME="qt.index"
  513. X
  514. # Default temporary inverted index file base name.
  515. X
  516. TMP_NAME="qt.index"
  517. X
  518. # If the environmental variable, TMPDIR, exists, then that directory
  519. # will be used for all temporary files, if not, then /tmp will be used.
  520. # The temporary file names in TMPDIR are always the basename of the
  521. # database, concatenated with a '-', a unique character that identifies
  522. # the temporary file to this script, a '.', and this script's pid.
  523. X
  524. # Temporary file directory name.
  525. X
  526. TEMP_DIR="${TMPDIR:-/tmp}"
  527. X
  528. # Temporary inverted index file name. There are two alternatives here.
  529. # The database is updated by backing up the current database (with the
  530. # ${MV} command) to a different name in its current directory. Then the
  531. # new database is moved (again with the ${MV} command) from its
  532. # temporary name to the database name. In some systems, if the /tmp
  533. # directory is on a different disk partition, ${MV} will have to copy
  534. # the data across the file systems to perform the move. During this
  535. # time, the database is vulnerable to power outages, etc. If the
  536. # temporary database is in the same directory as the current database,
  537. # the update will not involve any data transfer on the disk (ie., only
  538. # a name change.) On one hand, constructing the temporary database in
  539. # its home directory lowers the risk of corruption if the machine goes
  540. # down, but on the other hand, it could leave the temporary file-which
  541. # can be large-when the machine comes up. (Note that the /tmp directory
  542. # is purged during the boot process.) The two options are:
  543. X
  544. # TMP_DB="${TEMP_DIR}/${TMP_NAME}-1.$$"
  545. TMP_DB="${DB_NAME}.NEW"
  546. X
  547. # List of all temporary files:
  548. #
  549. #    "${TEMP_DIR}/${TMP_NAME}-1.$$", is the temporary file name of
  550. #    the new inverted index file. (Either in read or write modes.)
  551. #
  552. #    "${TEMP_DIR}/${TMP_NAME}-2.$$", is the temporary file name
  553. #    where anything that is to be added to the inverted index file
  554. #    is temporarily held.  (Either in read or write modes.)
  555. #
  556. #    "${TEMP_DIR}/${TMP_NAME}-3.$$", is the temporary file name
  557. #    where anything that is to be added to the inverted index file
  558. #    is temporarily held.  (In read mode only.)
  559. #
  560. #    "${TEMP_DIR}/${TMP_NAME}-E.$$", is a temporary file name that
  561. #    contains information about error conditions. If it does not
  562. #    exist, or exists and is zero length, then no error occured.
  563. X
  564. TEMP_FILES="${TEMP_DIR}/${TMP_NAME}-1.$$ ${TEMP_DIR}/${TMP_NAME}-2.$$ ${TEMP_DIR}/${TMP_NAME}-3.$$ ${TEMP_DIR}/${TMP_NAME}-E.$$ ${TMP_DB}"
  565. X
  566. # Default lexical analyzer.
  567. X
  568. LEXICO=4
  569. X
  570. # RELEVANCE_ARGUMENTS, a list of the words being queried for in
  571. # read_index().
  572. X
  573. RELEVANCE_ARGUMENTS=""
  574. X
  575. # RELEVANCE_ATTRIBUTE, 1 = include "${RELEVANCE_ARGUMENTS}" as the
  576. # first record ouput from read_index(), 0 = do not output
  577. # "${RELEVANCE_ARGUMENTS}"
  578. X
  579. RELEVANCE_ATTRIBUTE=0
  580. X
  581. #  Mode of operations:
  582. #
  583. #    READ_MODE, read only mode = 1.
  584. #
  585. #    WRITE_MODE, write mode = 2.
  586. #
  587. #    DELETE_WORDS, delete words mode = 3.
  588. #
  589. #    DELETE_FILES, delete files mode = 4.
  590. #
  591. #    RELEVANCE_COUNT, relevance count mode = 5.
  592. #
  593. #    RELEVANCE_PROXIMITY, relevance proximity mode = 6.
  594. X
  595. READ_MODE=1
  596. WRITE_MODE=2
  597. DELETE_WORDS=3
  598. DELETE_FILES=4
  599. RELEVANCE_COUNT=5
  600. RELEVANCE_PROXIMITY=6
  601. X
  602. # Default mode of operation.
  603. X
  604. OP_MODE="${READ_MODE}"
  605. X
  606. # Default termination character used by ${LOOK} program. Setting this
  607. # to "${TAB}" will allow "exact key" searches for the words in the
  608. # inverted index file. The default, which is null, is to allow "partial
  609. # key" searches.
  610. X
  611. END=
  612. X
  613. # Tab character, used to specify the field delimiter for the ${JOIN}
  614. # program.  This is chosen as a character that will never be in the
  615. # inverted index file, since all white space is to be parsed away. Note
  616. # that this file should never be detab'ed.
  617. X
  618. TAB='    '
  619. X
  620. # The help function. Prints to stdout, so that it can be redirected to
  621. # a file. The function requires no arguments.
  622. X
  623. help()
  624. {
  625. X    ${ECHO} "This program will create and query inverted index files that index"
  626. X    ${ECHO} "the words in text files. These indices are useful in information"
  627. X    ${ECHO} "retrieval systems. The inverted index files are, typically, about the"
  628. X    ${ECHO} "same size of the text files, and do not require the text files to be"
  629. X    ${ECHO} "present for query operations. The query functions, typically, consist"
  630. X    ${ECHO} "of boolean operations on word searches. The output of the query is,"
  631. X    ${ECHO} "typically, a list of the file names that contain the queried word(s)."
  632. X    ${ECHO} ""
  633. X    ${ECHO} "The read synopsis is:"
  634. X    ${ECHO} ""
  635. X    ${ECHO} "    qt [-e] [-r | -rc | -rp] [-f index_name] word1 [op1] word2 [op2] ..."
  636. X    ${ECHO} ""
  637. X    ${ECHO} "where word1-word2 ... are the words to be queried in the inverted"
  638. X    ${ECHO} "index, and op1-op2 ... are the operations to be performed on the set"
  639. X    ${ECHO} "of file names that contain these words. The word/operation arguments"
  640. X    ${ECHO} "consist of pairs of search words, and boolean operators, with a left"
  641. X    ${ECHO} "to right operational precedence."
  642. X    ${ECHO} ""
  643. X    ${ECHO} "Thus if A, B, and C are words, then the query:"
  644. X    ${ECHO} ""
  645. X    ${ECHO} "    A and B or C not D"
  646. X    ${ECHO} ""
  647. X    ${ECHO} "would specify that all file names containing word A should be found,"
  648. X    ${ECHO} "then all the file names containing word B should be found, and only"
  649. X    ${ECHO} "those file names that contain words A and B should be added to those"
  650. X    ${ECHO} "file names containing word C, and then if these file names do not"
  651. X    ${ECHO} "contain word D, they are output."
  652. X    ${ECHO} ""
  653. X    ${ECHO} "Logical \"or'ing\" is implicit, thus:"
  654. X    ${ECHO} ""
  655. X    ${ECHO} "    A B C"
  656. X    ${ECHO} ""
  657. X    ${ECHO} "is identical to:"
  658. X    ${ECHO} ""
  659. X    ${ECHO} "    A or B or C"
  660. X    ${ECHO} ""
  661. X    ${ECHO} "Obviously, the keywords, \"and,\" \"not,\" and \"or,\" may not be queried"
  662. X    ${ECHO} "for, when using the implicit \"or\" query constructs."
  663. X    ${ECHO} ""
  664. X    ${ECHO} "If the \"-e\" option is specified, then \"exact match\" queries will be"
  665. X    ${ECHO} "performed, otherwise, a \"partial key\" type of search will be"
  666. X    ${ECHO} "performed, which is the default. It is recommended that the \"-e\""
  667. X    ${ECHO} "option be used if the query involves any boolean operations."
  668. X    ${ECHO} ""
  669. X    ${ECHO} "If the \"-r\" option is specified, then the words being queried for"
  670. X    ${ECHO} "will be output before the list file names that contain the queried"
  671. X    ${ECHO} "words.  This output format is compatible with egrep(1), and is useful"
  672. X    ${ECHO} "in doing \"relevance feedback\" searches."
  673. X    ${ECHO} ""
  674. X    ${ECHO} "If the \"-rc\" option is specified, then the count of records in a file"
  675. X    ${ECHO} "that contain match(s) will be output with the file name containing"
  676. X    ${ECHO} "the matches.  This provides the system with a remedial \"relevance"
  677. X    ${ECHO} "feedback\" capability. The original text files that were used to"
  678. X    ${ECHO} "construct the inverted index file must be available in the system to"
  679. X    ${ECHO} "use this option."
  680. X    ${ECHO} ""
  681. X    ${ECHO} "If the \"-rp\" option is specified, then the records in a file that"
  682. X    ${ECHO} "contain match(s) will be output after the file name containing the"
  683. X    ${ECHO} "matches. This output format provides the system with a remedial"
  684. X    ${ECHO} "\"permuted index\" type of \"proximity retrieval.\" The original text"
  685. X    ${ECHO} "files that were used to construct the inverted index must be"
  686. X    ${ECHO} "available in the system to use this option."
  687. X    ${ECHO} ""
  688. X    ${ECHO} "The write synopsis is:"
  689. X    ${ECHO} ""
  690. X    ${ECHO} "    qt -w [-f index_name] [-1 | ... | -8] file1 file2 ..."
  691. X    ${ECHO} ""
  692. X    ${ECHO} "where file1 file2 ... are the file names that contain words that are"
  693. X    ${ECHO} "to be added to the inverted index, or:"
  694. X    ${ECHO} ""
  695. X    ${ECHO} "    qt -w [-f index_name] [-1 | ... | -8] < file_list"
  696. X    ${ECHO} ""
  697. X    ${ECHO} "where file_list is the name of a file that contains a list of file"
  698. X    ${ECHO} "names, one file name per record, that contain words that are to be"
  699. X    ${ECHO} "added to the inverted index."
  700. X    ${ECHO} ""
  701. X    ${ECHO} "It is recommended that file names contain the absolute path to the"
  702. X    ${ECHO} "system's root directory."
  703. X    ${ECHO} ""
  704. X    ${ECHO} "If the inverted index file does not exist, then it will be created,"
  705. X    ${ECHO} "and contain an index to all of the words in the input files. If the"
  706. X    ${ECHO} "inverted index file exists, then the indices of all words in the"
  707. X    ${ECHO} "input files will be added, incrementally. Instances of words and"
  708. X    ${ECHO} "filename pairs will be unique in the inverted index."
  709. X    ${ECHO} ""
  710. X    ${ECHO} "The \"-w\" option, specifies that write operations will be performed,"
  711. X    ${ECHO} "and is a mandatory option, to be used if and only if write operations"
  712. X    ${ECHO} "are desired."
  713. X    ${ECHO} ""
  714. X    ${ECHO} "The \"-f index_name,\" optionally, specifies the inverted index file's"
  715. X    ${ECHO} "name. If the \"-f\" option is not specified, the inverted index file"
  716. X    ${ECHO} "name will default to \"qt.index.\""
  717. X    ${ECHO} ""
  718. X    ${ECHO} "The lexical analyzer level is specified by, \"-1\", through \"-8\". If"
  719. X    ${ECHO} "none are specified, the default, \"-4\", will be used. The lexical"
  720. X    ${ECHO} "analyzers with larger numbers are, generally, more sophisticated"
  721. X    ${ECHO} "about the words that are placed in the inverted index. The lexical"
  722. X    ${ECHO} "analyzers available are:"
  723. X    ${ECHO} ""
  724. X    ${ECHO} "    1) Parses words and numbers. All other characters are omitted."
  725. X    ${ECHO} "    Capitalization is preserved. Probably the best choice if"
  726. X    ${ECHO} "    non-word searches are important."
  727. X    ${ECHO} ""
  728. X    ${ECHO} "    2) Like 1) above, but the '_' character is recognized. This"
  729. X    ${ECHO} "    parser seems to work well with \"C\" program source files."
  730. X    ${ECHO} ""
  731. X    ${ECHO} "    3) Like 1) above, but, only words of more than two characters"
  732. X    ${ECHO} "    are placed in the inverted index file. If capitalization is"
  733. X    ${ECHO} "    considered important in the search criteria, then this seems"
  734. X    ${ECHO} "    to be the best choice."
  735. X    ${ECHO} ""
  736. X    ${ECHO} "    4) Like 1) above, but, capitalization is ignored. For general"
  737. X    ${ECHO} "    text where all words and numbers are considered significant,"
  738. X    ${ECHO} "    this seems to be the best choice. Also seems a good choice for"
  739. X    ${ECHO} "    \"catman\" pages. Queries should be in lowercase."
  740. X    ${ECHO} ""
  741. X    ${ECHO} "    5) Like 3) above, but capitalization is ignored. For general"
  742. X    ${ECHO} "    text, this seems to be the best choice. Queries should be in"
  743. X    ${ECHO} "    lowercase."
  744. X    ${ECHO} ""
  745. X    ${ECHO} "    6) Like 4) above, but words containing only numbers are"
  746. X    ${ECHO} "    omitted from the inverted index file. For text containing only"
  747. X    ${ECHO} "    words, this seems to be the best choice. Queries should be in"
  748. X    ${ECHO} "    lowercase."
  749. X    ${ECHO} ""
  750. X    ${ECHO} "    7) Like 4) above, but does not include Unix mail headers in"
  751. X    ${ECHO} "    the inverted index file. Each email should be in a separate"
  752. X    ${ECHO} "    file, as opposed to concatenated into folders. This seems to"
  753. X    ${ECHO} "    be the best choice for Unix mail files, if the header"
  754. X    ${ECHO} "    information is not desirable."
  755. X    ${ECHO} ""
  756. X    ${ECHO} "    8) Like 4) above, but deletes TeX and/or LaTeX commands from"
  757. X    ${ECHO} "    the inverted index file. This seems to be the best choice for"
  758. X    ${ECHO} "    TeX and LaTeX documents."
  759. X    ${ECHO} ""
  760. X    ${ECHO} "The more sophisticated the parser, the smaller the size of the"
  761. X    ${ECHO} "inverted index file. Multiple runs can be made, using the different"
  762. X    ${ECHO} "parsers, to store words in the inverted index. For example, using"
  763. X    ${ECHO} "parsers 3) and 4) would place both the capitalized and"
  764. X    ${ECHO} "non-capitalized words in the index.  This would not duplicate any"
  765. X    ${ECHO} "words already in the index-only add the words that were different."
  766. X    ${ECHO} ""
  767. X    ${ECHO} "The remove words synopsis is:"
  768. X    ${ECHO} ""
  769. X    ${ECHO} "    qt -w -dw [-f index_name] word1 word2 ..."
  770. X    ${ECHO} ""
  771. X    ${ECHO} "where word1 word2 ... are the words that are to be deleted from the"
  772. X    ${ECHO} "inverted index file, and may be a regular expressions-no '^' or '$'"
  773. X    ${ECHO} "characters should be used, unless they are escaped. The \"-w\" option"
  774. X    ${ECHO} "is mandatory."
  775. X    ${ECHO} ""
  776. X    ${ECHO} "The remove files synopsis is:"
  777. X    ${ECHO} ""
  778. X    ${ECHO} "    qt -w -df [-f index_name] file1 file2 ..."
  779. X    ${ECHO} ""
  780. X    ${ECHO} "where file1 file2 ... are the file names that are to be deleted from"
  781. X    ${ECHO} "inverted index. The file names to be deleted from the inverted index"
  782. X    ${ECHO} "file may be regular expressions-no '^' or '$' characters should be"
  783. X    ${ECHO} "used, unless they are escaped. The \"-w\" option is mandatory."
  784. X    ${ECHO} ""
  785. X    ${ECHO} "The version synopsis is:"
  786. X    ${ECHO} ""
  787. X    ${ECHO} "    qt -v"
  788. X    ${ECHO} ""
  789. X    ${ECHO} "which will print the version number of qt."
  790. X    ${ECHO} ""
  791. X    ${ECHO} "The help synopsis is:"
  792. X    ${ECHO} ""
  793. X    ${ECHO} "    qt -h"
  794. X    ${ECHO} ""
  795. X    ${ECHO} "which will list a synopsis of the command semantics."
  796. X    ${ECHO} ""
  797. X    ${ECHO} "A common example of writing an inverted index file would be:"
  798. X    ${ECHO} ""
  799. X    ${ECHO} "    find /dir1/dir2 -type f -print | qt -w"
  800. X    ${ECHO} ""
  801. X    ${ECHO} "which would recursively descend through the directory hierarchy, and"
  802. X    ${ECHO} "create an inverted index of all of the words in all of the files in"
  803. X    ${ECHO} "all of the directories, starting with /dir1/dir2."
  804. X    ${ECHO} ""
  805. X    ${ECHO} "A common example of retrieving information from an inverted index"
  806. X    ${ECHO} "file would be:"
  807. X    ${ECHO} ""
  808. X    ${ECHO} "    more +/word \`qt word\`"
  809. X    ${ECHO} ""
  810. X    ${ECHO} "where the \"more\" program would page through the documents that"
  811. X    ${ECHO} "contain \"word,\" advancing to the next instance every time the 'n' key"
  812. X    ${ECHO} "is depressed."
  813. X    ${ECHO} ""
  814. X    ${ECHO} "A common example of relevance determination in retrieving information"
  815. X    ${ECHO} "from an inverted index file would be:"
  816. X    ${ECHO} ""
  817. X    ${ECHO} "    egrep -ic \`qt -r word\` | sort -n -r -t: +1"
  818. X    ${ECHO} ""
  819. X    ${ECHO} "which would print the file(s) that contain \"word,\" with the count of"
  820. X    ${ECHO} "the instances of records that contain \"word\" in each of the file(s)."
  821. }
  822. X
  823. # Query the inverted index file for word(s). If "${END}" is a "${TAB}",
  824. # then only exact matches will be found. If "${END}" is null, then
  825. # "partial key" types of operations will be performed. The command line
  826. # arguments consist of pairs of search words, and boolean operators,
  827. # with a left to right operational precedence.
  828. #
  829. # Thus if A, B, and C are words, then the query:
  830. #
  831. #    A and B or C not D
  832. #
  833. # would specify that all file names containing word A should be found,
  834. # then all the file names containing word B should be found, and only
  835. # those file names that contain words A and B should be added to those
  836. # file names containing word C, and then if these file names do not
  837. # contain word D, they are output.
  838. #
  839. # Logical "or'ing" is implicit, thus:
  840. #
  841. #    A B C
  842. #
  843. # is identical to:
  844. #
  845. #    A or B or C
  846. #
  847. # The boolean operators supported are:
  848. #
  849. #    and, which is implemented with the ${JOIN} program to perform a
  850. #    "natural join" of two files, each file containing a list of the
  851. #    file names which contain specific word(s).
  852. #
  853. #    not, which is implemented with the ${JOIN} program to perform an
  854. #    "natural join" operation, the output of which is combined with
  855. #    the first file, using the ${SORT} -m program, and piped to the
  856. #    ${UNIQ} -u program so that only those file names that are unique
  857. #    to the first file are output.
  858. #
  859. #    or, which is implemented by ${SORT} -mu to concatenate the two
  860. #    files together, each record being unique.
  861. #
  862. # The ${LOOK} program is used to perform a binary search on the inverted
  863. # index file, (which is made up of ASCII records, each record containing
  864. # a word in a file, and the file name, separated by a single "${TAB}".)
  865. # The inverted index file is sorted in the ASCII collation sequence of
  866. # the words. The output of the ${LOOK} program is also sorted in ASCII
  867. # collation sequence. The words are striped from the records output from
  868. # the ${LOOK} program with the ${SED} 's/.* //' program.
  869. #
  870. # The ${JOIN} program uses the -tc option, where c is a character that
  871. # can never be in the inverted index.
  872. #
  873. # Note, the use of "-e" for exact word match operations is recommended
  874. # when doing boolean searches.
  875. #
  876. # The variable, ${RELEVANCE_ARGUMENTS}, contains a running list of the
  877. # search words, separated by a pipe symbol, '|'. This argument is is
  878. # useful for piping to ${EGREP} for further refined searches.
  879. #
  880. # The rules of concatenation are:
  881. #
  882. #    1) initial operator argument, concatenate word only.
  883. #
  884. #    2) "or" operator argument, concatenate '|' and word to
  885. #       beginning.
  886. #
  887. #    3) "not" operator argument, do not concatenate.
  888. #
  889. #    4) "and" operator argument, replace concatenated string with
  890. #       word.
  891. #
  892. # This variable, at the conclusion of read_index(), will contain a
  893. # regular expression that is compatible with the first argument of
  894. # ${EGREP}. If the output of qt is piped to ${EGREP} with the file names
  895. # as additional arguments, then the words found in the records of the
  896. # files will approximately equal what the ${LOOK} program found in the
  897. # inverted index file.
  898. #
  899. # The arguments are the list of word/operators.
  900. X
  901. read_index ()
  902. {
  903. X    # If no arguments, return.
  904. X
  905. X    if [ "$#" -eq "0" ]; then
  906. X    return
  907. X    fi
  908. X
  909. X    # If the file does not exist, or is unreadable, exit.
  910. X
  911. X    if [ ! -f "${DB_NAME}" -o ! -r "${DB_NAME}" ]; then
  912. X    ${ECHO} "The inverted index file does not exist, or is unreadable, aborting." 1>&2
  913. X    exit 1
  914. X    fi
  915. X
  916. X    # For each query argument:
  917. X
  918. X    while [ "$#" -ne "0" ]
  919. X    do
  920. X    case "$1" in
  921. X        and)
  922. X
  923. X        # The query operator was an "and", shift over it.
  924. X
  925. X        shift
  926. X        if [ -f "${TEMP_DIR}/${TMP_NAME}-1.$$" ]; then
  927. X
  928. X            # A partial file list exists, clear any
  929. X            # ${RELEVANCE_ARGUMENTS} and start a new one.
  930. X
  931. X            RELEVANCE_ARGUMENTS="$1"
  932. X
  933. X            # Find the list of word/file name pairs, striping
  934. X            # the words, and make this list unique.
  935. X
  936. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  937. X
  938. X            # ${JOIN} this list with the existing partial file
  939. X            # list.
  940. X
  941. X            ${JOIN} "-t${TAB}" "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TEMP_DIR}/${TMP_NAME}-2.$$" > "${TEMP_DIR}/${TMP_NAME}-3.$$"
  942. X
  943. X            # This becomes the new partial file list.
  944. X
  945. X            ${MV} -f "${TEMP_DIR}/${TMP_NAME}-3.$$" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  946. X        else
  947. X
  948. X            # A partial file list does not exist, start one,
  949. X            # clear any ${RELEVANCE_ARGUMENTS} , and start a new
  950. X            # one.
  951. X
  952. X            RELEVANCE_ARGUMENTS="$1"
  953. X
  954. X            # Find the list of word/file name pairs, striping
  955. X            # the words, and make this list unique.
  956. X
  957. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-1.$$"
  958. X        fi; shift;;
  959. X        not)
  960. X
  961. X        # The query operator was an "not", shift over it.
  962. X
  963. X        shift
  964. X        if [ -f "${TEMP_DIR}/${TMP_NAME}-1.$$" ]; then
  965. X
  966. X            # A partial file list exists, don't add this to
  967. X            # ${RELEVANCE_ARGUMENTS}, find the list of word/file
  968. X            # name pairs, striping the words, and make this list
  969. X            # unique.
  970. X
  971. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  972. X
  973. X            # ${JOIN} this list with the existing partial file
  974. X            # list.
  975. X
  976. X            ${JOIN} "-t${TAB}" "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TEMP_DIR}/${TMP_NAME}-2.$$" > "${TEMP_DIR}/${TMP_NAME}-3.$$"
  977. X
  978. X            # ${SORT} -m this list with the existing partial
  979. X            # file list.
  980. X
  981. X            ${SORT} -m "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TEMP_DIR}/${TMP_NAME}-3.$$" | ${UNIQ} -u > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  982. X
  983. X            # This becomes the new partial file list.
  984. X
  985. X            ${MV} -f "${TEMP_DIR}/${TMP_NAME}-2.$$" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  986. X        else
  987. X
  988. X            # A partial file list does not exist-start one,
  989. X            # don't add this to # ${RELEVANCE_ARGUMENTS}, find
  990. X            # the list of word/file name pairs, striping the
  991. X            # words, and make this list unique.
  992. X
  993. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-1.$$"
  994. X        fi; shift;;
  995. X        or)
  996. X
  997. X        # The query operator was an "or", shift over it.
  998. X
  999. X        shift
  1000. X        if [ -f "${TEMP_DIR}/${TMP_NAME}-1.$$" ]; then
  1001. X
  1002. X            # A partial file list exists, add this with a
  1003. X            # leading '|, to the ${RELEVANCE_ARGUMENTS}.
  1004. X
  1005. X            RELEVANCE_ARGUMENTS="$1|${RELEVANCE_ARGUMENTS}"
  1006. X
  1007. X            # Find the list of word/file name pairs, striping
  1008. X            # the words, and make this list unique.
  1009. X
  1010. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  1011. X
  1012. X            # ${SORT} -mu this list with the existing partial
  1013. X            # file list.
  1014. X
  1015. X            ${SORT} -mu "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TEMP_DIR}/${TMP_NAME}-2.$$" > "${TEMP_DIR}/${TMP_NAME}-3.$$"
  1016. X
  1017. X            # This becomes the new partial file list.
  1018. X
  1019. X            ${MV} -f "${TEMP_DIR}/${TMP_NAME}-3.$$" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1020. X        else
  1021. X
  1022. X            # A partial file list does not exist, add this with a
  1023. X            # leading '|, to the ${RELEVANCE_ARGUMENTS}.
  1024. X
  1025. X            RELEVANCE_ARGUMENTS="$1"
  1026. X
  1027. X            # the list of word/file name pairs, striping the
  1028. X            # words, and make this list unique.
  1029. X
  1030. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1031. X        fi; shift;;
  1032. X        *)
  1033. X
  1034. X        # The query operator was not an "and", "not", or "or".
  1035. X
  1036. X        if [ -f "${TEMP_DIR}/${TMP_NAME}-1.$$" ]; then
  1037. X
  1038. X            # A partial file list exists, add this with a
  1039. X            # leading '|, to the ${RELEVANCE_ARGUMENTS}.
  1040. X
  1041. X            RELEVANCE_ARGUMENTS="$1|${RELEVANCE_ARGUMENTS}"
  1042. X
  1043. X            # Find the list of word/file name pairs, striping
  1044. X            # the words, and make this list unique.
  1045. X
  1046. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  1047. X
  1048. X            # ${SORT} -mu this list with the existing partial
  1049. X            # file list.
  1050. X
  1051. X            ${SORT} -mu "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TEMP_DIR}/${TMP_NAME}-2.$$" > "${TEMP_DIR}/${TMP_NAME}-3.$$"
  1052. X
  1053. X            # This becomes the new partial file list.
  1054. X
  1055. X            ${MV} -f "${TEMP_DIR}/${TMP_NAME}-3.$$" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1056. X        else
  1057. X
  1058. X            # A partial file list does not exist, add this with a
  1059. X            # leading '|, to the ${RELEVANCE_ARGUMENTS}.
  1060. X
  1061. X            RELEVANCE_ARGUMENTS="$1"
  1062. X
  1063. X            # Find the list of word/file name pairs, striping
  1064. X            # the words, and make this list unique.
  1065. X
  1066. X            ${LOOK} "$1${END}" "${DB_NAME}" | ${SED} "s/.*${TAB}//" | ${UNIQ} > "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1067. X        fi; shift;;
  1068. X    esac
  1069. X    done
  1070. X
  1071. X    # If a request for egrep(1) compatable regular expression search
  1072. X    # word to be output prior to file names option, the output it.
  1073. X
  1074. X    if [ ! "${RELEVANCE_ATTRIBUTE}" -eq "0" ]; then
  1075. X    ${ECHO} "${RELEVANCE_ARGUMENTS}"
  1076. X    fi
  1077. X
  1078. X    # Make shure that each file name in the list is unique.
  1079. X
  1080. X    ${SORT} -u "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1081. X
  1082. X    # Remove any temporary files.
  1083. X
  1084. X    ${RM} -f "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TEMP_DIR}/${TMP_NAME}-2.$$"
  1085. }
  1086. X
  1087. # Index the words in the files. Sort the index, lexographically, on the
  1088. # words, making sure that word file name pairs are unique. Write the
  1089. # output to a temporary file. If any errors, write the errors to
  1090. # "${TEMP_DIR}/${TMP_NAME}-E.$$". Test this file for zero length before
  1091. # proceeding.
  1092. #
  1093. # The required arguments are the list of file names, or none, in which
  1094. # case the file names will be read from the stdin.
  1095. X
  1096. write_index()
  1097. {
  1098. X    if [ "$#" -eq "0" ]; then
  1099. X
  1100. X    # There are no arguments, read each file name from stdin.
  1101. X
  1102. X    while read file_name
  1103. X    do
  1104. X
  1105. X        # Parse the words from the file.
  1106. X
  1107. X        parse_word "${file_name}"
  1108. X    done
  1109. X    else
  1110. X
  1111. X    # There are arguments, for each one of them, read the file.
  1112. X
  1113. X    for file_name
  1114. X    do
  1115. X
  1116. X        # Parse the words from the file.
  1117. X
  1118. X        parse_word "${file_name}"
  1119. X    done
  1120. X    fi | { ${SORT} -T "${TEMP_DIR}" -u > "${TEMP_DIR}/${TMP_NAME}-2.$$"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$"
  1121. X
  1122. X    # If any errors indexing files, abort.
  1123. X
  1124. X    if [ -s "${TEMP_DIR}/${TMP_NAME}-E.$$" ]; then
  1125. X
  1126. X    # There was an error, abort.
  1127. X
  1128. X    exit 1
  1129. X    fi
  1130. X
  1131. X    # If an inverted index file already exists, merge it, uniquely, with the
  1132. X    # new temporary inverted index file, else, move the the temporary
  1133. X    # inverted index file to that name.
  1134. X
  1135. X    if [ -f "${DB_NAME}" ]; then
  1136. X
  1137. X    # An inverted index file exists, merge the new inverted index
  1138. X    # with it, using ${SORT} -mu.
  1139. X
  1140. X    ${SORT} -T "${TEMP_DIR}" -mu "${TEMP_DIR}/${TMP_NAME}-2.$$" "${DB_NAME}" > "${TMP_DB}"
  1141. X    if [ ! "$?" -eq "0" ]; then
  1142. X
  1143. X        # Couldn't merge the two files, abort.
  1144. X
  1145. X        exit 1
  1146. X    fi
  1147. X    else
  1148. X
  1149. X    # An inverted index file does not exist, ${MV} the new inverted
  1150. X    # index as this file.
  1151. X
  1152. X    ${MV} -f "${TEMP_DIR}/${TMP_NAME}-2.$$" "${TMP_DB}"
  1153. X    if [ ! "$?" -eq "0" ]; then
  1154. X
  1155. X        # Couldn't move the file, abort.
  1156. X
  1157. X        exit 1
  1158. X    fi
  1159. X    fi
  1160. X
  1161. X    # Updataing the temporary inverted index file has been completed.
  1162. X    # Update the inverted index file.
  1163. X
  1164. X    update_database "${DB_NAME}" "${TMP_DB}"
  1165. }
  1166. X
  1167. # Update the database function-update the database as described above.
  1168. #
  1169. #    1) Backup the original inverted index file is backed up, using
  1170. #       the "mv" command. If this operation successful, then step 2)
  1171. #       is executed, if not, the script aborts.
  1172. #
  1173. #    2) After the original inverted index file has been backed up,
  1174. #       the temporary inverted index file is moved, using the "mv"
  1175. #       command, as the new inverted index file. If this operation is
  1176. #       successful, then step 3) is executed, if not, the script
  1177. #       aborts.
  1178. #
  1179. #    3) After the temporary inverted index file has been moved, the
  1180. #       original inverted index file backup is removed. If this
  1181. #       operation is successful, the script exits normally, if not,
  1182. #       the script aborts.
  1183. #
  1184. # On any error, this function aborts, after trying to restore the
  1185. # original database. The arguments to this function are:
  1186. #
  1187. #    "$1" is the original database's name.
  1188. #
  1189. #    "$2" is the new database's name.
  1190. X
  1191. update_database()
  1192. {
  1193. X    # Ignore interrupts.
  1194. X
  1195. X    trap '' 0 1 2 3 15
  1196. X
  1197. X    # If the Inverted index database does not exist, or is unreadable or
  1198. X    # unwritable, exit. Note that the original index may not exist, yet.
  1199. X
  1200. X    if [ -b "$1" -o -c "$1" -o -d "$1" -o -p "$1" ]; then
  1201. X    ${ECHO} "The inverted index file does not exist, or is not writable or readable, aborting." 1>&2
  1202. X    ${RM} -f "${TEMP_FILES}"
  1203. X    exit 1
  1204. X    fi
  1205. X
  1206. X    # If the original inverted index exists, back it up, if that fails,
  1207. X    # attempt to restore it.
  1208. X
  1209. X    if [ -f "$1" ]; then
  1210. X
  1211. X    # Inverted index file exists.
  1212. X
  1213. X    if [ -w "$1" ]; then
  1214. X
  1215. X        # Inverted index file is writable, back it up.
  1216. X
  1217. X        ${MV} -f "$1" "$1.BAK"
  1218. X        if [ ! "$?" -eq "0" ]; then
  1219. X
  1220. X        # Backup was not successful, attempt to restore.
  1221. X
  1222. X        if [ -f "$1.BAK" ]; then
  1223. X
  1224. X            # Backup file exists, print the message that an
  1225. X            # attemp to restore is underway.
  1226. X
  1227. X            ${ECHO} "Error backing up original index file, attempting to restore." 1>&2
  1228. X
  1229. X            # Remove any temporary files to attempt to open up
  1230. X            # disk space.
  1231. X
  1232. X            ${RM} -f "${TEMP_FILES}"
  1233. X
  1234. X            # Restore the backup inverted index file as the
  1235. X            # original inverted index file.
  1236. X
  1237. X            ${MV} -f "$1.BAK" "$1"
  1238. X            if [ "$?" -eq "0" ]; then
  1239. X
  1240. X            # Backup succeeded, notify the user.
  1241. X
  1242. X            ${ECHO} "Restoration of original index file succeeded, aborting." 1>&2
  1243. X            else
  1244. X
  1245. X            # Backup failed, notify the user.
  1246. X
  1247. X            ${ECHO} "Restoration of original index file failed, aborting." 1>&2
  1248. X            fi
  1249. X            ${SYNC}
  1250. X            ${SYNC}
  1251. X        else
  1252. X
  1253. X            # The original inverted index file was never
  1254. X            # backed up, test for the original.
  1255. X
  1256. X            if [ -f "$1" ]; then
  1257. X
  1258. X            # Original inverted index file exists, notify
  1259. X            # the user.
  1260. X
  1261. X            ${ECHO} "Restoration of original index file succeeded, aborting." 1>&2
  1262. X            else
  1263. X
  1264. X            # Something is terribly wrong-the original
  1265. X            # inverted index and its backp are both gone.
  1266. X
  1267. X            ${ECHO} "Restoration of original index file failed, aborting." 1>&2
  1268. X            fi
  1269. X
  1270. X            # Remove all temporary files.
  1271. X
  1272. X            ${RM} -f "${TEMP_FILES}"
  1273. X        fi
  1274. X        exit 1
  1275. X        fi
  1276. X    else
  1277. X
  1278. X        # Inverted index file is not writable, abort.
  1279. X
  1280. X        ${ECHO} "The index file is a not writeable, aborting." 1>&2
  1281. X        ${RM} -f "${TEMP_FILES}"
  1282. X        exit 1
  1283. X    fi
  1284. X    fi
  1285. X
  1286. X    # The original inverted index file, if it existed, is now backed up,
  1287. X    # move the temporary inverted index file as the new inverted index
  1288. X    # file.
  1289. X
  1290. X    ${MV} -f "$2" "$1"
  1291. X
  1292. X    # Erase the original inverted index backup file, if it exists, if that
  1293. X    # fails, attempt to restore it.
  1294. X
  1295. X    if [ "$?" -eq "0" ]; then
  1296. X
  1297. X    # The new inverted index has been moved into place, erase the
  1298. X    # backup inverted index file, if it exists.
  1299. X
  1300. X    if [ -f "$1.BAK" ]; then
  1301. X
  1302. X        # The backup inverted index file exists, erase it.
  1303. X
  1304. X        ${RM} -f "$1.BAK"
  1305. X        if [ ! "$?" -eq "0" ]; then
  1306. X
  1307. X        # The removal of the backup inverted index file
  1308. X        # failed, notify the user.
  1309. X
  1310. X        ${ECHO} "Error removing original index backup, attempting to restore." 1>&2
  1311. X
  1312. X        # Remove any temporary files to attempt to open up
  1313. X        # disk space.
  1314. X
  1315. X        ${RM} -f "${TEMP_FILES}"
  1316. X
  1317. X        # Restore the backup inverted index file as the
  1318. X        # original inverted index file.
  1319. X
  1320. X        ${MV} "$1.BAK" "$1"
  1321. X        if [ "$?" -eq "0" ]; then
  1322. X
  1323. X            # Backup succeeded, notify the user.
  1324. X
  1325. X            ${ECHO} "Restoration of original index file succeeded, aborting." 1>&2
  1326. X        else
  1327. X
  1328. X            # Backup failed, notify the user.
  1329. X
  1330. X            ${ECHO} "Restoration of original index file failed, aborting." 1>&2
  1331. X        fi
  1332. X        ${SYNC}
  1333. X        ${SYNC}
  1334. X        exit 1
  1335. X        fi
  1336. X        ${SYNC}
  1337. X        ${SYNC}
  1338. X    fi
  1339. X    else
  1340. X
  1341. X    # The ${MV} of the new inverted index file failed, attempt to
  1342. X    # restore the backup file.
  1343. X
  1344. X    if [ -f "$1.BAK" ]; then
  1345. X
  1346. X        # Backup file exists, print the message that an attemp to
  1347. X        # restore is underway.
  1348. X
  1349. X        ${ECHO} "Error writing new index file, attempting to restore." 1>&2
  1350. X
  1351. X        # A backup file exists, Remove any temporary files to
  1352. X        # attempt to open up disk space.
  1353. X
  1354. X        ${RM} -f "${TEMP_FILES}"
  1355. X
  1356. X        # Restore the backup inverted index file as the original
  1357. X        # inverted index file.
  1358. X
  1359. X        ${MV} -f "$1.BAK" "$1"
  1360. X        if [ "$?" -eq "0" ]; then
  1361. X
  1362. X        # Backup succeeded, notify the user.
  1363. X
  1364. X        ${ECHO} "Restoration of original index file succeeded, aborting." 1>&2
  1365. X
  1366. X        else
  1367. X
  1368. X        # Backup failed, notify the user.
  1369. X
  1370. X        ${ECHO} "Restoration of original index file failed, aborting." 1>&2
  1371. X        fi
  1372. X        ${SYNC}
  1373. X        ${SYNC}
  1374. X    else
  1375. X
  1376. X        # The original inverted index file was never backed up,
  1377. X        # test for the original.
  1378. X
  1379. X        if [ -f "$1" ]; then
  1380. X
  1381. X        # Original inverted index file exists, notify the user.
  1382. X
  1383. X        ${ECHO} "Restoration of original index file succeeded, aborting." 1>&2
  1384. X        else
  1385. X
  1386. X        # Something is terribly wrong-the original inverted
  1387. X        # index and its backp are both gone.
  1388. X
  1389. X        ${ECHO} "Restoration of original index file failed, aborting." 1>&2
  1390. X        fi
  1391. X
  1392. X        # Remove all temporary files.
  1393. X
  1394. X        ${RM} -f "${TEMP_FILES}"
  1395. X    fi
  1396. X    exit 1
  1397. X    fi
  1398. }
  1399. X
  1400. # Lexical analysis function. The words in the file are parsed, one word
  1401. # per record, and concatenated with a "${TAB}", and the file's name.
  1402. # The records are output to the standard output. If an error occurs in
  1403. # any of the programs in the pipe, it is included in the file, named
  1404. # "${TEMP_DIR}/${TMP_NAME}-E.$$". If any errors occurred, exit.
  1405. #
  1406. # Note: This parser is not particularly elegant or fast. As a better
  1407. # solution, the program "stopper," from reference 1) above (Frakes, et
  1408. # al,) can be adapted to work quite well. This program is implemented as
  1409. # an FSM, and supports stop words.
  1410. #
  1411. # The required argument is the file name to be parsed.
  1412. X
  1413. parse_word()
  1414. {
  1415. X    # If no arguments, exit.
  1416. X
  1417. X    if [ "$#" -eq "0" ]; then
  1418. X    ${ECHO} "No file name specified, aborting." 1>&2
  1419. X    exit 1
  1420. X    fi
  1421. X
  1422. X    # If the file does not exist, or is unreadable, exit.
  1423. X
  1424. X    if [ ! -f "$1" -o ! -r "$1" ]; then
  1425. X    ${ECHO} "File name $1 does not exist or is not readable, aborting." 1>&2
  1426. X    exit 1
  1427. X    fi
  1428. X
  1429. X    # Select the lexical analyzer, and parse the words in the file.
  1430. X
  1431. X    case "$LEXICO" in
  1432. X    1) { ${TR} -cs '[a-z][A-Z][0-9]' '[\012*]' < "$1" | ${SED} "/^ *$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1433. X    2) { ${TR} -cs '[a-z][A-Z]_[0-9]' '[\012*]' < "$1" | ${SED} "/^ *$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1434. X    3) { ${TR} -cs '[a-z][A-Z][0-9]' '[\012*]' < "$1" | ${SED} "/^ *$/d;/^.$/d;/^..$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1435. X    4) { ${TR} '[A-Z]' '[a-z]' < "$1" | ${TR} -cs '[a-z][0-9]' '[\012*]' | ${SED} "/^ *$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1436. X    5) { ${TR} '[A-Z]' '[a-z]' < "$1" | ${TR} -cs '[a-z][0-9]' '[\012*]' | ${SED} "/^ *$/d;/^.$/d;/^..$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1437. X    6) { ${TR} '[A-Z]' '[a-z]' < "$1" | ${TR} -cs '[a-z][0-9]' '[\012*]' | ${EGREP} -v '^[0-9]*$' | ${SED} "/^ *$/d;/^.$/d;/^..$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1438. X    7) { ${SED} -e '1,/^ *$/d' < "$1" | ${TR} '[A-Z]' '[a-z]' | ${TR} -cs '[a-z][0-9]' '[\012*]' | ${SED} "/^ *$/d;/^ /d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1439. X    8) { ${TR} '[A-Z]' '[a-z]' < "$1" | ${TR} -cs '\\[a-z][0-9]' '[\012*]' | ${SED} "/^ *$/d;/^ /d;/\\\.*/d;s,$,${TAB}$1,"; } 2>> "${TEMP_DIR}/${TMP_NAME}-E.$$";;
  1440. X    *) ${ECHO} "Unknown lexical analyzer, aborting." 1>&2; exit 1;;
  1441. X    esac
  1442. X
  1443. X    # If any errors are contained in "${TEMP_DIR}/${TMP_NAME}-E.$$", exit.
  1444. X
  1445. X    if [ -s "${TEMP_DIR}/${TMP_NAME}-E.$$" ]; then
  1446. X    exit 1
  1447. X    fi
  1448. }
  1449. X
  1450. # Remove words from the inverted index function. Since the inverted
  1451. # index file structure is records constructed of word/filename pairs,
  1452. # separated by a "${TAB}", search for the word, adding a claret to
  1453. # signify beginning of record, and terminated with a "${TAB}". With
  1454. # multiple arguments, this script will create multiple indices. A copy of
  1455. # the original is made in the temporary directory, and all operations
  1456. # occur there. The final copy is ${MV}'ed to the index's directory and
  1457. # finally update_database() is called to install the new version.
  1458. #
  1459. # The required arguments are regular expressions. All words in the
  1460. # inverted index that match these expressions will be deleted.
  1461. X
  1462. remove_words()
  1463. {
  1464. X    # If no arguments, exit.
  1465. X
  1466. X    if [ "$#" -eq "0" ]; then
  1467. X    ${ECHO} "No words to remove specified, aborting." 1>&2
  1468. X    exit 1
  1469. X    fi
  1470. X
  1471. X    # If the Inverted index database does not exist, or is unreadable or
  1472. X    # unwritable, exit.
  1473. X
  1474. X    if [ ! -f "${DB_NAME}" -o ! -r "${DB_NAME}" -o ! -w "${DB_NAME}" ]; then
  1475. X    ${ECHO} "The inverted index file does not exist, or is not writable or readable, aborting." 1>&2
  1476. X    exit 1
  1477. X    fi
  1478. X
  1479. X    # Make a copy of the database in the ${TEMP_DIR}-operate on the
  1480. X    # copy, exit if this fails.
  1481. X
  1482. X    ${CP} "${DB_NAME}" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1483. X
  1484. X    if [ ! "$?" -eq "0" ]; then
  1485. X    ${ECHO} "Error removing words from index file, aborting." 1>&2
  1486. X    exit 1
  1487. X    fi
  1488. X
  1489. X    # For each argument, scan the copy of the database, using ${EGREP}
  1490. X    # -v, to remove any records that contain the specified word, making
  1491. X    # a new database in the "${TEMP_DIR}". ${MV} this file as the the
  1492. X    # new copy of the database. Exit on any failure.
  1493. X
  1494. X    while [ "$#" -gt "0" ]
  1495. X    do
  1496. X    ${EGREP} -v "^$1${TAB}" "${TEMP_DIR}/${TMP_NAME}-1.$$" > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  1497. X
  1498. X    # Only check for syntax errors. No matches is OK.
  1499. X
  1500. X    if [ "$?" -eq "2" ]; then
  1501. X        ${ECHO} "Error removing words from index file, aborting." 1>&2
  1502. X        exit 1
  1503. X    fi
  1504. X
  1505. X    ${MV} -f "${TEMP_DIR}/${TMP_NAME}-2.$$" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1506. X
  1507. X    if [ ! "$?" -eq "0" ]; then
  1508. X        ${ECHO} "Error removing words from index file, aborting." 1>&2
  1509. X        exit 1
  1510. X    fi
  1511. X
  1512. X    shift
  1513. X    done
  1514. X
  1515. X    # The specified words have been removed from the copy of the
  1516. X    # database. ${MV} the copy of the database to "${TMP_DB}". Exit on
  1517. X    # failure.
  1518. X
  1519. X    ${MV} "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TMP_DB}"
  1520. X
  1521. X    if [ ! "$?" -eq "0" ]; then
  1522. X    ${ECHO} "Error removing words from index file, aborting." 1>&2
  1523. X    exit 1
  1524. X    fi
  1525. X
  1526. X    # Flush the buffers and call update_database() to update the
  1527. X    # inverted index file.
  1528. X
  1529. X    ${SYNC}
  1530. X    ${SYNC}
  1531. X
  1532. X    update_database "${DB_NAME}" "${TMP_DB}"
  1533. }
  1534. X
  1535. # Remove files from the inverted index function. Since the inverted
  1536. # index file structure is records constructed of word/filename pairs,
  1537. # separated by a "${TAB}", search for the file name, adding a "${TAB}"
  1538. # to the beginning of the file name, and a dollar sign to the end of the
  1539. # file name to signify the end of record. With multiple arguments, this
  1540. # script will create multiple indices. A copy of the original is made in
  1541. # the temporary directory, and all operations occur there. The final
  1542. # copy is ${MV}'e to the index's directory and finally update_database()
  1543. # is called to install the new version.
  1544. #
  1545. # The required arguments are regular expressions. All file names in the
  1546. # inverted index that match these expressions will be deleted.
  1547. X
  1548. remove_files()
  1549. {
  1550. X    # If no arguments, exit.
  1551. X
  1552. X    if [ "$#" -eq "0" ]; then
  1553. X    ${ECHO} "No file names to remove specified, aborting." 1>&2
  1554. X    exit 1
  1555. X    fi
  1556. X
  1557. X    # If the Inverted index database does not exist, or is unreadable or
  1558. X    # unwritable, exit.
  1559. X
  1560. X    if [ ! -f "${DB_NAME}" -o ! -r "${DB_NAME}" -o ! -w "${DB_NAME}" ]; then
  1561. X    ${ECHO} "The inverted index file does not exist, or is not writable or readable, aborting." 1>&2
  1562. X    exit 1
  1563. X    fi
  1564. X
  1565. X    # Make a copy of the database in the ${TEMP_DIR}-operate on the
  1566. X    # copy, exit if this fails.
  1567. X
  1568. X    ${CP} "${DB_NAME}" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1569. X
  1570. X    if [ ! "$?" -eq "0" ]; then
  1571. X    ${ECHO} "Error removing files from index file, aborting." 1>&2
  1572. X    exit 1
  1573. X    fi
  1574. X
  1575. X    # For each argument, scan the copy of the database, using ${EGREP}
  1576. X    # -v, to remove any records that contain the specified file name,
  1577. X    # making a new database in the "${TEMP_DIR}". ${MV} this file as the
  1578. X    # the new copy of the database. Exit on any failure.
  1579. X
  1580. X    while [ "$#" -gt "0" ]
  1581. X    do
  1582. X    ${EGREP} -v "${TAB}$1\$" "${TEMP_DIR}/${TMP_NAME}-1.$$" > "${TEMP_DIR}/${TMP_NAME}-2.$$"
  1583. X
  1584. X    # Only check for syntax errors. No matches is OK.
  1585. X
  1586. X    if [ "$?" -eq "2" ]; then
  1587. X        ${ECHO} "Error removing files from index file, aborting." 1>&2
  1588. X        exit 1
  1589. X    fi
  1590. X
  1591. X    ${MV} -f "${TEMP_DIR}/${TMP_NAME}-2.$$" "${TEMP_DIR}/${TMP_NAME}-1.$$"
  1592. X
  1593. X    if [ ! "$?" -eq "0" ]; then
  1594. X        ${ECHO} "Error removing files from index file, aborting." 1>&2
  1595. X        exit 1
  1596. X    fi
  1597. X
  1598. X    shift
  1599. X    done
  1600. X
  1601. X    # The specified file names have been removed from the copy of the
  1602. X    # database. ${MV} the copy of the database to "${TMP_DB}". Exit on
  1603. X    # failure.
  1604. X
  1605. X    ${MV} "${TEMP_DIR}/${TMP_NAME}-1.$$" "${TMP_DB}"
  1606. X
  1607. X    if [ ! "$?" -eq "0" ]; then
  1608. X    ${ECHO} "Error removing files from index file, aborting." 1>&2
  1609. X    exit 1
  1610. X    fi
  1611. X
  1612. X    # Flush the buffers and call update_database() to update the
  1613. X    # inverted index file.
  1614. X
  1615. X    ${SYNC}
  1616. X    ${SYNC}
  1617. X
  1618. X    update_database "${DB_NAME}" "${TMP_DB}"
  1619. }
  1620. X
  1621. # Relevance count function-the count of records in a file that contain
  1622. # match(s) will be output with the file name containing the matches.
  1623. # This provides the system with a remedial "relevance feedback"
  1624. # capability. The original text files that were used to construct the
  1625. # inverted index file must be available in the system to use this
  1626. # option.
  1627. #
  1628. # Note that ${LOOK}, essentially, uses the regular expression
  1629. # "^word${TAB}" (if exact matches are enabled) to search for words, and
  1630. # ${EGREP} uses, simply, "word", ie., this function will output more
  1631. # instances of records that contain "word" than were found by ${LOOK}.
  1632. #
  1633. # The required arguments are the regular expression to be searched for,
  1634. # followed by the the list of file names to be searched.
  1635. X
  1636. relevance_count()
  1637. {
  1638. X    # If no arguments, or only one argument, return.
  1639. X
  1640. X    if [ "$#" -eq "0" -o "$#" -eq "0" ]; then
  1641. X    return
  1642. X    fi
  1643. X
  1644. X    # Read the search field.
  1645. X
  1646. X    SEARCH_FIELD="$1"
  1647. X    shift
  1648. X
  1649. X    # ${EGREP} each file, count the records that contain queried words,
  1650. X    # ignoring case.  ${SORT} the output, in reverse numerical order on
  1651. X    # the second field, using a colon as the field delimiter.  This
  1652. X    # arranges the file names in descending order of the number of
  1653. X    # queried words that the files contain. One of the problems with
  1654. X    # ${EGREP} is that if only one file name is present, it does not
  1655. X    # output the file name-so handle each file separately, using ${ECHO}
  1656. X    # to print the file name.
  1657. X
  1658. X    while [ "$#" -ne "0" ]
  1659. X    do
  1660. X    ${ECHO} "$1: \c"
  1661. X    ${EGREP} -ic "${SEARCH_FIELD}" "$1"
  1662. X    shift
  1663. X    done | ${SORT} -n -r -t: +1
  1664. }
  1665. X
  1666. # Proximity retrieval function-the records in a file that contain
  1667. # match(s) will be output after the file name containing the matches.
  1668. # This output format provides the system with a remedial "permuted
  1669. # index" type of "proximity retrieval." The original text files that
  1670. # were used to construct the inverted index must be available in the
  1671. # system to use this option.
  1672. #
  1673. # Note that ${LOOK}, essentially, uses the regular expression
  1674. # "^word${TAB}" (if exact matches are enabled) to search for words, and
  1675. # ${EGREP} uses, simply, "word", ie., this function will output more
  1676. # instances of records that contain "word" than were found by ${LOOK}.
  1677. #
  1678. # The required arguments are the regular expression to be searched for,
  1679. # followed by the the list of file names to be searched.
  1680. X
  1681. relevance_proximity()
  1682. {
  1683. X    # If no arguments, or only one argument, return.
  1684. X
  1685. X    if [ "$#" -eq "0" -o "$#" -eq "0" ]; then
  1686. X    return
  1687. X    fi
  1688. X
  1689. X    # Read the search field.
  1690. X
  1691. X    SEARCH_FIELD="$1"
  1692. X    shift
  1693. X
  1694. X    # ${EGREP} each file, count the records that contain queried words,
  1695. X    # ignoring case.  ${SORT} the output, in reverse numerical order on
  1696. X    # the second field, using a colon as the field delimiter.  This
  1697. X    # arranges the file names in descending order of the number of
  1698. X    # queried words that the files contain. One of the problems with
  1699. X    # ${EGREP} is that if only one file name is present, it does not
  1700. X    # output the file name-so handle each file separately, using ${ECHO}
  1701. X    # to print the file name. Strip the colon and anything following it
  1702. X    # using ${SED}.
  1703. X
  1704. X    while [ "$#" -ne "0" ]
  1705. X    do
  1706. X    ${ECHO} "$1: \c"
  1707. X    ${EGREP} -ic "${SEARCH_FIELD}" "$1"
  1708. X    shift
  1709. X    done | ${SORT} -n -r -t: +1 | ${SED} "s/: .*$//" | while read file_name
  1710. X    do
  1711. X    # For each file name, print the file name, and ...
  1712. X
  1713. X    ${ECHO} ""
  1714. X    ${ECHO} "${file_name}:"
  1715. X    ${ECHO} ""
  1716. X
  1717. X    # ${EGREP} the file for instances of the queried words, ignoring
  1718. X    # case and without printing the file name, pipe this output to
  1719. X    # ${SED} to despace, and detab the record, and print the record
  1720. X    # with leading and trailing dots
  1721. X
  1722. X    ${EGREP} -i -h "${SEARCH_FIELD}" "${file_name}" | ${SED} "/[[${TAB} ]*[${TAB} ]/s/[[${TAB} ]*[${TAB} ]/ /g;/^ /s/^ //;/^/s/^/\.\.\. /;/$/s/$/ \.\.\./"
  1723. X    done
  1724. }
  1725. X
  1726. # Trap all interrupts, removing the the temporary and error files on any
  1727. # signal.
  1728. X
  1729. trap "${RM} -f ${TEMP_FILES}; exit" 0 1 2 3 15
  1730. X
  1731. # The inverted index backup file should have been removed on the
  1732. # completion of any previous execution of this script. If it wasn't, the
  1733. # previous execution failed, and the backup file should be restored-if
  1734. # this fails, exit with an error.
  1735. X
  1736. if [ -f "${DB_NAME}.BAK" ]; then
  1737. X    ${ECHO} "Index backup file exists, restoring." 1>&2
  1738. X    ${MV} -f "${DB_NAME}.BAK" "${DB_NAME}"
  1739. X    if [ "$?" -eq "0" ]; then
  1740. X    ${ECHO} "Restoration of index backup file succeeded, continuing." 1>&2
  1741. X    else
  1742. X    ${ECHO} "Restoration of index backup file failed, aborting." 1>&2
  1743. X    exit 1
  1744. X    fi
  1745. X    ${SYNC}
  1746. X    ${SYNC}
  1747. fi
  1748. X
  1749. # If no operations specified, print the version, which also gives
  1750. # information on help(), and exit.
  1751. X
  1752. if [ "$#" -eq "0" ]; then
  1753. X    ${ECHO} "${VERSION}"
  1754. X    exit 1
  1755. fi
  1756. X
  1757. # Set variables from command line options. The attempt here is to allow
  1758. # only one mode switch in the command line-two or more are illegal. A
  1759. # "-w" switch is required for any command that updates the database.
  1760. # READ_MODE is the default mode. GETOPT could be used, but some of the
  1761. # switches require two character names.
  1762. X
  1763. while [ "$#" -gt "0" ]
  1764. do
  1765. X    case "$1" in
  1766. X
  1767. X    # Lexical analysis option.
  1768. X
  1769. X    -1) LEXICO=1; shift;;
  1770. X    -2) LEXICO=2; shift;;
  1771. X    -3) LEXICO=3; shift;;
  1772. X    -4) LEXICO=4; shift;;
  1773. X    -5) LEXICO=5; shift;;
  1774. X    -6) LEXICO=6; shift;;
  1775. X    -7) LEXICO=7; shift;;
  1776. X    -8) LEXICO=8; shift;;
  1777. X
  1778. X    # Delete file(s) option-requires write mode enable.
  1779. X
  1780. X    -df) if [ "${OP_MODE}" -eq "${WRITE_MODE}" ]; then
  1781. X        OP_MODE="${DELETE_FILES}"; shift
  1782. X         else
  1783. X        ${ECHO} "Delete files mode requires preceding \"-w\" option, aborting." 1>&2; exit 1
  1784. X         fi;;
  1785. X
  1786. X    # Delete words(s) option-requires write mode enable.
  1787. X
  1788. X    -dw) if [ "${OP_MODE}" -eq "${WRITE_MODE}" ]; then
  1789. X        OP_MODE="${DELETE_WORDS}"; shift
  1790. X         else
  1791. X        ${ECHO} "Delete words mode requires preceding \"-w\" option, aborting." 1>&2; exit 1
  1792. X         fi;;
  1793. X
  1794. X    # Exact query match option.
  1795. X
  1796. X    -e) END="${TAB}"; shift;;
  1797. X
  1798. X    # Change file name option, "${TMP_NAME} is the base name of
  1799. X    # this file name.
  1800. X
  1801. X    -f) shift
  1802. X        if [ "$#" -gt "0" ]; then
  1803. X        DB_NAME="$1"
  1804. X        TMP_NAME=`basename "$1"`
  1805. X        shift
  1806. X        else
  1807. X        ${ECHO} "No inverted index file name specified, aborting." 1>&2; exit 1
  1808. X        fi;;
  1809. X
  1810. X    # Request for help option.
  1811. X
  1812. X    -h) help; shift; exit 0;;
  1813. X
  1814. X    # Request for egrep(1) compatable regular expression search word
  1815. X    # to be output prior to file names option.
  1816. X
  1817. X    -r) RELEVANCE_ATTRIBUTE=1; shift;;
  1818. X
  1819. X    # Count of records in a file that contain match(s) option-
  1820. X    # requires no previous mode switch..
  1821. X
  1822. X    -rc) if [ "${OP_MODE}" -eq "${READ_MODE}" ]; then
  1823. X        RELEVANCE_ATTRIBUTE=1; OP_MODE="${RELEVANCE_COUNT}" ;shift
  1824. X         else
  1825. X        ${ECHO} "Illegal relevance mode specified, aborting." 1>&2; exit 1
  1826. X         fi;;
  1827. X
  1828. X    # Records in a file that contain match(s) will be output after
  1829. X    # the file name option-requires no previous mode switch..
  1830. X
  1831. X    -rp) if [ "${OP_MODE}" -eq "${READ_MODE}" ]; then
  1832. X        RELEVANCE_ATTRIBUTE=2; OP_MODE="${RELEVANCE_PROXIMITY}" ;shift
  1833. X         else
  1834. X        ${ECHO} "Illegal relevance mode specified, aborting." 1>&2; exit 1
  1835. X         fi;;
  1836. X
  1837. X    # Version option.
  1838. X
  1839. X    -v) ${ECHO} "${VERSION}"; shift; exit 0;;
  1840. X
  1841. X    # Write mode enable-requires no previous mode switch.
  1842. X
  1843. X    -w) if [ "${OP_MODE}" -eq "${READ_MODE}" ]; then
  1844. X        OP_MODE="${WRITE_MODE}"; shift
  1845. X        else
  1846. X        ${ECHO} "Illegal write mode specified, aborting." 1>&2; exit 1
  1847. X        fi;;
  1848. X
  1849. X    # Anything else is a file name or query word/operator.
  1850. X
  1851. X    *) break;;
  1852. X    esac
  1853. done
  1854. X
  1855. # Dispatch to the specified operation.
  1856. X
  1857. case "${OP_MODE}" in
  1858. X
  1859. X    # Write mode, call the function.
  1860. X
  1861. X    "${WRITE_MODE}") write_index "$@";;
  1862. X
  1863. X    # Read mode, call the function.
  1864. X
  1865. X    "${READ_MODE}") read_index "$@";;
  1866. X
  1867. X    # Delete words mode, call the function.
  1868. X
  1869. X    "${DELETE_WORDS}") remove_words "$@";;
  1870. X
  1871. X    # Delete files mode, call the function.
  1872. X
  1873. X    "${DELETE_FILES}") remove_files "$@";;
  1874. X
  1875. X    # Relevance count-call read_index() to get a list of the files, with
  1876. X    # RELEVANCE_ATTRIBUTES set.
  1877. X
  1878. X    "${RELEVANCE_COUNT}") relevance_count `read_index "$@"`;;
  1879. X
  1880. X    # Proximity relevance-call read_index() to get a list of the files,
  1881. X    # with RELEVANCE_ATTRIBUTES set. Pipe these files to
  1882. X    # relevance_proximity().
  1883. X
  1884. X    "${RELEVANCE_PROXIMITY}") relevance_proximity `read_index "$@"`;;
  1885. X
  1886. X    # Nothing to do, fall through to the exit.
  1887. X
  1888. X    *) break;;
  1889. esac
  1890. X
  1891. # Restore trapping all interrupts, removing the the temporary and error
  1892. # files on any signal, (update_database() disables interrupts.)
  1893. X
  1894. trap "${RM} -f ${TEMP_FILES}; exit" 0 1 2 3 15
  1895. X
  1896. Xexit 0
  1897. SHAR_EOF
  1898. chmod 0766 qt/qt ||
  1899. echo 'restore of qt/qt failed'
  1900. Wc_c="`wc -c < 'qt/qt'`"
  1901. test 65404 -eq "$Wc_c" ||
  1902.     echo 'qt/qt: original size 65404, current size' "$Wc_c"
  1903. fi
  1904. # ============= qt/README ==============
  1905. if test -f 'qt/README' -a X"$1" != X"-c"; then
  1906.     echo 'x - skipping qt/README (File already exists)'
  1907. else
  1908. echo 'x - extracting qt/README (Text)'
  1909. sed 's/^X//' << 'SHAR_EOF' > 'qt/README' &&
  1910. Qt stands for Query Text, a text information retrieval system. Qt
  1911. creates, maintains, and queries a full text database. The database
  1912. file system is organized as an inverted index. The program is written
  1913. as a single script, in Bourne Shell, and permits simple natural
  1914. language queries.
  1915. X
  1916. As a simple application example, this program can be used to search
  1917. the "catman" pages for a command that performs a specific function,
  1918. even though the command's name is not known-e.g., if you knew what
  1919. you wanted to do, you could find the command that would do it.
  1920. X
  1921. The program, qt, is free software, and can be redistributed and/or
  1922. modified, without any restrictions. It is distributed with no
  1923. warranty of any kind, implied or otherwise.  Specifically, there is
  1924. no warranty of fitness for any particular purpose and/or
  1925. merchantability.
  1926. X
  1927. Comments and/or bug reports should be addressed to:
  1928. X
  1929. X      john@johncon.com (John Conover)
  1930. X
  1931. Known caveats: There is no concurrency control-it would be
  1932. ill-advised to use this program as a concurrent application.
  1933. Additionally, the natural language query does not support grouping
  1934. operators.
  1935. X
  1936. For a quick start, execute qt -h for help, which may be re-directed to
  1937. stdio. At the "tail -23" of this help file are some simple commands to
  1938. evaluate this script.
  1939. X
  1940. Installation:
  1941. X
  1942. The comments in this script are verbose, and should be stripped prior
  1943. to any installation with something like:
  1944. X
  1945. X      sed '/^ *#/d;/^$/d' qt > qt.new
  1946. X
  1947. and installing qt.new as qt in the executable path. Likewise,
  1948. possibly, the function, help(), should be eliminated.     The function,
  1949. find_program(), is not efficient and should be eliminated, by hard
  1950. coding the paths to the various programs in your system.  There are
  1951. tab characters used in this script, (which are referenced as the
  1952. variable, "${TAB}") requiring that the script be saved with tabs.
  1953. SHAR_EOF
  1954. chmod 0644 qt/README ||
  1955. echo 'restore of qt/README failed'
  1956. Wc_c="`wc -c < 'qt/README'`"
  1957. test 1844 -eq "$Wc_c" ||
  1958.     echo 'qt/README: original size 1844, current size' "$Wc_c"
  1959. fi
  1960. exit 0
  1961.