Friday, November 4, 2016

Unix Find Command -- Where'd I Put My Keys

I'll be the first to admit, I'm a bit of a dinosaur with respect to usage of IDEs.  While I'm well versed in Eclipse and other IDE's, I still find myself more efficient when using a multi-window environment, specifically 3-4 editor sessions along side debugging and execution windows.  In my native habitat, you'll find me running Linux with 3-4 terminals each with specific source files open (I'm not a tabbed session window man myself), one terminal tail'ing a redirected output file, and one where I run the application redirecting to the output file.  Certainly this depends on what I'm working on, but that's a pretty reasonable representation of how I roll.

So what?  Well, because I don't use IDEs I don't have the luxury of 'Open Declaration' style features isn't my jam.  In the absence of IntelliSense style functionality, I rely on the Unix find command to locate my next source code victim.  Despite that, I've had a poor understanding on how to properly use the find command, especially for more sophisticated commands.

For years, I had zero understanding of the ACTIONS options.  Casual observation of co-workers over the years seems to imply that many don't, resulting in clumsy, half-azzed usage of it.  Few understand the ramifications, specifically whenever you misuse the find command, it causes a puppy to cry.  So take heed my tech warrior and prevent the crying of puppies.


So let's set the stage for some find commands.
$ echo "hello" > hello.txt
$ echo "hello world" > helloworld.txt
$ mkdir subdir

$ echo "hello\nworld" > subdir/helloworld2.txt


So as an example of a clumsy misuse of find, for years if I wanted to display the contents of all these text files I'd do something like this:
$ more `find . -name "*.txt"`
::::::::::::::
./subdir/helloworld2.txt
::::::::::::::
hello\nworld
::::::::::::::
./helloworld.txt
::::::::::::::
hello world
::::::::::::::
./hello.txt
::::::::::::::
hello


Similarly, if I wanted to edit each of the files the clumsy command would take the form of:
$ vi `find . -name "*.txt"`


Ugh.

A key contributor to years of misuse is the God-awful syntax for the action commands, a more confusing syntax I've never met.  Albeit, incredibly powerful, but seriously.....WTF?

The first part of the command line is reasonable, straight-forward and typical:
$ find [starting-point] [expression]

$ find . -name "*.txt" will search the current directory and subdirectories for file names that match '*.txt'; straight-forward.

Say you want to display the contents of each of the text files like above, proper form is:
$ find . -name "*.txt" -exec more {} \;

Seriously; '{}' & '\;', what fresh hell is this?

I think a turning point for me was the day that I quit quibbling on how ridiculous the syntax is and simply accepted it; Admission, Surrender & Acceptance.

The '-exec' optional parameter takes the general form of '-exec [command] {} \;'.  Above we want to run more on each of the files, the list of files is represented by the brackets '{}', the delimited ';' implies it's the end of the command parameters.

Conceptually, the find . -name "*.txt" -exec more {} \; equates to a sequence of commands:
$ find . -name "*.txt"
./subdir/helloworld2.txt
./helloworld.txt
./hello.txt


Followed by:
$ more ./subdir/helloworld2.txt ./helloworld.txt ./hello.txt

Similarly, editing each txt file can be done by:
$ find . -name "*.txt" -exec vi {} \;

No puppies cried in the last two commands.

You're also able to chain commands together.  Say for instance you're interested in finding files that contain the word 'hello' in them, the following command will suit the bill:
$ find . -name "*.txt" -exec grep -l 'hello' {} \;

What if however you want to find files that contain 'hello' and 'world' and not necessarily on the same line, you accomplish that by chaining grep commands as follows:
$ find . -name "*.txt" -exec grep -q 'hello' {} \; - exec grep -l 'world' {} \;
./subdir/helloworld2.txt
./helloworld.txt



No comments:

Post a Comment