using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; namespace RobvanderWoude { class RxGrep { static readonly string progver = "2.04"; static bool dedup = false; static int skipmatches = 0; static int takematches = 0; static int bytes = -1; static int Main( string[] args ) { #region Initialize Variables bool caseset = false; bool quiet = false; bool skipset = false; bool takeset = false; string filename = string.Empty; string pattern = string.Empty; string input = string.Empty; int redirectnum = ( Console.IsInputRedirected ? 1 : 0 ); RegexOptions regexoptions = RegexOptions.None; #endregion Initialize Variables #region Command Line Parsing if ( args.Length + redirectnum < 2 ) { return ShowHelp( ); } List arguments = new List( args ); if ( arguments.Contains( "/?" ) ) { return ShowHelp( ); } if ( !Console.IsInputRedirected ) { filename = arguments[0]; arguments.RemoveAt( 0 ); } pattern = arguments[0]; arguments.RemoveAt( 0 ); foreach ( string option in arguments ) { switch ( option.ToUpper( ).Substring( 0, 2 ) ) { case "/D": if ( dedup ) { return ShowHelp( "Duplicate switch /D" ); } dedup = true; break; case "/F": if ( bytes != -1 ) { return ShowHelp( "Duplicate switch /F" ); } try { bytes = Convert.ToInt32( option.Substring( 3 ) ); } catch ( Exception e ) { Console.Error.WriteLine( "Error: {0}", e.Message ); return ShowHelp( string.Format( "Invalid command line switch \"{0}\"", option ) ); } break; case "/I": if ( caseset ) { return ShowHelp( "Duplicate switch /I" ); } regexoptions |= RegexOptions.IgnoreCase; caseset = true; break; case "/Q": if ( quiet ) { return ShowHelp( "Duplicate switch /Q" ); } quiet = true; break; case "/S": if ( skipset ) { return ShowHelp( "Duplicate switch /S" ); } try { skipmatches = Convert.ToInt32( option.Substring( 3 ) ); } catch ( Exception e ) { Console.Error.WriteLine( "Error: {0}", e.Message ); return ShowHelp( string.Format( "Invalid command line switch \"{0}\"", option ) ); } break; case "/T": if ( takeset ) { return ShowHelp( "Duplicate switch /T" ); } try { takematches = Convert.ToInt32( option.Substring( 3 ) ); } catch ( Exception e ) { Console.Error.WriteLine( "Error: {0}", e.Message ); return ShowHelp( string.Format( "Invalid command line switch \"{0}\"", option ) ); } break; default: return ShowHelp( string.Format( "Invalid command line {0}: \"{1}\"", ( option[0] == '/' ? "switch" : "argument" ), option ) ); } } #endregion Command Line Parsing #region Command Line Arguments Validation if ( Console.IsInputRedirected ) { // Read the redirected Standard Input input = Console.In.ReadToEnd( ); if ( bytes != -1 ) { input = input.Substring( 0, Math.Min( bytes, input.Length ) ); } } else { // Check if the file name is valid if ( filename.IndexOf( "/" ) > -1 ) { return ShowHelp( ); } if ( filename.IndexOfAny( "?*".ToCharArray( ) ) > -1 ) { return ShowHelp( "Wildcards not allowed" ); } // Check if the file exists if ( File.Exists( filename ) ) { // Read the file content using ( StreamReader file = new StreamReader( filename ) ) { if ( bytes == -1 ) { input = file.ReadToEnd( ); } else { char[] buffer = new char[bytes]; file.Read( buffer, 0, bytes ); input = string.Join( string.Empty, buffer ); } } } else { return ShowHelp( string.Format( "File not found: \"{0}\"", filename ) ); } } if ( dedup && ( skipset || takeset ) ) { return ShowHelp( "Switch /D cannot be combined with switches /S or /T" ); } #endregion Command Line Arguments Validation // Now that the command line parsing is done, let's get some action if ( DisplayMatches( input, pattern, regexoptions ) == 0 && !quiet ) { return ShowHelp( "No match found" ); } return 0; } // The main functionality: display all matching substrings public static int DisplayMatches( string haystack, string needle, RegexOptions options ) { int counter = 0; int displayed = 0; // Get all matches MatchCollection matches = Regex.Matches( haystack, needle, options ); if ( dedup ) { List dedupedmatches = new List( ); foreach ( Match match in matches ) { if ( !dedupedmatches.Contains( match.Value ) ) { Console.WriteLine( match.Value ); dedupedmatches.Add( match.Value ); displayed += 1; } } } else { if ( matches.Count > skipmatches ) { foreach ( Match match in matches ) { if ( counter >= skipmatches && ( displayed < takematches || takematches == 0 ) ) { Console.WriteLine( match.Value ); displayed += 1; } counter += 1; } } } return displayed; } #region Error Handling public static int ShowHelp( params string[] errmsg ) { #region Error Message if ( errmsg.Length > 0 ) { List errargs = new List( errmsg ); errargs.RemoveAt( 0 ); Console.Error.WriteLine( ); Console.ForegroundColor = ConsoleColor.Red; Console.Error.Write( "ERROR:\t" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.WriteLine( errmsg[0], errargs.ToArray( ) ); Console.ResetColor( ); } #endregion Error Message #region Help Text /* RxGrep, Version 2.03 Multi-line FindStr/Grep like tool Usage: RXGREP filename pattern [ options ] or: command | RXGREP pattern [ options ] Where: filename is the file to be filtered command is the command whose standard output is to be filtered pattern is the search pattern (regular expression) Options: /D do not show Duplicate matches /F:nn search only the First nn bytes /I case Insensitive search /Q Quiet mode: no message if no match is found /S:nn Skip the first nn matches /T:nn Take only nn matches Example: ROBOCOPY D:\sourcedir E:\targetdir /NP /MIR | RXGREP "\s+\d+\s+D:\\sourcedir\\[^\n\r]*\r\n([^\n\r\\]+\r\n)+" (to be read as a single command line) will return something like: 125 D:\sourcedir\subdir\ New File 342 brandnewfile.ext Newer 4.06m updatedfile.ext *EXTRA File 2.40m deletedfile.ext Notes: If /F:nn is used and a file is specified, only the first nn bytes of that file will be read; if the input is redirected, it is read entirely, and then chopped to nn bytes before being searched. Switch /D cannot be combined with /S or /T. Return code ("errorlevel") will be 1 in case of (command line) errors or if no match was found, otherwise 0. This version of the program requires .NET Framework 4.5. Written by Rob van der Woude http://www.robvanderwoude.com */ #endregion Help Text #region Display Help Text Console.Error.WriteLine( ); Console.Error.WriteLine( "RxGrep, Version {0}", progver ); Console.Error.WriteLine( "Multi-line FindStr/Grep like tool" ); Console.Error.WriteLine( ); Console.Error.Write( "Usage: " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.WriteLine( "RXGREP filename pattern [ options ]" ); Console.ResetColor( ); Console.Error.Write( "or: " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.WriteLine( "command | RXGREP pattern [ options ]" ); Console.ResetColor( ); Console.Error.WriteLine( ); Console.Error.Write( "Where: " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "filename" ); Console.ResetColor( ); Console.Error.WriteLine( " is the file to be filtered" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " command" ); Console.ResetColor( ); Console.Error.WriteLine( " is the command whose standard output is to be filtered" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " pattern" ); Console.ResetColor( ); Console.Error.WriteLine( " is the search pattern (regular expression)" ); Console.Error.WriteLine( ); Console.Error.Write( "Options: " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/D" ); Console.ResetColor( ); Console.Error.Write( " do not show " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "D" ); Console.ResetColor( ); Console.Error.WriteLine( "uplicate matches" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /F:nn" ); Console.ResetColor( ); Console.Error.Write( " search only the " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "F" ); Console.ResetColor( ); Console.Error.Write( "irst " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "nn" ); Console.ResetColor( ); Console.Error.WriteLine( " bytes" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /I" ); Console.ResetColor( ); Console.Error.Write( " case " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "I" ); Console.ResetColor( ); Console.Error.WriteLine( "nsensitive search" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /Q Q" ); Console.ResetColor( ); Console.Error.WriteLine( "uiet mode: no message if no match is found" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /S:nn S" ); Console.ResetColor( ); Console.Error.Write( "kip the first " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "nn" ); Console.ResetColor( ); Console.Error.WriteLine( " matches" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /T:nn T" ); Console.ResetColor( ); Console.Error.Write( "ake only " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "nn" ); Console.ResetColor( ); Console.Error.WriteLine( " matches" ); Console.Error.WriteLine( ); Console.Error.Write( "Example: " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.WriteLine( @"ROBOCOPY D:\sourcedir E:\targetdir /NP /MIR |" ); Console.Error.WriteLine( @" RXGREP ""\s+\d+\s+D:\\sourcedir\\[^\n\r]*\r\n([^\n\r\\]+\r\n)+""" ); Console.ResetColor( ); Console.Error.WriteLine( " (to be read as a single command line) will return something like:" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.WriteLine( @" 125 D:\sourcedir\subdir\" ); Console.Error.WriteLine( " New File 342 brandnewfile.ext" ); Console.Error.WriteLine( " Newer 4.06m updatedfile.ext" ); Console.Error.WriteLine( " *EXTRA File 2.40m deletedfile.ext" ); Console.ResetColor( ); Console.Error.WriteLine( ); Console.Error.Write( "Notes: If " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/F:nn" ); Console.ResetColor( ); Console.Error.Write( " is used and a file is specified, only the first " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "nn" ); Console.ResetColor( ); Console.Error.WriteLine( " bytes" ); Console.Error.WriteLine( " of that file will be read; if the input is redirected, it is read" ); Console.Error.Write( " entirely, and then chopped to " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "nn" ); Console.ResetColor( ); Console.Error.WriteLine( " bytes before being searched." ); Console.Error.Write( " Switch " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/D" ); Console.ResetColor( ); Console.Error.Write( " cannot be combined with " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/S" ); Console.ResetColor( ); Console.Error.Write( " or " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/T" ); Console.ResetColor( ); Console.Error.WriteLine( "." ); Console.Error.WriteLine( " Return code (\"errorlevel\") will be 1 in case of (command line)" ); Console.Error.WriteLine( " errors or if no match was found, otherwise 0." ); Console.Error.WriteLine( " This version of the program requires .NET Framework 4.5." ); Console.Error.WriteLine( ); Console.Error.WriteLine( "Written by Rob van der Woude" ); Console.Error.WriteLine( "http://www.robvanderwoude.com" ); #endregion Display Help Text return 1; } #endregion Error Handling } }