search++(1)
β¨ NAME β¨
search++ β okay so this is the SWISH++ searcher, and honestly? she's that girl. π
π SYNOPSIS π
search++ [ options ] query
π¦ DESCRIPTION π¦
So search++ is literally the searcher part of SWISH++. What it does is take a query and go digging through an index you already built earlier to find the words you asked for. And like β not only does it run from the command line like a normal little tool, it can also run as a daemon process, in which case it's acting as a "search++ server." Main character energy, truly. π
πΈ QUERY INPUT πΈ
Query Syntax
Okay bestie, here's the formal grammar of a query, no cap:
query: query relop meta
meta
meta: meta_name = primary
primary
meta_name: word
primary: (query)
not meta
word
word*
relop: and
near
not near
or
(empty)
But in practice? The query is just the set of words you're hunting for, maybe narrowed down to meta data, and maybe smooshed together with the operators and, or, near, not, and not near. The asterisk (*) works as a wildcard character, but only at the end of a word.
Heads up bestie: an asterisk and parentheses are shell meta-characters, so when you're passing them to a shell they've gotta either be escaped (backslashed) or quoted. Do not skip this, I'm begging. π
Now β even though near right before not is syntactically legal, it's a semantic error, because those queries are just nonsense. Like, look at this mess:
mouse near not computer
Queries get evaluated left-to-right, which means and has the exact same precedence as or. (Yes really. It's giving "we don't do hierarchy here.") For more about query syntax, go check the EXAMPLES.
Character Mapping and Word Determination
The same character mapping and word determination heuristics that index++(1) uses get applied to queries before any searching happens. Same energy, same rules. π«Ά
π₯ RESULTS OUTPUT π₯
Result Components
Results come out in either "classic" or XML format. Either way, here's what's in them:
- rank β an integer from
1to100. - path-name β the relative path to where the file was originally indexed.
- file-size β the file's size in bytes.
- file-title β if the file is a format that can have titles (HTML, XHTML, LaTeX, mail, or Unix manual pages) and the title was extracted, then
file-titleis its title; otherwise, it's its filename.
Classic Results Format
The "classic" results format is plain text that looks like:
rank path-name file-size file-title
And it parses super easily in Perl with:
($rank,$path,$size,$title) = split( / /, $_, 4 );
(FYI the separator can be swapped out via the -R or --separator options, or the ResultSeparator variable. Your call, bestie.)
Before the results lines, you might also get comment lines carrying extra info about the query results. Comment lines look like this:
# comment-key: comment-value
And here are the keys and values:
ignored: stop-wordsβ the list of stop-words (separated by spaces) that got ignored in the query.not found: wordβ the word was not found in the index.results: result-countβ the total number of results.
XML Results Format
The XML results format is given by this DTD:
<!ELEMENT SearchResults (IgnoredList?, ResultCount, ResultList?)>
<!ELEMENT IgnoredList (Ignored+)>
<!ELEMENT Ignored (#PCDATA)>
<!ELEMENT ResultCount (#PCDATA)>
<!ELEMENT ResultList (File+)>
<!ELEMENT File (Rank, Path, Size, Title)>
<!ELEMENT Rank (#PCDATA)>
<!ELEMENT Path (#PCDATA)>
<!ELEMENT Size (#PCDATA)>
<!ELEMENT Title (#PCDATA)>
and by the XML schema located at:
http://homepage.mac.com/pauljlucas/software/swish/SearchResults/SearchResults.xsd
For example:
<?xml version="1.0" encoding="us-ascii"?>
<!DOCTYPE SearchResults SYSTEM
"http://homepage.mac.com/pauljlucas/software/swish/SearchResults.dtd">
<SearchResults
xmlns="http://homepage.mac.com/pauljlucas/software/swish/SearchResults"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://homepage.mac.com/pauljlucas/software/swish/SearchResults
SearchResults.xsd">
<IgnoredList>
<Ignored>stop-word</Ignored>
...
</IgnoredList>
<ResultCount>42</ResultCount>
<ResultList>
<File>
<Rank>rank</Rank>
<Path>path-name</Path>
<Size>file-size</Size>
<Title>file-title</Title>
</File>
...
</ResultList>
</SearchResults>
π RUNNING AS A DAEMON PROCESS π
Description
So search++ can alternatively run as a daemon process (via either the -b or --daemon-type options, or the SearchDaemon variable), functioning as a "search++ server" by listening to a Unix domain socket (specified by either the -u or --socket-file options, or the SocketFile variable), a TCP socket (specified by either the -a or --socket-address options, or the SocketAddress variable), or both. π«
Unix domain sockets are the preferred choice for both performance and security. Periodt.
For search-intensive applications β think a search engine on a heavily used web site β this can give you a large performance improvement, because the start-up cost (fork(2), exec(2), and initialization) only gets paid once. One and done, slay. πΈ
Also, if the process was started with root privileges, it gives them away immediately after initialization and before servicing any requests. Iconic behavior, honestly. π
Clients and Requests
Search clients connect to a daemon through a socket and send a query in the same manner as on the command line β and yes, that includes the first word being search++. The only exception is that shell meta-characters must not be escaped (backslashed), since no shell is involved at all. Search results get returned via that same socket. See the EXAMPLES. π«Ά
Multithreading
A daemon can serve multiple query requests at the same time because it's multi-threaded. When it starts up, it "pre-threads," meaning it creates a pool of threads in advance that service an indefinite number of requests β another performance improvement, since a thread isn't created and destroyed per request. Efficiency queen. π
There's an initial, minimum number of threads in the thread pool. The number of threads grows dynamically when there are more requests than threads, but it never goes past a specified maximum, so the server doesn't start thrashing. (See the -t, --min-threads, -T, and --max-threads options, or the ThreadsMin or ThreadsMax variables.)
If the number of threads hits the maximum, subsequent requests get queued until existing threads become available to service them after finishing their in-progress requests. (See either the -q or --queue-size options, or the SocketQueueSize variable.)
And if there are more than the minimum number of threads and some just sit there idle longer than a specified timeout period (because the number of requests per unit time dropped), then threads die off until the pool returns to its original minimum size. RIP to those threads, they served. π (See either the -O or --thread-timeout options, or the ThreadTimeout variable.)
Restrictions
- A single daemon can search only a single index.
- To
search++multiple indices concurrently, you can run multiple daemons, each one searching its own index and using its own socket. - An index must not be modified or deleted while a daemon is using it. Not even a little. Do not test this. π«
β¨ OPTIONS β¨
okay bestie, before we even get to the options themselves, we gotta talk about how option parsing works here, because it has lore π
- Options begin with either a
-for short options or a--for long options. Either a-or--by itself explicitly ends the options; however, the difference is that-is returned as the first non-option whereas--is skipped entirely. Either short or long options may be used. Long option names may be abbreviated so long as the abbreviation is unambiguous. - For a short option that takes an argument, the argument is either taken to be the remaining characters of the same option, if any, or, if not, is taken from the next option unless said option begins with a
-. - Short options that take no arguments can be grouped (but the last option in the group can take an argument), e.g.
-Bq511is equivalent to-B -q 511. β¨ - For a long option that takes an argument, the argument is either taken to be the characters after a
=, if any, or, if not, is taken from the next option unless said option begins with a-.
now. the actual options. buckle up π
-?,--helpβ Print the usage (``help'') message and exit. π«Ά-aa,--socket-address=aβ When running as a daemon, the address, a, to listen to for TCP requests. (Default is all IP addresses and port1967.) The address argument is of the form:
[ host : ] port
that is: an optional host and colon followed by a port number. The host may be one of a host name, an IP address, or the * character meaning any IP address.'' Omitting the host and colon also means any IP address.'' π
-bt,--daemon-type=tβ Run as a daemon process. (Default is not to.) The type, t, is one of:noneβ Same as not specifying the option at all. (This does not purport to be useful, but rather consistent with the types that can be specified to theSearchDaemonvariable.)tcpβ Listen on a TCP socket (see the-aoption).unixβ Listen on a Unix domain socket (see the-uoption).bothβ Listen on both.
By default, if executed from the command-line, search++ appears to return immediately; however, it has merely detached from the terminal and put itself into the background. There is no need to follow the command with an &. π
-B,--no-backgroundβ When running as a daemon process, do not detach from the terminal and run in the background. (Default does.)
The reason not to run in the background is so a wrapper script can see if the process dies for any reason and automatically restart it.
This option is implied by the -X or --launchd options. π
-cf,--config-file=fβ The name of the configuration file, f, to use. (Default isswish++.confin the current directory.) A configuration file is not required: if none is specified and the default does not exist, none is used; however, if one is specified and it does not exist, then this is an error. π¨-d,--dump-wordsβ Dump the query word indices to standard output and exit. Wildcards are not permitted.-D,--dump-index++β Dump the entire word index to standard output and exit.-Ff,--format=fβ The format, f, search results are output in. The format is eitherclassicorXML. (Default isclassic.)-Gs,--group=sβ The group, s, to switch the process to after starting and only if started as root. (Default isnobody.)-if,--index-file=fβ The name of the index file, f, to use. (Default isswish++.indexin the current directory.) π-mn,--max-results=nβ The maximum number of results, n, to return. (Default is100.)-M,--dump-metaβ Dump the meta-name index to standard output and exit.-nn,--near=nβ The maximum number of words apart, n, two words can be to be considered ``near'' each other in queries using near. (Default is10.)-os,--socket-timeout=sβ The number of seconds, s, a sarch client has to complete a query request before the socket connection is closed. (Default is10.) This is to prevent a client from connecting, not completing a request, and causing the thread servicing the request to wait forever. β³-Os,--thread-timeout=sβ The number of seconds, s, until an idle spare thread dies while running as a daemon. (Default is30.)-pn,--word-percent=nβ The maximum percentage, n, of files a word may occur in before it is discarded as being too frequent. (Default is100.) If you want to keep all words regardless, specify101. π-Pf,--pid-file=fβ The name of the file to record the process ID ofsearch++if running as a daemon. (Default is none.)-qn,--queue-size=nβ The maximum number of socket connections to queue. (Default is511.)-rn,--skip-results=nβ The initial number of results, n, to skip. (Default is0.) Used in conjunction with-mor--max-results, results can be returned in ``pages.'' π-Rs,--separator=sβ The classic result separator string. (Default is" ".)-s,--stem-wordsβ Perform stemming (suffix stripping) on words during the search. Words that end in the wildcard character are not stemmed. (Default is no.)-S,--dump-stopβ Dump the stop-word index to standard output and exit.-tn,--min-threads=nβ Minimum number of threads to maintain while running as a daemon.-Tn,--max-threads=nβ Maximum number of threads to allow while running as a daemon.-uf,--socket-file=fβ The name of the Unix domain socket file to use while running as a daemon. (Default is/tmp/search.socket.) π§¦-Us,--user=sβ The user, s, to switch the process to after starting and only if started as root. (Default isnobody.)-V,--versionβ Print the version number of SWISH++ to standard output and exit. β-wn[,c],--window=n[,c]β Dump a ``window'' of at most n lines around each query word matching c characters. Wildcards are not permitted. (Default for c is0.) Every window ends with a blank line. πͺ-X,--launchdβ If run as a daemon process, cooperate with Mac OS X'slaunchd(8)by not ``daemonizing'' itself sincelaunchd(8)handles that. This option implies the-Bor--no-backgroundoptions.
This option is available only under Mac OS X, should be used only for version 10.4 (Tiger) or later, and only when search++ will be started via launchd(8). π
π CONFIGURATION FILE π
The following variables can be set in a configuration file. Variables and command-line options can be mixed, the latter taking priority. so you can have both, command line just wins π
Groupβ Same as-Gor--groupIndexFileβ Same as-ior--index-fileLaunchdCooperationβ Same as-Xor--launchdPidFileβ Same as-Por--pid-fileResultSeparatorβ Same as-Ror--separatorResultsFormatβ Same as-For--formatResultsMaxβ Same as-mor--max-resultsSearchBackgroundβ Same as-Bor--no-backgroundSearchDaemonβ Same as-bor--daemon-typeSocketAddressβ Same as-aor--socket-addressSocketFileβ Same as-uor--socket-fileSocketQueueSizeβ Same as-qor--queue-sizeSocketTimeoutβ Same as-oor--socket-timeoutStemWordsβ Same as-sor--stem-wordsThreadsMaxβ Same as-Tor--max-threadsThreadsMinβ Same as-tor--min-threadsThreadTimeoutβ Same as-Oor--thread-timeoutUserβ Same as-Uor--userWordFilesMaxβ Same as-for--word-filesWordPercentMaxβ Same as-por--word-percentWordsNearβ Same as-nor--near
β¨ EXAMPLES β¨
Okay bestie, this is the fun part β let's actually use the thing. π
πΈ Simple Queries
So like, this query:
computer mouse
is literally the same as β and short for β this:
computer and mouse
(because and is implicit, the vibes just do it automatically) and it would return only those documents that contain both words. Two words, both required, no notes. π
Then this query:
cat or kitten or feline
would return only those documents regarding cats. Obsessed with how that works.
And this query:
mouse and computer or keyboard
is the same as:
(mouse and computer) or keyboard
(because queries are evaluated left-to-right β left to right, always, periodt) in that they will both return only those documents regarding either mice attached to a computer or any kind of keyboard.
But heads up β neither of those is the same as:
mouse and (computer or keyboard)
that would return only those documents regarding mice (including the rodents π) and either a computer or a keyboard. Parentheses are the main character here, no cap.
π¦ Queries Using Wildcards
The query:
comput*
would return only those documents that contain words beginning with comput such as computation,'' computational,'' computer,'' computerize,'' ``computing,'' and others. Wildcarded words can be used anywhere ordinary words can be β they're just like everyone else, fr fr.
The query:
comput* (medicine or doctor*)
would return only those documents that contain something about computer use in medicine or by doctors. Slay. π©Ί
π Queries Using ``not''
The query:
mouse or mice and not computer*
would return only those documents regarding mice (the rodents) and not the kind attached to a computer. Rodent supremacy, I guess. π
π§ Queries Using ``near''
Using near'' is the same as using and'' except that it not only requires both words to be in the documents, but that they be near each other β i.e., it returns potentially fewer documents than the corresponding ``and'' query. So it's stricter, highkey.
The query:
computer near mouse
would return only those documents where both words are near each other.
The query:
mouse near (computer or keyboard)
is the same as:
(mouse near computer) or (mouse near keyboard)
i.e., ``near'' gets distributed across parenthesized subqueries. It's giving distributive property. β¨
π Queries Using ``not near''
Using not near'' is the same as using and not'' except that it allows the right-hand side words to be in the documents, just not near the left-hand side words β i.e., it returns potentially more documents than the corresponding and not'' query. Of course the word(s) on the right-hand side need not be in the documents at all, i.e., they would be considered infinitely far'' apart. π«
The query:
mouse or mice not near computer*
would return only those documents regarding mice (the rodents) more effectively than the query:
mouse or mice and not computer*
because the latter would exclude documents about mice (the rodents) where computers just so happened to be mentioned in the same documents. Rude, honestly.
π Queries Using Meta Data
The query:
author = hawking
would return only those documents whose author attribute contains ``hawking.''
The query:
author = hawking radiation
would return only those documents regarding radiation whose author attribute contains ``hawking.''
The query:
author = (stephen hawking)
would return only those documents whose author is Stephen Hawking. Iconic.
The query:
author = (stephen hawking) or (black near hole*)
would return only those documents whose author is Stephen Hawking or that contain the word black'' near hole'' or ``holes'' regardless of the author.
Note that the second set of parentheses are necessary β otherwise the query would have been the same as:
(author = (stephen hawking) or black) near hole*
that would have additionally required both stephen'' and hawking'' to be near hole'' or holes.'' So like, don't do that unless you mean it. π
π«Ά Sending Queries to a Search Daemon
To send a query request to a search daemon using Perl, first open the socket and connect to the daemon (see [Wall], pp. 439-440):
use Socket;
$SocketFile = '/tmp/search.socket';
socket( SEARCH, PF_UNIX, SOCK_STREAM, 0 ) or
die "can not open socket: $!\n";
connect( SEARCH, sockaddr_un( $SocketFile ) ) or
die "can not connect to \"$SocketFile\": $!\n";
Autoflush must be set for the socket filehandle (see [Wall], p. 781), otherwise the server thread will hang since I/O buffering will wait for the buffer to fill that will never happen since queries are short. So like, do not skip this step bestie, I am begging. π
select( (select( SEARCH ), $| = 1)[0] );
Next, send a query request (beginning with the word ``search++'' and any options just as with a command-line) to the daemon via the socket filehandle making sure to include a trailing newline since the server reads an entire line of input (so therefore it looks and waits for a newline):
$query = 'mouse and computer';
print SEARCH "search++ $query\n";
Finally, read the results back and print them:
print while <SEARCH>;
close( SEARCH );
π EXIT STATUS π
Exits with one of the values given below:
0β Success. π1β Error in configuration file.2β Error in command-line options.40β Unable to read index file.50β Malformed query.51β Attempted ``near'' search++ without word-position data.60β Could not write to PID file.61β Host or IP address is invalid or nonexistent.62β Could not open a TCP socket.63β Could not open a Unix domain socket.64β Could notunlink(2)a Unix domain socket file.65β Could notbind(3)to a TCP socket.66β Could notbind(3)to a Unix domain socket.67β Could notlisten(3)to a TCP socket.68β Could notlisten(3)to a Unix domain socket.69β Could notselect(3).70β Could notaccept(3)a socket connection.71β Could notfork(2)child process.72β Could not change directory to/.73β Could not create thread.74β Could not create thread key.75β Could not detach thread.76β Could not initialize thread condition.77β Could not initialize thread mutex.78β Could not switch to user.79β Could not switch to group.
πΈ CAVEATS πΈ
- Stemming can be done only when searching through an index of files that are in English because the Porter stemming algorithm used only stems English words. English only, sorry to the polyglots. π
- When run as a daemon using a TCP socket, there are no security restrictions on who may connect and search++. The code to implement domain and IP address restrictions isn't worth it since such things are better handled by firewalls and routers. So like, handle that yourself, bestie. π₯
- XML output can currently only be obtained for actual search results and not word, index, meta-name, or stop-word dumps.
π FILES π
swish++.confβ default configuration file nameswish++.indexβ default index file name
π SEE ALSO π
index++(1)perlfunc(1)exec(2)fork(2)unlink(2)accept(3)bind(3)listen(3)select(3)swish++.conf(5)launchd(8)searchmonitor(8)
Tim Bray, et al. Extensible Markup Language (XML) 1.0, February 10, 1998.
Bradford Nichols, Dick Buttlar, and Jacqueline Proulx Farrell. Pthreads Programming, O'Reilly & Associates, Sebastopol, CA, 1996.
M.F. Porter. ``An Algorithm For Suffix Stripping,'' Program, 14(3), July 1980, pp. 130-137.
W. Richard Stevens. Unix Network Programming, Vol 1, 2nd ed., Prentice-Hall, Upper Saddle River, NJ, 1998.
Larry Wall, et al. Programming Perl, 3rd ed., O'Reilly & Associates, Inc., Sebastopol, CA, 2000.
π¦ AUTHOR π¦
Paul J. Lucas <pauljlucas@mac.com>
And that's a wrap on the whole page, bestie!! π«Ά This was SWISH++, the search++(1) page, dated June 16, 2005. Go forth and query slayfully β ily, bye! π
β¨
π¦ where this page comes from
- debian bookworm: swish++ 6.1.5-6
- debian trixie: swish++ 6.1.5-6
- ubuntu jammy: swish++ 6.1.5-6
- ubuntu noble: swish++ 6.1.5-6build1
- ubuntu questing: swish++ 6.1.5-6build1
- ubuntu resolute: swish++ 6.1.5-6build2
- ubuntu stonking: swish++ 6.1.5-6build2
- Copyright (C) 2003 Paul J. Lucas
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms