loc – my own simple .NET version of the unix ‘which’ facility

As is often the case, I wrote a small program to do something useful for myself.  I have a (Win32) copy of the unix “which” utility, but I wanted something that would simultaneously search my path, include, and lib environment variables.  What I came up with was loc, and it is a pretty simple, but pretty cool, example of what you can do with the .NET runtime.

I'm including both the old and new syntax versions of loc, as locOS.cpp and locNS.cpp.  (Take a guess which is old syntax and which is new.)  Really, the conversion from old to new in this case was really painless.  I just had to fix the array syntax, and use handles instead of gc pointers.  Enough small talk, let's get on with the code!  As is my preference, I'll use the new syntax to describe the code.  (Scroll to the very bottom of this post to get the code.)

using namespace System;

using namespace System::IO;
using namespace stdcli::language;

Some fairly straightforward using namespace declarations.  I need System for general things (like String), System::IO for some of the file IO stuff I'm going to use, and stdcli::language for the CLI array.

#define STRARR(x) (((System::String^)x)->ToCharArray())

Here's an interesting macro.  As is the case with most macros, it isn't necessary, its just a time-saving measure.  Here's the rub: some of the .NET runtime functions (like String::Split) take, as a parameter, a managed array of System::Chars (wchar_ts).  Sometimes, you want to pass a string literal to these functions, and we don't provide a standard conversion from a string literal to a managed array of System::Chars.  However, we do provide a conversion to a String^, and the System::String object has a member function, String::ToCharArray, that will give me that elusive managed array.  So, I create an unnamed temporary String object out of x (which is my string literal) and call ToCharArray.  Though I only happen to use it once in this program, I could use it a whole lot more. (And I have, in other code.)

int envSearch(String^ file, String^ env);


int main(int argc, char**argv){


    Console::WriteLine("usage: loc <file>");

    return 1;

A couple of things here.  First, a pretty straightforward global function prototype.  Second, my main function, with the standard argument list.  Then, a simple check to make sure that the user specified one (and only one) file name.  Remember, the name of the program always counts as a command-line argument - that's why I check for two arguments.  I print the usage for my program, and return 1 if the user didn't specify a file name.

  String ^f = argv[1];

  int found=0;


  //we want to search a number of ENVs - path, include, lib

  found+=envSearch(f, "path");

  found+=envSearch(f, "include");

  found+=envSearch(f, "lib");


  if(found) return 0;

  else return 1;

Here's the rest of my main function.  I copy the filename the user gave me into a System::String, and create an int, found, that is going to remember if I found the file I'm looking for across several calls of envSearch.  Everything else is pretty straightforward.  Let's look at the real guts of the program, contained in the envSearch function.

int envSearch(String^ file, String^ env){

  int found=0;

  array<String^>^ dirs = (Environment::GetEnvironmentVariable(env))->Split(STRARR(";"));

  for(int i=0; i<dirs->Length; i++){

    String^ tmpFn = String::Concat(dirs[i], "\\", file);

    if (File::Exists(tmpFn)){

      Console::WriteLine("{0} ({1})", tmpFn, env);




  return found;


Looks like founds are running cheap today.  This one has the same role, just remembering if I've actually found the file I'm looking for or not, so I can give the user a meaningful return code from my program. 


I use a static method of the .NET runtime, Environment::GetEnvironmentVariable to, you guessed it, get the contents of an environment variable.  Unfortunately, it comes across as a single System::String, semi-colon delimited, and I'd prefer a CLI array of Strings.  So, I use the String::Split function (and my handy STRARR macro) to split that string, on-the-fly, into an array of Strings.


Then, I loop through that array.  I create a temporary filename by concatenating the directory from the environment variable with a backslash (escaped with a second one), and the name of the file I'm looking for.  Then, I use the .NET method File::Exists to see if the file I want exists at the location I created.  If it does, I output it, along with the environment variable the path was contained in (in parentheses), and increment found.  That's it!

Getting the files!  Of course, what we're all interested in, is where to get the files.  You can get them here.  There are two files in this zip:

  • locOS.cpp - the loc program source, in Managed Extensions (Everett) syntax.

  • locNS.cpp - the loc program source, in Whidbey C++ syntax.

Comments (6)
  1. Anonymous says:

    Very helpful little app, thanks.

    Since you’re already using System.IO wouldn’t you want to do the directory and filename concatenation using Path.Combine rather than String.Concat? It seems to be "smart" about dealing with missing, or already present, trailing backslashes, or converting from forward slashes etc.

  2. Anonymous says:

    Even if you wanted to use string smashing, I’m always partial to String->Format 🙂

    ‘Course, rather than re-inventing the Wheel*, I’d probably recommend the use of the Win32 API SearchPath (which is chock full of heady goodness):

    DWORD SearchPath(

    LPCTSTR lpPath,

    LPCTSTR lpFileName,

    LPCTSTR lpExtension,

    DWORD nBufferLength,

    LPTSTR lpBuffer,

    LPTSTR* lpFilePart


    Sorry, but I just couldn’t resist. 🙂

    * I’ve been having a discussion with my one of clients on the merits of "Anything you didn’t write runs at in zero time". Apparently some of the less experienced folk beleive that anything you don’t write yourself doesn’t have a speed or performance impact. I just stood there dumbfounded for a bit. I guess time to get another Extra-shot-Breve-Latte :p


  3. Anonymous says:

    You do realize that this is a three-line batch file.

    @for %%i in (%1) do @if NOT "%%~$PATH:i"=="" echo %%~$PATH:i

    @for %%i in (%1) do @if NOT "%%~$LIB:i"=="" echo %%~$LIB:i

    @for %%i in (%1) do @if NOT "%%~$INCLUDE:i"=="" echo %%~$INCLUDE:i

  4. Anonymous says:

    Yes, yes. There are more efficient and easier ways of doing what I did (SearchPath, or a batch file) – but those would have made for far less interesting quickie code samples.

    Path.Combine is pretty nice, though. I would have used that for readability’s sake – some people use trailing backslashes in their path ENVs. The fact that I didn’t doesn’t negatively affect the outcome, as windows ignores multiple backslashes.

    The one thing I really wish my program did (that it doesn’t do right now) is wildcards.

  5. Anonymous says:

    As is often the case, I wrote a small program to do something useful for myself. I have a (Win32) copy of the unix &amp;#8220;which&amp;#8221; utility, but I wanted something that would simultaneously search my path, include, and lib environment variables.

Comments are closed.

Skip to main content