/etc/init.d/multipathd |-- binary: basename |-- binary: test |-- teardown_slaves | |-- binary: pwd | |-- binary: sed | |-- binary: echo | |-- binary: readlink | |-- function: teardown_slaves | |-- binary: sed | `-- binary: echo |-- binary: test |-- binary: echo |-- binary: touch |-- binary: echo |-- binary: echo |-- binary: echo `-- binary: echo /etc/init.d/multipathd Code lines: 102 Comment lines: 13 Empty lines: 12 Total lines: 127 Function(s): teardown_slaves Uses function(s): teardown_slaves Uses binarie(s): basename echo info pwd readlink rm sed test touch Uses RPM(s): coreutils-5.97-19.el5 info-4.8-14.el5 sed-4.1.5-5.fc6 #### ------------------------------------------- Duplicate local function names: 5 stop 5 start 2 status 2 restart 2 condrestart 1 do_restart_sanity_check 1 makedev 1 start_isdnlog ... ------------------------------------------- Most used functions: 18 stop 12 start 6 restart 4 status 4 invoke_command ... ------------------------------------------- Most used binaries: 422 echo 72 rm 57 touch 40 grep 36 test 30 awk ... ------------------------------------------- Most used RPM's: 671 coreutils-5.97-19.el5 50 grep-2.5.1-54.2.el5 30 gawk-3.1.5-14.el5 19 util-linux-2.13-0.50.el5 10 sed-4.1.5-5.fc6 9 file-4.17-15.el5_3.1 4 xorg-x11-xfs-1.0.2-4 ... ------------------------------------------- Total code lines: 4438 Total comment lines: 989 Total empty lines: 705 Total lines: 6132 Total files: 51 #### #!/usr/bin/perl # Bash Parser v0.9 # # Creates a dependency and statistics report of a directory with BASH scripts. # # Copyright (c) 2009, Michael Persson (mickep76@mac.com) # All rights reserved. # Error checking for files use strict; # Check arguments die "usage: bash_reporter directory\n" if $#ARGV; my $path = $ARGV[0]; die "Please specify a valid directory\n" if not -d $path; # Ignore functions our %ignore_functions = ( # 'echo' => 1, # 'true' => 1 ); # Ignore binaries our %ignore_binaries = ( # 'sed' => 1, # 'awk' => 1, # 'echo' => 1, # 'cut' => 1 ); # BASH keywords 'compgen -k' our %bash_keywords = ( 'if' => 1, 'then' => 1, 'else' => 1, 'elif' => 1, 'fi' => 1, 'case' => 1, 'esac' => 1, 'for' => 1, 'select' => 1, 'while' => 1, 'until' => 1, 'do' => 1, 'done' => 1, 'in' => 1, 'function' => 1, 'time' => 1 ); # Global hashes our %binaries_in_path; our %binaries_to_rpm; our %all_sources; our %all_functions; our %dependencies; our %variables; # Global statistics hashes our %stat_sources; our %stat_functions; our %stat_uses_binaries; our %stat_uses_functions; our %stat_uses_rpms; # Global totals our $tot_code_lines = 0; our $tot_comment_lines = 0; our $tot_empty_lines = 0; our $tot_lines = 0; our $tot_files = 0; # Get all binaries in the PATH get_binaries(); # Recurse through all files in the path recurse($path); # Go through all sourced files foreach(keys %all_sources) { match($_, 0) } # Print statistics print_stat_hash(\%stat_sources, "Most sourced files:"); print_stat_hash(\%stat_functions, "Duplicate local function names:", 1); print_stat_hash(\%stat_uses_functions, "Most used functions:"); print_stat_hash(\%stat_uses_binaries, "Most used binaries:"); print_stat_hash(\%stat_uses_rpms, "Most used RPM's:"); # Print totals print_separator(); print "Total code lines:\t$tot_code_lines\n"; print "Total comment lines:\t$tot_comment_lines\n"; print "Total empty lines:\t$tot_empty_lines\n"; print "Total lines:\t\t$tot_lines\n"; print "Total files:\t\t$tot_files\n"; # Get all binaries in the PATH sub get_binaries { foreach my $directory (split ':', $ENV{'PATH'}) { foreach(glob("$directory/*")) { my $file = $_; if(-f $_ && -x $_) { s/.*\///; $binaries_in_path{$_} = $file; } } } } # Recurse through all files in a given path sub recurse { my $path = shift; foreach(glob("$path/*")) { if(-d $_) { recurse($_) } else { match($_, 1) } } } # Find sources, functions and binaries used in file sub match { my $file = shift; my $check_if_bash = shift; undef %all_functions; undef %dependencies; undef %variables; my %functions; my %sources; my %uses_binaries; my %uses_functions; my %uses_rpms; my $function = undef; my $line_number = 1; my $comment_lines = 0; my $empty_lines = 0; my $text_block = undef; my $function_line = undef; open FH, "<$file" or die "Failed to open file $file: $!\n"; foreach() { chomp(); my $ignore_line = 0; # Check if file is a Bash script if($check_if_bash == 1 && $line_number == 1 && ! /bash/) { return } # Count comments and empty lines elsif(/(\%\%.*\%\%)/) { if($1 eq $text_block) { $text_block = undef } else { $text_block = $1; } } elsif(/^[ \t]*#/) { $comment_lines++; $ignore_line = 1; } elsif($text_block) { $ignore_line = 1 } elsif($_ eq '') { $empty_lines++ } # Get variable assignments elsif(/([\w\.\-\_]+)=(.*)/) { $variables{$1} = $2; } # Get files sourced by script elsif(/^[ \t]*source +([\w\-\/\.\_]+)/) { my $source = expand_path($1); $sources{$source}++; $stat_sources{$source}++; push @{$dependencies{$file}}, $source; get_sources($source); } # Get functions in script elsif((/function +([\w\.\-\_]+)/ || /([\w\.\-\_]+)\(\)/) && ! $bash_keywords{$1}) { $function = $1; $function_line = $line_number; } elsif($function ne undef && ($function_line + 1) == $line_number) { if(/^{/) { $functions{$function}++; $all_functions{$function}++; $stat_functions{$function}++; push @{$dependencies{$file}}, $function; } else { $function = undef; $function_line = undef; } } elsif(/^}/) { $function = undef; $function_line = undef; } $line_number++; if($ignore_line) { next } # Get all binaries and functions called from script s/\#.*//; foreach my $word (split /[\`\;\| \t]+/) { if($all_functions{$word} && ! $ignore_functions{$word}) { $uses_functions{$word}++; $stat_uses_functions{$word}++; if($function eq undef) { push @{$dependencies{$file}}, "function: $word" } else { push @{$dependencies{$function}}, "function: $word" } } elsif($binaries_in_path{$word} ne undef && ! $ignore_binaries{$word}) { $uses_binaries{$word}++; $stat_uses_binaries{$word}++; if($function eq undef) { push @{$dependencies{$file}}, "binary: $word" } else { push @{$dependencies{$function}}, "binary: $word" } if($binaries_to_rpm{$word} eq undef) { my $rpm = `rpm -qf $binaries_in_path{$word}`; if($rpm =~ /([\w\.\-\_]+)/ && $rpm !~ /not owned/) { $uses_rpms{$1}++; $binaries_to_rpm{$word} = $1; $stat_uses_rpms{$1}++; } } else { $uses_rpms{$binaries_to_rpm{$word}}++; $stat_uses_rpms{$binaries_to_rpm{$word}}++; } } } } close FH; $tot_code_lines += $line_number - $comment_lines - $empty_lines - 1; $tot_comment_lines += $comment_lines; $tot_empty_lines += $empty_lines; $tot_lines += $line_number - 1; $tot_files++; print_separator(); print_dependencies($file); print "\n$file\n"; printf "\tCode lines:\t%s\n", $line_number - $comment_lines - $empty_lines - 1; printf "\tComment lines:\t%s\n", $comment_lines; printf "\tEmpty lines:\t%s\n", $empty_lines; printf "\tTotal lines:\t%s\n", $line_number - 1; print_hash(\%sources, 'Source(s):'); print_hash(\%functions, 'Function(s):'); print_hash(\%uses_functions, 'Uses function(s):'); print_hash(\%uses_binaries, 'Uses binarie(s):'); print_hash(\%uses_rpms, 'Uses RPM(s):'); } # Get all functions for sourced files and place in all_functions sub get_sources { my $file = shift; $all_sources{$file}++; my $function = undef; open FH, "<$file" or die "Failed to open file $file: $!\n"; foreach() { chomp(); # Get files sourced by script if(/^[ \t]*source +([\w\-\/\.\_\$]+)/) { my $source = expand_path($1); push @{$dependencies{$file}}, $source; get_sources($source); } # Get variable assignments elsif(/([\w\.\-\_]+)=(.*)/) { $variables{$1} = $2; } # Get functions in script elsif(/function +([\w\.\-]+)/ || /([\w\.\-]+)\(\)/) { $function = $1; } elsif($function ne undef && /{/ && ! $bash_keywords{$function}) { $all_functions{$function}++; } elsif(/}/) { $function = undef; } } close FH; } # Expand variables in path sub expand_path { my $path = shift; if($path =~ /\$([\w\-\.\_]+)/) { my $variable = $1; $path =~ s/\$$variable/$variables{$variable}/; } return $path; } # Print separator sub print_separator { print "-" x 100 . "\n"; } # Print hash sub print_hash { my $hash = shift; my $heading = shift; if(scalar(keys %{$hash}) > 0) { print "\t$heading\n" } foreach(sort keys %{$hash}) { print "\t\t$_\n" } } # Print statistics hash sub print_stat_hash { my $hash = shift; my $heading = shift; my $minimum = shift; if(scalar(keys %{$hash}) > 0) { print_separator(); print "$heading\n"; } foreach(sort { ${$hash}{$b} <=> ${$hash}{$a} } keys %{$hash}) { if(${$hash}{$_} > $minimum) { printf "%s\t$_\n", ${$hash}{$_} } } } # Print dependency tree sub print_dependencies { my $function = shift; my $padding = shift; my $where = shift; if($where eq 'middle') { print "$padding|-- "; $padding .= '| '; } elsif($where eq 'last') { print "$padding`-- "; $padding .= ' '; } print "$function\n"; $where = "middle"; my $count = 1; foreach(@{$dependencies{$function}}) { if($count++ == scalar(@{$dependencies{$function}})) { $where = "last" } if($function ne $_) { print_dependencies($_, $padding, $where) } } }