12
$\begingroup$

I have so many *.nb files in a folder. In one of them I have written a phrase, unfortunately I cannot remember that file. I am searching about it and the file in which I have written. Is there any way to search to Find a proposed phrase or word? I have used Find but it just work in an opened file not all files in the folder!

$\endgroup$
10
  • 3
    $\begingroup$ See the examples in CreateSearchIndex -- they exactly for the use case you have. $\endgroup$ May 17, 2016 at 12:59
  • 3
    $\begingroup$ On OS X Mathematica integrates with Spotlight (the OS's search system). It's possible to simply search for the phrase with the file manager. I don't know if this also work on Windows (there's a good chance it does). $\endgroup$
    – Szabolcs
    May 17, 2016 at 13:17
  • $\begingroup$ @Anton Does it explicitly support notebooks or are you relying on the fact that notebooks are just big semi-human-readable expressions anyway? Just curious. $\endgroup$
    – Szabolcs
    May 17, 2016 at 13:19
  • $\begingroup$ @Szabolcs the latter. :) I have to say at some point in order to do expression search within notebooks I experimented with converting them all to text (or PDF's) and then making the search. $\endgroup$ May 17, 2016 at 13:26
  • 2
    $\begingroup$ in the olden days one used to grep pattern *.nb in the terminal, in case you are in a unix machine. $\endgroup$ May 17, 2016 at 13:53

5 Answers 5

13
$\begingroup$

Here is a way to search from within mathematica:

notebooks = Quiet@FileNames["*.nb", NotebookDirectory[], 2];
Monitor[Select[
   Table[{nb, 
     StringJoin@Select[ StringSplit[Import[nb, "Plaintext"], "\n"] ,
        ((If[#, Print["match on:", nb]]; #) &@
             StringMatchQ[#, "*NIntegrate*"]) &, 5]},
                {nb,notebooks}], #[[2]] != "" &], {nb}] // Grid[#, Alignment -> {Left, Top}, Dividers -> All] &

This is painfully slow, but it does just search and show only the plain text of the notebook.

$\endgroup$
4
  • $\begingroup$ Neat (+1). When I tried your code the results look better with Grid than with MatrixForm so I changed your answer... $\endgroup$ May 17, 2016 at 21:19
  • $\begingroup$ Unfortunately NBImport.exe (which actually imports NB files under the hood) returns $Failed when trying to import a NB file with non-ASCII file path. $\endgroup$ May 18, 2016 at 3:19
  • 1
    $\begingroup$ @AlexeyPopkov good point. worth noting also the failure can be "silent" in the above code (It returns the string $Failed but does not actually generate an error ). I ran the above on my entire system and only got faied on 2 notebooks that also report errors simply opening in the front end (But I don't use non-ascii paths) $\endgroup$
    – george2079
    May 19, 2016 at 16:27
  • $\begingroup$ An alternative to Import[nb, "Plaintext"] is new in 10.1 NotebookImport[nb, _ -> "Text"]. $\endgroup$ Mar 21, 2017 at 20:26
8
$\begingroup$

A couple of additional solutions. The first, with FindList, is probably the simplest and quickest.

Using FindList

searchDir = "<NB dir>";

fnames = FileNames["*.nb", searchDir, 2];
Length@fnames

sres = {#, FindList[#, {"curve"}, WordSearch -> False]} & /@ fnames;
sres = Select[sres, Length[#[[2]]] > 0 &];
Grid[sres, Dividers -> All, Alignment -> {Left, Top}]

See the options of FindList.

Using CreateSearchIndex

searchDir = "<NB dir>";

index = CreateSearchIndex[searchDir]

sobjs = TextSearch[index, {"curve", "regression"}]

sres = MapThread[{#1, 
     StringCases[#2, "curve" ~~ (Except["\n"] ...) ~~ "regression", 
      IgnoreCase -> True]} &,
   {Through[sobjs["Location"]], Through[sobjs["Plaintext"]]}];

Grid[sres, Dividers -> All, Alignment -> {Left, Top}]

See the signature of TextSearch -- it allows complicated "and", "or", "except" searches.

This solution seems to be fairly slow.

$\endgroup$
7
$\begingroup$

Note: the following method isn't robust. See this answer of mine for a robust solution.


Here is an approach which does not rely on the NBImport.exe (which actually performs importing of the NB files as "Plaintext" under the hood) and performs all the operations in the Kernel only. Currently NBImport.exe contains a bug due to which it returns $Failed when have to import a NB file with non-ASCII file path.

The weak side of the following method is that it relies upon the ability of MakeExpression to convert a low-level Notebook expression into the high-level DocumentNotebook what it doesn't always able to do even for correct NB files (and this ability is not guaranteed by the developers in general). This conversion is necessary because ToString doesn't accept raw boxes as the low-level representation of a WL expression (even wrapping the raw boxes by RawBoxes is simply ignored).

The simple function presented below currently fails in many situations but demonstrates the idea.

Here is a function which Gets the contents of a NB file as Notebook expression, then extracts all the Cells as the actual WL expressions wrapped by HoldComplete, converts them into strings and checks whether they contain specified string pattern or not:

findInNBFile[NBFilePath_String, stringPattern_] := 
  Module[{expr = MakeExpression[Get[NBFilePath], StandardForm], cellExprPos, foundPos},
   cellExprPos = Replace[Position[expr, ExpressionCell | TextCell], 0 -> 1, {2}];
   foundPos = 
    Flatten@Position[
      StringFreeQ[
       StringTake[ToString /@ Extract[expr, cellExprPos, HoldComplete], {14, -2}], 
       stringPattern], False];
   If[foundPos =!= {}, 
    Grid[Join[{{Row[{"Found \"", stringPattern, "\" in file \"", NBFilePath, "\""}], 
        SpanFromLeft}, {"Cell #", "The Cell"}}, 
      Transpose[{foundPos, Extract[expr, Most /@ cellExprPos[[foundPos]], HoldForm]}]], 
     Frame -> All], {NBFilePath, False}]
   ];

It can be used as follows:

findInNBFile["ExampleData/document.nb", "abcde"]

output

$\endgroup$
4
$\begingroup$

Here is a solution written in the spirit of the answer by george2079 but NOT relying upon the buggy NBImport.exe executable. Instead it uses FrontEnd for converting Notebooks into plain text using the findings described here:

findInNBFile::cntconv = "Failed to extract plain text from `1`";
findInNBFile[NBFilePath_String, stringPattern_, resPerFile_Integer: 5] := 
 Module[{NB, NBText, NBlines, matches},
  NB = Get[NBFilePath];
  NB = Notebook[First@NB, 
    Join[FilterRules[List @@ Rest[NB], 
      Except[DynamicUpdating | NotebookDynamicExpression]], {DynamicUpdating -> False, 
       NotebookDynamicExpression -> Null}]];
  NBText = FrontEndExecute[FrontEnd`ExportPacket[NB, "PlainText"]];
  If[TrueQ[Head[NBText] === List && Head[First@NBText] === String],
   NBlines = StringSplit[First@NBText, "\n"];
   matches = Flatten@Position[StringMatchQ[NBlines, stringPattern], True];
   If[matches =!= {}, Print[Length[matches], " matches on: ", NBFilePath]];
   {NBFilePath, Length[matches], 
    StringJoin@
     If[Length[matches] > resPerFile, NBlines[[Take[matches, resPerFile]]], 
      NBlines[[matches]]]},
   Message[findInNBFile::cntconv, NBFilePath];
   {NBFilePath, $Failed,}]
  ]

For example, let us find all Tutorials containing the word NIntegrate. Listing of the all Tutorials:

notebooks = 
  FileNames["*.nb", 
   FileNameJoin[{$InstallationDirectory, "Documentation", "English", "System",
      "Tutorials"}]];
Length[notebooks]
Total[FileByteCount /@ notebooks]/1024.^2
715

215.841

Searching (I won't post here the output except to the timings):

startTime = AbsoluteTime[];

Monitor[Select[Table[findInNBFile[nb, "*NIntegrate*"], {nb, notebooks}], 
   TrueQ[#[[2]] > 0] &], {nb}] // 
 Grid[#, Alignment -> {Left, Top}, Dividers -> All] &

DateString[AbsoluteTime[] - startTime, {"Hour", ":", "Minute", ":", "Second"}]

"00:08:11"

We have successfully processed 715 Notebooks of total size 215 Mb in 8 minutes. Not so bad...

Note that the above function extracts text also from textual elements inside of output, even from the saved (cached) versions of Dynamic expressions. For example in the "Views.nb" Tutorial Notebook the word "Afghanistan" is present only inside of the cached Dynamic output for the second example under the "SlideView" section. Let us try to find it:

findInNBFile[
 FileNameJoin[{$InstallationDirectory, "Documentation", "English", "System", 
   "Tutorials", "Views.nb"}], "*Afghanistan*"]

2 matches on: C:\Program Files\Wolfram Research\Mathematica\10.4\Documentation\English\System\Tutorials\Views.nb

{"C:\\Program Files\\Wolfram Research\\Mathematica\\10.4\\Documentation\\English\\System\\Tutorials\\Views.nb",
2,
"BoxData[Afghanistan]
BoxData[Afghanistan]
"}

Found!

$\endgroup$
1
$\begingroup$

A Unix style text search works best for me. The following search is a timed search over all the .nb files on a 2017 iMac 4GHz Quad Core i7 with 32 GB. The timed find / grep command is:

find . -name \*.nb -exec grep -iH nintegrate {} \; 71.11s user 3.30s system 97% cpu 1:16.48 total

1:16 wall clock time. The files searched were in a multiple directory structure with 2.9 GBytes of files over 1949 .nb files. The directory tree had 1974 subdirectories.

The Mathematica code above that starts with "notebooks = Quiet" required 3:52 wall clock time (mm:ss) or about 3 times slower.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.