views:

55

answers:

5

I have a number of .css files spread across some directories. I need to find those .css files, read them and if they contain a particular class definition, print it to the screen.

For example, im looking for ".ExampleClass" and it exists in /includes/css/MyStyle.css, i would want the shell command to print

.ExampleClass {
color: #ff0000;
}
A: 
find /starting/directory -type f -name '*.css' | xargs -ti grep '\.ExampleClass' {}

will find all the css files, print the filename and search string and then print the results of the grep. You could pipe the output through sed to remove any unnecessary text.

ETA: the regex needs work if we want to catch multiline expressions. Likely the EOL character should be set to } so that complete classes are considered one line. If this were done, then piping the find to perl -e rather than grep would be more effective

dnagirl
That won't find multi-line blocks like the OP shows in the question. It will only find the first line.
Dennis Williamson
Shouldn't that be grep '\.ExampleClass'?
Christopher W. Allen-Poole
@Christopher W. Allen-Poole: fixed
dnagirl
A: 

Considering that the css file can have multiline class definitions, and that there can be several ocurrences in the same file, I'd bet perl is the way to go.

For example:

#input: css filename , and css class name without dot (in the example, ExampleClass)
my ($filen,$classn) = @ARGV;  
my $out = findclassuse($filen,$classn);
# print filename and result if non empty
print ("===== $filen : ==== \n" . $out . "\n") if ($out); 

sub findclassuse {
    my ($filename,$classname) = @_;
    my $output = "";
    open(my $fh, '<', $filename) or die $!;
    $/ = undef; # so that i read the full file content
    my $css  = <$fh>;
    $css =~ s#/\*.*?\*/# #g; # strip out comments
    close $fh;
    while($css =~ /([^}{]*\.$classname\b.*?{.*?})/gs) {
        $output .= "\n\n"  . $1;
    }
    return $output;
}

But this is not 100% foolproof, there remains some issues with comments, and the css parsing is surely not perfect.

leonbloy
+4  A: 

Use find to filter on all css files and execute a sed script on those files printing lines, between two regular expressions:

find ${DIR} -type f -name "*.css" -exec sed -n '/\.ExampleClass.{/,/}/p' \{\} \+
Jürgen Hötzel
2 problems: sed is a stream editor so, like my solution, it doesn't handle multiline classes. And -exec, though it works, is very slow. xargs is recommended for any kind of heavy lifting.
dnagirl
The exec+ variant is used, so the actual command line is built in the same way that xargs builds its command lines. exec+ doesn't require IPC via pipe so its actually MORE efficient than using xargs.
Jürgen Hötzel
It's good looking at everyone's solution, and I can learn from them all. I just grabbed this one and it's doing what I need for my project. Thank you
Brian
What do you mean it doesn't handle multiline classes? It looks to me like it should print everything including and between the first line matching `.ExampleClass.{` and `}`
amertune
A: 

Assuming you never do anything weird, like putting the opening brace on a separate line, or putting an unindented (nested) closing brace before the intended one, you can do this:

sed -n '/\.ExampleClass *{/,/^}/p' *.css

And if the files are all over a directory structure:

find . -name *.css | xargs sed ...
Jefromi
Or the closing brace on the same line as the class name and opening brace (see my answer).
Dennis Williamson
A: 

This version handles multi-line blocks as well as blocks on a single line:

sed -n '/^[[:space:]]*\.ExampleClass[[:space:]]*{/{p;q}; /^[[:space:]]*\.ExampleClass[[:space:]]*{/,/}/p'

Examples:

foo { bar }

or

foo {
        bar
}
Dennis Williamson
+1; definitely more robust than my answer, though possibly a little less practical for a one-off. Wheeeeeeeeeee, sed.
Jefromi