mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-07-31 04:02:00 -07:00
binary: rejigger ripgrep's handling of binary files
This commit attempts to surface binary filtering in a slightly more user friendly way. Namely, before, ripgrep would silently stop searching a file if it detected a NUL byte, even if it had previously printed a match. This can lead to the user quite reasonably assuming that there are no more matches, since a partial search is fairly unintuitive. (ripgrep has this behavior by default because it really wants to NOT search binary files at all, just like it doesn't search gitignored or hidden files.) With this commit, if a match has already been printed and ripgrep detects a NUL byte, then it will print a warning message indicating that the search stopped prematurely. Moreover, this commit adds a new flag, --binary, which causes ripgrep to stop filtering binary files, but in a way that still avoids dumping binary data into terminals. That is, the --binary flag makes ripgrep behave more like grep's default behavior. For files explicitly specified in a search, e.g., `rg foo some-file`, then no binary filtering is applied (just like no gitignore and no hidden file filtering is applied). Instead, ripgrep behaves as if you gave the --binary flag for all explicitly given files. This was a fairly invasive change, and potentially increases the UX complexity of ripgrep around binary files. (Before, there were two binary modes, where as now there are three.) However, ripgrep is now a bit louder with warning messages when binary file detection might otherwise be hiding potential matches, so hopefully this is a net improvement. Finally, the `-uuu` convenience now maps to `--no-ignore --hidden --binary`, since this is closer to the actualy intent of the `--unrestricted` flag, i.e., to reduce ripgrep's smart filtering. As a consequence, `rg -uuu foo` should now search roughly the same number of bytes as `grep -r foo`, and `rg -uuua foo` should search roughly the same number of bytes as `grep -ra foo`. (The "roughly" weasel word is used because grep's and ripgrep's binary file detection might differ somewhat---perhaps based on buffer sizes---which can impact exactly what is and isn't searched.) See the numerous tests in tests/binary.rs for intended behavior. Fixes #306, Fixes #855
This commit is contained in:
315
tests/binary.rs
Normal file
315
tests/binary.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use crate::util::{Dir, TestCommand};
|
||||
|
||||
// This file contains a smattering of tests specifically for checking ripgrep's
|
||||
// handling of binary files. There's quite a bit of discussion on this in this
|
||||
// bug report: https://github.com/BurntSushi/ripgrep/issues/306
|
||||
|
||||
// Our haystack is the first 500 lines of Gutenberg's copy of "A Study in
|
||||
// Scarlet," with a NUL byte at line 237: `abcdef\x00`.
|
||||
//
|
||||
// The position and size of the haystack is, unfortunately, significant. In
|
||||
// particular, the NUL byte is specifically inserted at some point *after* the
|
||||
// first 8192 bytes, which corresponds to the initial capacity of the buffer
|
||||
// that ripgrep uses to read files. (grep for DEFAULT_BUFFER_CAPACITY.) The
|
||||
// position of the NUL byte ensures that we can execute some search on the
|
||||
// initial buffer contents without ever detecting any binary data. Moreover,
|
||||
// when using a memory map for searching, only the first 8192 bytes are
|
||||
// scanned for a NUL byte, so no binary bytes are detected at all when using
|
||||
// a memory map (unless our query matches line 237).
|
||||
//
|
||||
// One last note: in the tests below, we use --no-mmap heavily because binary
|
||||
// detection with memory maps is a bit different. Namely, NUL bytes are only
|
||||
// searched for in the first few KB of the file and in a match. Normally, NUL
|
||||
// bytes are searched for everywhere.
|
||||
//
|
||||
// TODO: Add tests for binary file detection when using memory maps.
|
||||
const HAY: &'static [u8] = include_bytes!("./data/sherlock-nul.txt");
|
||||
|
||||
// This tests that ripgrep prints a warning message if it finds and prints a
|
||||
// match in a binary file before detecting that it is a binary file. The point
|
||||
// here is to notify that user that the search of the file is only partially
|
||||
// complete.
|
||||
//
|
||||
// This applies to files that are *implicitly* searched via a recursive
|
||||
// directory traversal. In particular, this results in a WARNING message being
|
||||
// printed. We make our file "implicit" by doing a recursive search with a glob
|
||||
// that matches our file.
|
||||
rgtest!(after_match1_implicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "Project Gutenberg EBook", "-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
WARNING: stopped searching binary file hay after match (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_implicit, except we provide a file to search
|
||||
// explicitly. This results in identical behavior, but a different message.
|
||||
rgtest!(after_match1_explicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "Project Gutenberg EBook", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
Binary file matches (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_explicit, except we feed our content on stdin.
|
||||
rgtest!(after_match1_stdin, |_: Dir, mut cmd: TestCommand| {
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "Project Gutenberg EBook",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
Binary file matches (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.pipe(HAY));
|
||||
});
|
||||
|
||||
// Like after_match1_implicit, but provides the --binary flag, which
|
||||
// disables binary filtering. Thus, this matches the behavior of ripgrep as
|
||||
// if the file were given explicitly.
|
||||
rgtest!(after_match1_implicit_binary, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--binary", "Project Gutenberg EBook", "-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
Binary file hay matches (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_implicit, but enables -a/--text, so no binary
|
||||
// detection should be performed.
|
||||
rgtest!(after_match1_implicit_text, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--text", "Project Gutenberg EBook", "-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_implicit_text, but enables -a/--text, so no binary
|
||||
// detection should be performed.
|
||||
rgtest!(after_match1_explicit_text, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--text", "Project Gutenberg EBook", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_implicit, except this asks ripgrep to print all matching
|
||||
// files.
|
||||
//
|
||||
// This is an interesting corner case that one might consider a bug, however,
|
||||
// it's unlikely to be fixed. Namely, ripgrep probably shouldn't print `hay`
|
||||
// as a matching file since it is in fact a binary file, and thus should be
|
||||
// filtered out by default. However, the --files-with-matches flag will print
|
||||
// out the path of a matching file as soon as a match is seen and then stop
|
||||
// searching completely. Therefore, the NUL byte is never actually detected.
|
||||
//
|
||||
// The only way to fix this would be to kill ripgrep's performance in this case
|
||||
// and continue searching the entire file for a NUL byte. (Similarly if the
|
||||
// --quiet flag is set. See the next test.)
|
||||
rgtest!(after_match1_implicit_path, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-l", "Project Gutenberg EBook", "-g", "hay",
|
||||
]);
|
||||
eqnice!("hay\n", cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_implicit_path, except this indicates that a match was
|
||||
// found with no other output. (This is the same bug described above, but
|
||||
// manifest as an exit code with no output.)
|
||||
rgtest!(after_match1_implicit_quiet, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-q", "Project Gutenberg EBook", "-g", "hay",
|
||||
]);
|
||||
eqnice!("", cmd.stdout());
|
||||
});
|
||||
|
||||
// This sets up the same test as after_match1_implicit_path, but instead of
|
||||
// just printing the matching files, this includes the full count of matches.
|
||||
// In this case, we need to search the entire file, so ripgrep correctly
|
||||
// detects the binary data and suppresses output.
|
||||
rgtest!(after_match1_implicit_count, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-c", "Project Gutenberg EBook", "-g", "hay",
|
||||
]);
|
||||
cmd.assert_err();
|
||||
});
|
||||
|
||||
// Like after_match1_implicit_count, except the --binary flag is provided,
|
||||
// which makes ripgrep disable binary data filtering even for implicit files.
|
||||
rgtest!(after_match1_implicit_count_binary, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-c", "--binary",
|
||||
"Project Gutenberg EBook",
|
||||
"-g", "hay",
|
||||
]);
|
||||
eqnice!("hay:1\n", cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match1_implicit_count, except the file path is provided
|
||||
// explicitly, so binary filtering is disabled and a count is correctly
|
||||
// reported.
|
||||
rgtest!(after_match1_explicit_count, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-c", "Project Gutenberg EBook", "hay",
|
||||
]);
|
||||
eqnice!("1\n", cmd.stdout());
|
||||
});
|
||||
|
||||
// This tests that a match way before the NUL byte is shown, but a match after
|
||||
// the NUL byte is not.
|
||||
rgtest!(after_match2_implicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n",
|
||||
"Project Gutenberg EBook|a medical student",
|
||||
"-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
WARNING: stopped searching binary file hay after match (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like after_match2_implicit, but enables -a/--text, so no binary
|
||||
// detection should be performed.
|
||||
rgtest!(after_match2_implicit_text, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--text",
|
||||
"Project Gutenberg EBook|a medical student",
|
||||
"-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
hay:236:\"And yet you say he is not a medical student?\"
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// This tests that ripgrep *silently* quits before finding a match that occurs
|
||||
// after a NUL byte.
|
||||
rgtest!(before_match1_implicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "Heaven", "-g", "hay",
|
||||
]);
|
||||
cmd.assert_err();
|
||||
});
|
||||
|
||||
// This tests that ripgrep *does not* silently quit before finding a match that
|
||||
// occurs after a NUL byte when a file is explicitly searched.
|
||||
rgtest!(before_match1_explicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "Heaven", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
Binary file matches (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like before_match1_implicit, but enables the --binary flag, which
|
||||
// disables binary filtering. Thus, this matches the behavior of ripgrep as if
|
||||
// the file were given explicitly.
|
||||
rgtest!(before_match1_implicit_binary, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--binary", "Heaven", "-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
Binary file hay matches (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like before_match1_implicit, but enables -a/--text, so no binary
|
||||
// detection should be performed.
|
||||
rgtest!(before_match1_implicit_text, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--text", "Heaven", "-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:238:\"No. Heaven knows what the objects of his studies are. But here we
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// This tests that ripgrep *silently* quits before finding a match that occurs
|
||||
// before a NUL byte, but within the same buffer as the NUL byte.
|
||||
rgtest!(before_match2_implicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "a medical student", "-g", "hay",
|
||||
]);
|
||||
cmd.assert_err();
|
||||
});
|
||||
|
||||
// This tests that ripgrep *does not* silently quit before finding a match that
|
||||
// occurs before a NUL byte, but within the same buffer as the NUL byte. Even
|
||||
// though the match occurs before the NUL byte, ripgrep still doesn't print it
|
||||
// because it has already scanned ahead to detect the NUL byte. (This matches
|
||||
// the behavior of GNU grep.)
|
||||
rgtest!(before_match2_explicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "a medical student", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
Binary file matches (found \"\\u{0}\" byte around offset 9741)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Like before_match1_implicit, but enables -a/--text, so no binary
|
||||
// detection should be performed.
|
||||
rgtest!(before_match2_implicit_text, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&[
|
||||
"--no-mmap", "-n", "--text", "a medical student", "-g", "hay",
|
||||
]);
|
||||
|
||||
let expected = "\
|
||||
hay:236:\"And yet you say he is not a medical student?\"
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
500
tests/data/sherlock-nul.txt
Normal file
500
tests/data/sherlock-nul.txt
Normal file
@@ -0,0 +1,500 @@
|
||||
The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
|
||||
This eBook is for the use of anyone anywhere at no cost and with
|
||||
almost no restrictions whatsoever. You may copy it, give it away or
|
||||
re-use it under the terms of the Project Gutenberg License included
|
||||
with this eBook or online at www.gutenberg.org
|
||||
|
||||
|
||||
Title: A Study In Scarlet
|
||||
|
||||
Author: Arthur Conan Doyle
|
||||
|
||||
Posting Date: July 12, 2008 [EBook #244]
|
||||
Release Date: April, 1995
|
||||
[Last updated: February 17, 2013]
|
||||
|
||||
Language: English
|
||||
|
||||
|
||||
*** START OF THIS PROJECT GUTENBERG EBOOK A STUDY IN SCARLET ***
|
||||
|
||||
|
||||
|
||||
|
||||
Produced by Roger Squires
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
A STUDY IN SCARLET.
|
||||
|
||||
By A. Conan Doyle
|
||||
|
||||
[1]
|
||||
|
||||
|
||||
|
||||
Original Transcriber's Note: This etext is prepared directly
|
||||
from an 1887 edition, and care has been taken to duplicate the
|
||||
original exactly, including typographical and punctuation
|
||||
vagaries.
|
||||
|
||||
Additions to the text include adding the underscore character to
|
||||
indicate italics, and textual end-notes in square braces.
|
||||
|
||||
Project Gutenberg Editor's Note: In reproofing and moving old PG
|
||||
files such as this to the present PG directory system it is the
|
||||
policy to reformat the text to conform to present PG Standards.
|
||||
In this case however, in consideration of the note above of the
|
||||
original transcriber describing his care to try to duplicate the
|
||||
original 1887 edition as to typography and punctuation vagaries,
|
||||
no changes have been made in this ascii text file. However, in
|
||||
the Latin-1 file and this html file, present standards are
|
||||
followed and the several French and Spanish words have been
|
||||
given their proper accents.
|
||||
|
||||
Part II, The Country of the Saints, deals much with the Mormon Church.
|
||||
|
||||
|
||||
|
||||
|
||||
A STUDY IN SCARLET.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PART I.
|
||||
|
||||
(_Being a reprint from the reminiscences of_ JOHN H. WATSON, M.D., _late
|
||||
of the Army Medical Department._) [2]
|
||||
|
||||
|
||||
|
||||
|
||||
CHAPTER I. MR. SHERLOCK HOLMES.
|
||||
|
||||
|
||||
IN the year 1878 I took my degree of Doctor of Medicine of the
|
||||
University of London, and proceeded to Netley to go through the course
|
||||
prescribed for surgeons in the army. Having completed my studies there,
|
||||
I was duly attached to the Fifth Northumberland Fusiliers as Assistant
|
||||
Surgeon. The regiment was stationed in India at the time, and before
|
||||
I could join it, the second Afghan war had broken out. On landing at
|
||||
Bombay, I learned that my corps had advanced through the passes, and
|
||||
was already deep in the enemy's country. I followed, however, with many
|
||||
other officers who were in the same situation as myself, and succeeded
|
||||
in reaching Candahar in safety, where I found my regiment, and at once
|
||||
entered upon my new duties.
|
||||
|
||||
The campaign brought honours and promotion to many, but for me it had
|
||||
nothing but misfortune and disaster. I was removed from my brigade and
|
||||
attached to the Berkshires, with whom I served at the fatal battle of
|
||||
Maiwand. There I was struck on the shoulder by a Jezail bullet, which
|
||||
shattered the bone and grazed the subclavian artery. I should have
|
||||
fallen into the hands of the murderous Ghazis had it not been for the
|
||||
devotion and courage shown by Murray, my orderly, who threw me across a
|
||||
pack-horse, and succeeded in bringing me safely to the British lines.
|
||||
|
||||
Worn with pain, and weak from the prolonged hardships which I had
|
||||
undergone, I was removed, with a great train of wounded sufferers, to
|
||||
the base hospital at Peshawar. Here I rallied, and had already improved
|
||||
so far as to be able to walk about the wards, and even to bask a little
|
||||
upon the verandah, when I was struck down by enteric fever, that curse
|
||||
of our Indian possessions. For months my life was despaired of, and
|
||||
when at last I came to myself and became convalescent, I was so weak and
|
||||
emaciated that a medical board determined that not a day should be lost
|
||||
in sending me back to England. I was dispatched, accordingly, in the
|
||||
troopship "Orontes," and landed a month later on Portsmouth jetty, with
|
||||
my health irretrievably ruined, but with permission from a paternal
|
||||
government to spend the next nine months in attempting to improve it.
|
||||
|
||||
I had neither kith nor kin in England, and was therefore as free as
|
||||
air--or as free as an income of eleven shillings and sixpence a day will
|
||||
permit a man to be. Under such circumstances, I naturally gravitated to
|
||||
London, that great cesspool into which all the loungers and idlers of
|
||||
the Empire are irresistibly drained. There I stayed for some time at
|
||||
a private hotel in the Strand, leading a comfortless, meaningless
|
||||
existence, and spending such money as I had, considerably more freely
|
||||
than I ought. So alarming did the state of my finances become, that
|
||||
I soon realized that I must either leave the metropolis and rusticate
|
||||
somewhere in the country, or that I must make a complete alteration in
|
||||
my style of living. Choosing the latter alternative, I began by making
|
||||
up my mind to leave the hotel, and to take up my quarters in some less
|
||||
pretentious and less expensive domicile.
|
||||
|
||||
On the very day that I had come to this conclusion, I was standing at
|
||||
the Criterion Bar, when some one tapped me on the shoulder, and turning
|
||||
round I recognized young Stamford, who had been a dresser under me at
|
||||
Barts. The sight of a friendly face in the great wilderness of London is
|
||||
a pleasant thing indeed to a lonely man. In old days Stamford had never
|
||||
been a particular crony of mine, but now I hailed him with enthusiasm,
|
||||
and he, in his turn, appeared to be delighted to see me. In the
|
||||
exuberance of my joy, I asked him to lunch with me at the Holborn, and
|
||||
we started off together in a hansom.
|
||||
|
||||
"Whatever have you been doing with yourself, Watson?" he asked in
|
||||
undisguised wonder, as we rattled through the crowded London streets.
|
||||
"You are as thin as a lath and as brown as a nut."
|
||||
|
||||
I gave him a short sketch of my adventures, and had hardly concluded it
|
||||
by the time that we reached our destination.
|
||||
|
||||
"Poor devil!" he said, commiseratingly, after he had listened to my
|
||||
misfortunes. "What are you up to now?"
|
||||
|
||||
"Looking for lodgings." [3] I answered. "Trying to solve the problem
|
||||
as to whether it is possible to get comfortable rooms at a reasonable
|
||||
price."
|
||||
|
||||
"That's a strange thing," remarked my companion; "you are the second man
|
||||
to-day that has used that expression to me."
|
||||
|
||||
"And who was the first?" I asked.
|
||||
|
||||
"A fellow who is working at the chemical laboratory up at the hospital.
|
||||
He was bemoaning himself this morning because he could not get someone
|
||||
to go halves with him in some nice rooms which he had found, and which
|
||||
were too much for his purse."
|
||||
|
||||
"By Jove!" I cried, "if he really wants someone to share the rooms and
|
||||
the expense, I am the very man for him. I should prefer having a partner
|
||||
to being alone."
|
||||
|
||||
Young Stamford looked rather strangely at me over his wine-glass. "You
|
||||
don't know Sherlock Holmes yet," he said; "perhaps you would not care
|
||||
for him as a constant companion."
|
||||
|
||||
"Why, what is there against him?"
|
||||
|
||||
"Oh, I didn't say there was anything against him. He is a little queer
|
||||
in his ideas--an enthusiast in some branches of science. As far as I
|
||||
know he is a decent fellow enough."
|
||||
|
||||
"A medical student, I suppose?" said I.
|
||||
|
||||
"No--I have no idea what he intends to go in for. I believe he is well
|
||||
up in anatomy, and he is a first-class chemist; but, as far as I know,
|
||||
he has never taken out any systematic medical classes. His studies are
|
||||
very desultory and eccentric, but he has amassed a lot of out-of-the way
|
||||
knowledge which would astonish his professors."
|
||||
|
||||
"Did you never ask him what he was going in for?" I asked.
|
||||
|
||||
"No; he is not a man that it is easy to draw out, though he can be
|
||||
communicative enough when the fancy seizes him."
|
||||
|
||||
"I should like to meet him," I said. "If I am to lodge with anyone, I
|
||||
should prefer a man of studious and quiet habits. I am not strong
|
||||
enough yet to stand much noise or excitement. I had enough of both in
|
||||
Afghanistan to last me for the remainder of my natural existence. How
|
||||
could I meet this friend of yours?"
|
||||
|
||||
"He is sure to be at the laboratory," returned my companion. "He either
|
||||
avoids the place for weeks, or else he works there from morning to
|
||||
night. If you like, we shall drive round together after luncheon."
|
||||
|
||||
"Certainly," I answered, and the conversation drifted away into other
|
||||
channels.
|
||||
|
||||
As we made our way to the hospital after leaving the Holborn, Stamford
|
||||
gave me a few more particulars about the gentleman whom I proposed to
|
||||
take as a fellow-lodger.
|
||||
|
||||
"You mustn't blame me if you don't get on with him," he said; "I know
|
||||
nothing more of him than I have learned from meeting him occasionally in
|
||||
the laboratory. You proposed this arrangement, so you must not hold me
|
||||
responsible."
|
||||
|
||||
"If we don't get on it will be easy to part company," I answered. "It
|
||||
seems to me, Stamford," I added, looking hard at my companion, "that you
|
||||
have some reason for washing your hands of the matter. Is this fellow's
|
||||
temper so formidable, or what is it? Don't be mealy-mouthed about it."
|
||||
|
||||
"It is not easy to express the inexpressible," he answered with a laugh.
|
||||
"Holmes is a little too scientific for my tastes--it approaches to
|
||||
cold-bloodedness. I could imagine his giving a friend a little pinch of
|
||||
the latest vegetable alkaloid, not out of malevolence, you understand,
|
||||
but simply out of a spirit of inquiry in order to have an accurate idea
|
||||
of the effects. To do him justice, I think that he would take it himself
|
||||
with the same readiness. He appears to have a passion for definite and
|
||||
exact knowledge."
|
||||
|
||||
"Very right too."
|
||||
|
||||
"Yes, but it may be pushed to excess. When it comes to beating the
|
||||
subjects in the dissecting-rooms with a stick, it is certainly taking
|
||||
rather a bizarre shape."
|
||||
|
||||
"Beating the subjects!"
|
||||
|
||||
"Yes, to verify how far bruises may be produced after death. I saw him
|
||||
at it with my own eyes."
|
||||
|
||||
"And yet you say he is not a medical student?"
|
||||
abcdef |