Topics


Blogs


Forums


Samples


Media


Labs


Resources

 




DevCentral > Weblogs > Joe Pruitt - A Software Architect's take on Network Security
 Unix To PowerShell - Find
posted on Wednesday, April 29, 2009 11:44 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.

find

The Unix “find” command searches through one or more directory trees of a file system, locating files based on some user specific criteria.  By default, find returns all files below the current working directory.  It also allows you to perform an action to be taken on each matched file.

In my PowerShell script I have only included the “file location” functions and will leave adding the action feature as an exercise for the reader.

This script starts out by calling the Do-Find function which basically just stuffs all the command line arguments into a hash table and calls the function Find-InDirectory with the given start location.  Find-InDirectory will get all child items in the specified location and then iterate through that list.  If the child item is a directory, the current depth is incremented, a recursive call to the Find-InDirectory is made for the child directory, and then the current depth is decremented.  If the child item is a file, the Get-IsMatch function is called to determine whether the file matches the specified criteria from the command line arguments.

The Unix parameters map to the following in my PowerShell script:

Unix PowerShell Description
path -start The directory to start the search from (default = “.”).
-maxdepth -maxdepth Descend at most “n” levels of directories below start path.
-mindepth -mindepth Do not apply tests at levels less than “n” levels below start path.
-amin -amin Only process files that were accessed more recently than “n” minutes ago.
-atime -atime Only process files that were accessed “n”*24 hours ago.
-empty -empty Only process empty files or directories.
-name -name Only process files where file name matches “name” pattern.
-path -path Only process files where the full path matches the “path” pattern.
-exec -exec Execute expression on each match.  “{}” is replaced by file name in expr.

 

   1: #----------------------------------------------------------------
   2: # Find.ps1
   3: #----------------------------------------------------------------
   4: param
   5: (
   6:   [string]$start = ".", # directory to start search in
   7:   [int]$maxdepth = -1, # decend at most "n" levels below starting line
   8:   [int]$mindepth = -1, # don't process levels less than mindepth
   9:   [int]$amin = -1, # file was last accessed "n" minutes ago
  10:   [int]$atime = -1, # file was last accessed "n"*24 hours ago
  11:   [bool]$empty = $false, # file is empty and is a file or directory.
  12:   [string]$name = "", # base of file name matches pattern.
  13:   [string]$path = "", # filename/path matches pattern.
  14:   [string]$exec = "" # Execute command on the matched files
  15: );
  16:  
  17: $script:CURRENT_DEPTH = 0;
  18:  
  19: #----------------------------------------------------------------
  20: # function Get-IsMatch
  21: #----------------------------------------------------------------
  22: function Get-IsMatch()
  23: {
  24:   param
  25:   (
  26:     $info = $null,
  27:     $context = $null
  28:   );
  29:   [bool]$bIsMatch = $true;
  30:   
  31:   if ( Is-InDepthRange -context $context )
  32:   {
  33:     if ( $context["name"].Length -gt 0 )
  34:     {
  35:       $bIsMatch = $info.Name -like $context["name"];
  36:     }
  37:     elseif ( $context["path"].Length -gt 0 )
  38:     {
  39:       $bIsMatch = $info.FullName -like $context["path"];
  40:     }
  41:     
  42:     if ( $bIsMatch -and ($context["amin"] -ne -1) )
  43:     {
  44:       $ts = [DateTime]::Now - $info.LastAccessTime;
  45:       if ( $ts.TotalMinutes -gt $context["amin"] )
  46:       {
  47:         $bIsMatch = $false;
  48:       }
  49:     }
  50:  
  51:     if ( $bIsMatch -and ($context["atime"] -ne -1) )
  52:     {
  53:       $ts = [DateTime]::Now - $info.LastAccessTime;
  54:       if ( $ts.TotalHours -gt (24 * $context["atime"]) )
  55:       {
  56:         $bIsMatch = $false;
  57:       }
  58:     }
  59:  
  60:     $bIsEmpty = $false;
  61:     if ( $info -is [System.IO.FileInfo] )
  62:     {
  63:       $bIsEmpty = $info.Length -eq 0;
  64:     }
  65:     elseif ( $info -is [System.IO.DirectoryInfo] )
  66:     {
  67:       $bIsEmpty = ( $info.GetFiles().Length -eq 0 );
  68:     }
  69:     if ( $context["empty"] ) { $bIsMatch = $bIsEmpty; }
  70:   }
  71:   else
  72:   {
  73:     $bIsMatch = $false;
  74:   }
  75:   
  76:   $bIsMatch;
  77: }
  78:  
  79: #----------------------------------------------------------------
  80: # function Is-InDepthRange
  81: #----------------------------------------------------------------
  82: function Is-InDepthRange()
  83: {
  84:   param($context = $null);
  85:   
  86:   $bInRange = $true;
  87:   if ( $context )
  88:   {
  89:     if ( -1 -ne $context["mindepth"] )
  90:     {
  91:       if ( $script:CURRENT_DEPTH -lt $context["mindepth"] )
  92:       {
  93:         $bInRange = $false;
  94:       }
  95:     }
  96:     if ( -1 -ne $context["maxdepth"] )
  97:     {
  98:       if ( $script:CURRENT_DEPTH -gt $context["maxdepth"] )
  99:       {
 100:         $bInRange = $false;
 101:       }
 102:     }
 103:   }
 104:   
 105:   $bInRange;
 106: }
 107:  
 108: #----------------------------------------------------------------
 109: # function Do-FindAction
 110: #----------------------------------------------------------------
 111: function Do-FindAction()
 112: {
 113:   param
 114:   (
 115:     $info = $null,
 116:     $context = $null
 117:   );
 118:   
 119:   if ( $context["exec"].Length -gt 0 )
 120:   {
 121:     $cmd = $context["exec"];
 122:     $expr = $cmd.Replace("{}", "`$info");
 123:     Invoke-Expression $expr;
 124:   }
 125:   else
 126:   {
 127:     $info.FullName;
 128:   }
 129: }
 130:  
 131: #----------------------------------------------------------------
 132: # function Find-InDirectory
 133: #----------------------------------------------------------------
 134: function Find-InDirectory()
 135: {
 136:   param
 137:   (
 138:     [string]$location = ".", # directory to start search in
 139:     $context = $null
 140:   );
 141:   
 142:   $cis = @(Get-ChildItem -Path $location);
 143:   foreach ($ci in $cis)
 144:   {
 145:     if ( $ci.PSIsContainer )
 146:     {
 147:       if ( Get-IsMatch $ci -context $context )
 148:       {
 149:         Do-FindAction -info $ci -context $context;
 150:       }
 151:       
 152:       # Stop recursion if maxdepth is reached
 153:       if ( (-1 -ne $context["maxdepth"]) -and
 154:         (($script:CURRENT_DEPTH + 1) -gt $context["maxdepth"]) )
 155:       {
 156:         break;
 157:       }
 158:  
 159:       # Recurse through directories
 160:       $script:CURRENT_DEPTH++;
 161:       Find-InDirectory -location $ci.FullName -context $context;
 162:       $script:CURRENT_DEPTH--;
 163:     }
 164:     else
 165:     {
 166:       if ( Get-IsMatch $ci -context $context )
 167:       {
 168:         Do-FindAction -info $ci -context $context;
 169:       }
 170:     }
 171:   }
 172: }
 173:  
 174: #----------------------------------------------------------------
 175: # function Do-Find
 176: #----------------------------------------------------------------
 177: function Do-Find()
 178: {
 179:   param
 180:   (
 181:     [string]$start = ".",
 182:     [int]$maxdepth = -1,
 183:     [int]$mindepth = -1,
 184:     [int]$amin = -1,
 185:     [int]$atime = -1,
 186:     [bool]$empty = $false,
 187:     [string]$name = "",
 188:     [string]$path = "",
 189:     [string]$exec = ""
 190:   );
 191:   
 192:   $context = @{
 193:     "maxdepth" = $maxdepth; "mindepth" = $mindepth;
 194:     "amin" = $amin; "atime" = $atime;
 195:     "empty" = $empty; "name" = $name;
 196:     "path" = $path; "exec" = $exec};
 197:  
 198:   Find-InDirectory -location $start -context $context;
 199: }
 200:  
 201: Do-Find -start $start -maxdepth $maxdepth -mindepth $mindepth -amin $amin `
 202:   -atime $atime -empty $empty -name $name -path $path -exec $exec;

There are a few enhancements that could be made to this script such as better range checking to eliminate unnecessary directory recursion and also adding support for actions.

*Update – I’ve added support for Range testing so recursion will stop if maxdepth is reached.  Also, I’ve added support for executing commands on the resulting FileInfo or DirectoryInfo objects.  The string “{}” will be substituted for the FileInfo or DirectoryInfo object.  So, if you want the full objects instead of just the FullName values, you can do something like this: ‘.\Find.ps1 –exec “{}”’ 

You can download the full source for the script here: Find.ps1



 
      

Feedback


5/31/2009 7:09 AM
Gravatar Thanks! This is a life saver.

Now if I can only find a replacement for "less"... The built-in "more" command in PowerShell is garbage.
Craig

8/1/2009 11:04 PM
Gravatar Thanks! it is important for me.
This script help my site.
it make my site easy to edit.
Nail Biting
 Leave Feedback
Title  
Name  
Email
Url
Comments   
Please add 3 and 6 and type the answer here: