using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace RobvanderWoude { class RxGrep { static readonly string progver = "3.02"; #region Global Variables static bool dedup = false; static bool returngroup = false; static bool singlelinemode = false; static int skipmatches = 0; static int takematches = 0; static string groupseparator = " "; static List groupnums = new List( ); static int bytes = -1; static List dedupedmatches = new List( ); #endregion Global Variables 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 input = string.Empty; int matchcount; 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 "/?": return ShowHelp( ); case "/A": if ( groupseparator == "\t" ) { return ShowHelp( "Duplicate switch /A" ); } groupseparator = "\t"; break; 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 "/G": returngroup = true; int groupnum = 0; try { groupnum = Convert.ToInt32( option.Substring( 3 ) ); if ( groupnum < 1 ) { return ShowHelp( "Invalid group number {0} specified", groupnum.ToString( ) ); } } catch ( Exception e ) { Console.Error.WriteLine( "Error: {0}", e.Message ); return ShowHelp( string.Format( "Invalid command line switch \"{0}\"", option ) ); } if ( groupnum > 0 ) { groupnums.Add( groupnum ); } break; case "/I": if ( caseset ) { return ShowHelp( "Duplicate switch /L" ); } regexoptions |= RegexOptions.IgnoreCase; caseset = true; break; case "/L": if ( singlelinemode ) { return ShowHelp( "Duplicate switch /I" ); } singlelinemode = 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 finally done, let's get some action if ( singlelinemode ) { matchcount = 0; foreach ( string line in input.Split( new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries ) ) { matchcount += DisplayMatches( line, pattern, regexoptions ); } } else { matchcount = DisplayMatches( input, pattern, regexoptions ); } return matchcount; } // 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 ( returngroup ) { if ( match.Groups.Count >= groupnums.Count ) { bool empty = true; foreach ( int groupnum in groupnums ) { Group group = match.Groups[groupnum]; if ( !dedupedmatches.Contains( group.Value ) ) { if ( empty ) { Console.Write( group.Value ); } else { Console.Write( groupseparator + group.Value ); } dedupedmatches.Add( group.Value ); displayed += 1; empty = false; } } Console.WriteLine( ); } } else 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 ) ) { if ( returngroup && match.Groups.Count >= groupnums.Count ) { bool empty = true; foreach ( int groupnum in groupnums ) { Group group = match.Groups[groupnum]; if ( empty ) { Console.Write( group.Value ); } else { Console.Write( groupseparator + group.Value ); } displayed += 1; empty = false; } Console.WriteLine( ); } else { 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 3.00 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: /A use tAb as separator for multiple groups (default: space) /D do not show Duplicate matches /F:nn search only the First nn bytes /G:nn return match Group #nn (may be used multiple times) /I case Insensitive search /L single Line mode: ^ and $ match begin and end of each line /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. /G:nn can be used multiple times; all matching groups are written to screen, separated by spaces, unless /A is used, in which case the matches will be separated by tabs; matching groups are shown in order of input, not in order of command line arguments, e.g. /G:1 /G:3 will show the same result as /G:3 /G:1. Switches /D and /G cannot be combined with /S or /T. Return code ("errorlevel") will equal the number of matches found, or -1 in case of (command line) errors. Written by Rob van der Woude https://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( "/A" ); Console.ResetColor( ); Console.Error.Write( " use t" ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "A" ); Console.ResetColor( ); Console.Error.WriteLine( "b as separator for multiple groups" ); Console.Error.WriteLine( " (default: space)" ); 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( " /G:nn" ); Console.ResetColor( ); Console.Error.Write( " return match " ); Console.ForegroundColor = ConsoleColor .White; Console.Error.Write( "G" ); Console.ResetColor( ); Console.Error.Write( "roup " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.WriteLine( "#nn" ); Console.ResetColor( ); 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( " /L" ); Console.ResetColor( ); Console.Error.Write( " single " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "L" ); Console.ResetColor( ); Console.Error.WriteLine( "ine mode: ^ and $ match begin and end of each line" ); 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.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /G:nn" ); Console.ResetColor( ); Console.Error.WriteLine( " can be used multiple times; all matching groups are written" ); Console.Error.Write( " to screen, separated by spaces, unless " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/A" ); Console.ResetColor( ); Console.Error.WriteLine( " is used, in which case" ); Console.Error.WriteLine( " the matches will be separated by tabs; matching groups are shown" ); Console.Error.WriteLine( " in order of input, not in order of command line arguments, e.g." ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( " /G:1 /G:3" ); Console.ResetColor( ); Console.Error.Write( " will show the same result as " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/G:3 /G:1" ); Console.ResetColor( ); Console.Error.WriteLine( "." ); Console.Error.Write( " Switches " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/D" ); Console.ResetColor( ); Console.Error.Write( " and " ); Console.ForegroundColor = ConsoleColor.White; Console.Error.Write( "/G" ); 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 equal the number of matches found," ); Console.Error.WriteLine( " or -1 in case of (command line) errors." ); Console.Error.WriteLine( ); Console.Error.WriteLine( "Written by Rob van der Woude" ); Console.Error.WriteLine( "https://www.robvanderwoude.com" ); #endregion Display Help Text return -1; } #endregion Error Handling } }