Try something like this:
$ cat find-dupes.pl
#!/usr/bin/perl
use strict;
#use Data::Dump qw(dd);
# Explanation of the regexes ($f_re and $a_re):
#
# Both $f_re and $a_re start with '(?:^|&&|\|\||;|&)' to anchor
# the remainder of the expression to the start of the line or
# immediately after a ;, &, &&, or ||. Because it begins with
# '?:', this is a non-capturing sub-expression, i.e. it just
# matches its pattern but doesn't return what it matches.
# $f_re has two main sub-expressions. One to match 'function name ()'
# (with 'function ' being optional) and the other to match
# 'function name () {' (with the '()' being optional).
#
# Each sub-expression contains more sub-expressions, with one of
# them being a capture group '([-\w.]+)' and the rest being
# non-capturing (they start with '?:'). i.e. it returns the
# function name as either $1 or $2, depending on which subexp
# matched.
my $f_re = qr/(?:^|&&|\|\||;|&)\s*(?:(?:function\s+)?([-\w.]+)\s*\(\)|function\s+([-\w.]+)\s+(?:\(\))?\s*\{)/;
# $a_re matches alias definitions and returns the name of
# the alias as $1.
my $a_re = qr/(?:^|&&|\|\||;|&)(?:\s*alias\s+)([-\w.]+)=/;
# %fa is a Hash-of-Hashes (HoH) to hold function/alias names and
# the files/lines they were found on. i.e an associative array
# where each element is another associative array. Search for
# HoH in the perldsc man page.
my %fa;
# main loop, read and process the input
while(<>) {
s/#.*|^\s*:.*//; # delete comments
s/'[^']+'/''/g; # delete everything inside ' single-quotes
s/"[^"]+"/""/g; # delete everything inside " double-quotes
next if /^\s*$/; # skip blank lines
while(/$f_re/g) {
my $match = $1 // $2;
#print "found: '$match':'$&':$ARGV:$.\n";
$fa{$match}{"function $ARGV:$."}++;
};
while(/$a_re/g) {
#print "found: '$1':'$&':$ARGV:$.\n";
$fa{$1}{"alias $ARGV:$."}++;
};
close(ARGV) if eof;
};
#dd \%fa;
# Iterate over the function/alias names found and print the
# details of duplicates if any were found.
foreach my $key (sort keys %fa) {
my $p = 0;
# Is this function/alias ($key) defined more than once on
# different lines or in different files?
if (keys %{ $fa{$key} } > 1) {
$p = 1;
} else {
# Iterate over the keys of the second-level hash to find out
# if there is more than one definition of a function/alias
# ($key) in the same file on the same line ($k)
foreach my $k (keys %{ $fa{$key} }) {
if ($fa{$key}{$k} > 1) {
$p = 1;
# break out of the foreach loop, there's no need to keep
# searching once we've found a dupe
last;
};
};
};
# print the details if there was more than one.
print join("\n\t", "$key:", (keys %{$fa{$key}}) ), "\n\n" if $p;
};
The commented-out Data::Dump, print
, and dd
lines were for debugging. Uncomment them to get a better idea of what this script does and how it works. The output of the dd
function from the Data::Dump
module is particularly interesting as it shows you the structure (and contents) of the %fa
HoH. Data::Dump
is not included with perl, it's a library module you need to install. You didn't mention what distro you're using but if you're using debian/ubuntu/mint/etc, you can install it with sudo apt install libdata-dump-perl
. Other distros probably have it packaged under a slightly different name. Otherwise, you can install it with cpan
.
Example output (using a file containing your aliases from your comment plus a few dummy functions):
$ cat yorsub.aliases
function foo () { echo ; }
bar () { echo ; }
bar () { echo ; }
function baz () { echo ; } && quux () { echo ; } ; alias xyz=abc;
type tmux &> /dev/null && alias t='tmux'
alias cd-='cd -'; alias cd..='cd ..'; alias u1='cd ..'; alias u2='cd ../..'; alias u3='cd ../../..'; alias u4='cd ../../../../..'; alias u5='cd ../../../../../..'; alias u6='cd ../../../../../../..' alias back='cd -'; alias cd-='cd -'; alias .1="cd .."; alias .2="cd ../.."; alias .3="cd ../../.."; alias .4="cd ../../../.."; alias .5="cd ../../../../.."; alias .6='cd ../../../../../../..'
function cd.. { cd .. ; }
function abc () { xyx "$@" }; abc () { xyz } ; function abc { xyz }; alias abc=xyz
$ ./find-dupes.pl yorsub.aliases
abc:
function yorsub.aliases:8
alias yorsub.aliases:8
bar:
function yorsub.aliases:3
function yorsub.aliases:2
cd-:
alias yorsub.aliases:6
cd..:
alias yorsub.aliases:6
function yorsub.aliases:7
m() { man "$@"; }
. That statement about functions being faster and aliases getting looked up after functions sounds a bit suspect.grep -E '^alias|^[a-zA-Z0-9_]+\(\)'
do?^
, they could be declared inside aif-then-fi
orfor-do-done
loop, or could have multiple aliases on a linealias xx='thing1'; alias yy='thing2'; alias zz='thing3'
etc? And could it only show the actual duplicates found?declare -f -r funcname
. That would trigger an error if you tried to re-declare it later in the same session. Your question currently seem to be about parsing shell code, but the correct solution may be to organize the code better instead.declare -f -r funcname
does sound useful (new to me). If an alias is declared later, it would overwrite that function name, right?declare -f -r xxxx; xxxx() { man "$@"; }; alias xxxx=echo 1
immediately breaks the read-only funcname from what I see.