Topics


Blogs


Forums


Samples


Media


Labs


Resources

 




DevCentral > Weblogs > Joe Pruitt - A Software Architect's take on Network Security
 Unix To PowerShell - Dirname
posted on Thursday, April 30, 2009 10:07 AM

PowerShell_unix PowerShell is definitely gaining momentum in the windows scripting world but I still hear folks wanting to rely on Unix based tools to get their job done.  In this series of posts I’m going to look at converting some of the more popular Unix based tools to PowerShell.

dirname

The Unix “dirname” command will strip any non-directory suffix from a file name.  Given a NAME, dirname will print the name with it’s trailing “/” component removed.  If NAME contains no “/” characters, it will output “.” (meaning the current directory).

Since PowerShell is run on windows, my script will substitute the forward slash with the windows backslash directory specifier.  This can be overridden by changing the $script:SEPARATOR variable in the script.

At first I thought that a simple [System.IO.FileInfo] cast would do the trick, but it doesn’t behave the same if you give a non-fully qualified directory (ie foo.txt, .\foo.txt, ..\foo.txt).  For non-qualified paths, the FileInfo class will default to the $env:home directory (ie. C:\Users\Joe) which is not the behavior of the Unix dirname command.  I also looked at using the builtin Get-ChildItem cmdlet but that will not work for paths that are not present on the file system and this command should work for any string passed in.

I’ve included a few unit test cases with the Do-DirNameUnitTests function to show the inputs and expected outputs.

The script takes only one argument, the name to process and returns the dirname to the output.

   1: #----------------------------------------------------------------
   2: # Dirname.ps1
   3: #----------------------------------------------------------------
   4: param
   5: (
   6:   [string]$name = $null
   7: );
   8:  
   9: $script:SEPARATOR = "\";
  10:  
  11: #----------------------------------------------------------------
  12: # function Do-Dirname
  13: #----------------------------------------------------------------
  14: function Do-Dirname()
  15: {
  16:   param
  17:   (
  18:     [string]$name = $null
  19:   );
  20:   
  21:   $dirname = ".";
  22:   
  23:   if ( $name  )
  24:   {
  25:     if ( $name -ne $script:SEPARATOR )
  26:     {
  27:       $start_index = $name.Length-1;
  28:       
  29:       # for paths like \foo\bar\, bypass the trailing separator
  30:       if ( ($name.Length -gt 1) -and $name.EndsWith($script:SEPARATOR) )
  31:       {
  32:         $start_index--;
  33:       }
  34:       
  35:       # Reverse Iterate through the string until we find a path separator
  36:       for($dirlen=$start_index; $dirlen -ge 0; $dirlen--)
  37:       {
  38:         if ( $name[$dirlen] -eq $script:SEPARATOR )
  39:         {
  40:           break;
  41:         }
  42:       }
  43:       if ( $dirlen -eq -1 )
  44:       {
  45:         # No path separators found, default to the current directory
  46:         $dirname = "."
  47:       }
  48:       else
  49:       {
  50:         $dirname = $name.Substring(0, $dirlen);
  51:         
  52:         #sanity checks
  53:         if ( $dirname.Length -eq 0 ) { $dirname = $script:SEPARATOR; }
  54:         if ( $dirname -like "[a-zA-Z]:" ) { $dirname += $script:SEPARATOR; }
  55:       }
  56:     }
  57:   }
  58:   $dirname;
  59: }
  60:  
  61: #----------------------------------------------------------------
  62: # function Do-DirnameUnitTests
  63: #----------------------------------------------------------------
  64: function Do-DirnameUnitTests()
  65: {
  66:   $tests = @( 
  67:     @("foo", "."),
  68:     @("foo\", "."),
  69:     @("", "."),
  70:     @("\foo", "\"),
  71:     @("\foo\", "\"),
  72:     @("\foo\bar", "\foo"),
  73:     @("\foo\bar\", "\foo"),
  74:     @("c:\foo", "c:\"),
  75:     @("c:\foo\bar", "c:\foo"),
  76:     @("c:\foo\bar\", "c:\foo")
  77:   );
  78:   
  79:   $success = "PASS";
  80:   
  81:   "  {0,-15}      {1,-15}       {2,-15}    {3}" -f ("Test", "Expected", "Found", "Pass");
  82:   foreach ($test in $tests)
  83:   {
  84:     $result = Do-Dirname $test[0];
  85:     $status = $result -eq $test[1];
  86:     "(""{0,-15}"" -> ""{1,-15}"") -> ""{2,-15}"" : {3}" -f ($test[0], $test[1], $result, $status);
  87:     #Write-Host "TEST: (""$($test[0])"" -> ""$($test[1])"") -> ""$result"" : $status";
  88:     if ( ! $status ) { $success = "FAIL"; }
  89:   }
  90:   ""
  91:   "RESULT: $success";
  92:  
  93: }
  94:  
  95: Do-Dirname -name $name;

You can get the full script here: Dirname.ps1



 
      

Feedback


4/30/2009 11:05 AM
Gravatar For full paths, Split-Path will do the same.

Like many of these commands, PowerShell has its own version. Though usually it doesn't support quite the same features because they don't often make sense in a PowerShell environment.
Jason Archer

4/30/2009 11:26 AM
Gravatar Unfortunately, Split-Path does not cover all the use cases for expected dirname output. Mainly this is around the expected output for current directories defaulting to ".".

input, dirname, Split-Path
"foo", ".", ""
"foo\", ".", ""
"", ".", Error

I guess I could use Split-Path and look for edge cases and correct it's results for the current directory exceptions.

Thanks for the tip

-Joe

Joe Pruitt

4/30/2009 11:33 AM
Gravatar BTW, I just compared my solution with a combination Split-Path/exception function and the Split-Path was a "little" bit slower. By little I mean in the 1-5 ms. range. Not a huge optimization by going my route, but I would have expected Split-Path to be much faster.

-Joe
Joe Pruitt

7/27/2009 6:19 PM
Gravatar Thanks, its just what I wanted.
OZNT

7/29/2009 9:24 PM
Gravatar I've been looking for this tutorial since last 3 months ago, my friend refered your blog, and I got what I need. Thanks
Jean
 Leave Feedback
Title  
Name  
Email
Url
Comments   
Please add 3 and 4 and type the answer here: