1

How can I share my .bashrc file but only relevant part?

For example I created 5 functions in .bashrc:

f1() {
...
}

f2() {
   f1
   ...
}

f3() {
   f2
   ...
}

f4() {
   f1
   f3
   ...
}

f5() {
   ...
}

Case 1: I want to share f5() with my colleagues, so I only have to copy f5() and paste to them

Case 2: I want to share f3(), so I want to copy f3() and recursively f2() and f1().

Case 3: I want to share f4(), so I want to copy f3() and recursively f2() and f1(); but I want to copy f1() only once, even though f1() is called both in f3() and f2().

Now I do it manually find-and-copy, but it's error-prone; is it possible to do it automatically? I don't use Python. Suppose all functions are all in the same .bashrc file, all are bash functions defined by me.

2
  • Maybe a combination of type function_name and type -t possible_function_name could achieve what you want. type function_name will return (only if the function_name is actually a function) its code. And type -t possible_function_name will return function in case possible_function_name is actually a function. Feb 15, 2023 at 8:44
  • That's my first thought too, but I cannot find out how to recursively do this: run function, get error output, substring before :, execute that function again, do the same, like this. Maybe : can do... not sure.
    – WesternGun
    Feb 15, 2023 at 22:11

2 Answers 2

0

Have a look at flatten.sh. I wrote that so I could just source all my aliases and functions into a shell-script and flatten it to just what's needed, so just the same situation you are in right now.

So with an example.lib like this (or your .bashrc for that matter):

f1() {
  echo "I am f1"
}

f2() {
   f1
}

f3() {
   f2
}

f4() {
   f1
   f3
}

f5() {
  :
}

And a script example.sh like this:

#!/usr/bin/env -S bash - 
. /path/to/example.lib

f4

you run flatten.sh example.sh and get

#!/usr/bin/env -S bash - 
f1() {
  echo "I am f1"
}

f2() {
   f1
}

f3() {
   f2
}

f4() {
   f1
   f3
}

f4

3
  • Thanks. I see you are in the same situation. But what is example.lib? I tried this: ./flatten.sh ./test.sh while test.sh is #!/usr/bin/env -S bash - \ . /home/westerngun/.bashrc \ . /home/westerngun/.bash_aliases \ hawtio ( backslash for newline) but it does not work. Am I using it right? I only get output hawtio(the name of function itself). It's a function containing alias java11. But when I try other functions without alias, I also only got the original function name.
    – WesternGun
    Feb 15, 2023 at 21:44
  • example.lib is just a dummy, it can be any file holding your functions. The script will only work with functions, not with aliases. That would need some more scripting. It's weird that you only get function names, though. I've been using this script for years and it always worked. Any error-messages?
    – markgraf
    Feb 16, 2023 at 7:34
  • The above being said, please do not use aliases in scripts or functions. There's a reason I do unalias -a in the beginning of my shell-scripts. You never know what the aliases elsewhere will be doing.
    – markgraf
    Feb 16, 2023 at 7:49
0

The following perl script is a proof-of-concept demo for extracting function names and definitions from a script. It builds up a hash (associative array) called %funcs containing each function's code. Then it searches each function definition to figure out which other functions are called within that function and stores that info in a hash-of-hashes ("HoH", a hash where each element is another hash. see man perldsc) called %contains.

Finally, starting from a list of function names provided on the command line, it builds up a list of functions to print (another hash called %out), then prints them.

The script is conceptually fairly simple & brute-force - no effort has been made to optimise the code for either performance or brevity.

NOTE: this is not a complete parser for shell code (far from it, it's a simple regex token matcher at best). It has only been tested against the shell code defining your example functions above, my own ~/.bashrc, and stdin from the output of the set built-in (which prints defined functions as well as variables, even though that isn't mentioned in help set or in the bash man page).

It's possible - indeed, quite likely - that other shell code will break this. In that case, you'll need to (at minimum) refine the token extraction regex, and probably the regex that looks for the start of function definitions too. and probably the code that strips out quoted strings and comments. They're the three most likely points of failure.

#!/usr/bin/perl

use strict;
use v5.10;

# primitive arg handling to separate function names from
# input files on the command line.
#
# if an argument is a filename that exists, treat it as
# an input file.  If not, treat it as a function name to
# search for.

my (@args,%find) = ();
foreach (@ARGV) {
  if (-e $_) {
    push @args, $_; # array of input files to process
  } else {
    $find{$_} = 1;  # hash of function name(s) to search for.
  }
};
@ARGV = @args;

# Main loop, read and process the input.
# Build up a hash called %funcs with key = function name
# and val = function code.
my %funcs = ();
while(<>) {
  state ($fname, $in_func, $counter);
  # state variables:
  # $fname   - name of current function
  # $in_func - are we in a function definition
  # $counter - how many { and } have we seen in this function?

  if (/^(?:function)?\s*([^\$(=\s]*)\s*[(]/) {
    $fname   = $1;
    $funcs{$fname} = $_;
    $in_func = 1;

    # cuddled braces begin on same line as function name. uncuddled don't.
    my $cuddled  = () = $_ =~ (/{/g); # count of { on this line
    next unless $cuddled;
    $cuddled    -= () = $_ =~ (/}/g); # subtract count of }s on line
    $counter = $cuddled;

    $in_func = $cuddled;
    next;
  };

  if ($in_func) {
    $funcs{$fname} .= $_;

    my $plus  = () = $_ =~ (/{/g); # count of {s on line
    my $minus = () = $_ =~ (/}/g); # count of }s on line

    $counter += $plus - $minus;

    $in_func = ($counter > 0);
  }
};

###
### Now determine which functions to print, then print them.
###

my %contains = ();
my $match = join("|", keys %funcs);

foreach my $f (keys %funcs) {
  # ignore everything in quoted strings and comments by
  # copying the current function to variable $function and
  # stripping unwanted junk. ignore unquoted array references  too.
  my $function;
  ($function = $funcs{$f}) =~ s/"[^"]*"|'[^']*'|#.*$|\$\{[^}]}//mg;

  # find tokens that *look like* calling one of our known function names
  my (@tokens) = ($function =~ /(?:^|;|&{1,2}|\|{1,2}|[({])\s*($match)\b(?!=)/gm);

  foreach my $t (@tokens) {
    # if the current token $t is one of our known functions
    # then add it to %contains{$f}
    if (defined($funcs{$t})) {
      $contains{$f}->{$t} = 1;
    };
  };
};

my %out = %find;

# Iterate over each function's name.  Add the name to %out 
# and %find if it is called from within a wanted function.
# Repeat until %out doesn't change.
my %old_out;
do {
  %old_out = %out;
  foreach my $f (keys %find) {
    foreach my $t (keys %{ $contains{$f} }) {
      $out{$t}  = 1; # add to output hash
      $find{$t} = 1; # add to list of function names to search for
    };
  };
} until %out == %old_out;

# print the functions listed in %out, sorted by name
# otherwise will be printed in pseudo-random order as hashes
# are un-ordered.
foreach my $f (sort keys %out) {
  print $funcs{$f}, "\n";
};

Save as, e.g. extract-funcs.pl, make executable with chmod +x and run like so (with a copy of your function definitions in functions.txt):

$ ./extract-funcs.pl functions.txt f1
f1() {
...
}

f1 doesn't contain any calls to other functions so only f1 is printed.

$ ./extract-funcs.pl functions.txt f2
f1() {
...
}

f2() {
   f1
   ...
}

f2 contains a call to f1, so f1 & f2 are printed.

$ ./extract-funcs.pl functions.txt f4
f1() {
...
}

f2() {
   f1
   ...
}

f3() {
   f2
   ...
}

f4() {
   f1
   f3
   ...
}

f4 contains a call to f1 & f3, f3 contains a call to f2, and f2 contains a call to f1, so f1, f2, f3, and f4 are printed. f1 is only printed once, even though it is called from both f4 and f2

You can pipe the output to xsel -i -b to copy to the clipboard for use with Paste in an Edit menu, or Ctrl-V, or Shift-Ins. or just xsel -i to copy to X's primary selection for middle-button paste. see man xsel.

5
  • I've used the declare -f-approach in early versions of flatten.sh, but that looses comments from the functions. It will still work, of course, but I like to document what my functions do. That may not be an issue for @WesternGun, though. Nice work with the perl-approach :-)
    – markgraf
    Feb 15, 2023 at 18:11
  • Thanks for answering, didn't expect such a long answer. If my f4() calls a lot of functions and all I want is that I need to share f4(), will your approach(1 or 2) work only with name of f4? I guess not right? The aim is to use f4() and recursively go back to f1() and so, not to list everything one by one.
    – WesternGun
    Feb 15, 2023 at 21:29
  • @WesternGun I'd be interested to hear how the new script works above with your actual ~/.bashrc
    – cas
    Feb 16, 2023 at 3:05
  • I executed it but see this error: Can't declare scalar assignment in "my" at ./extract.pl line 72, near ") =~" \ Execution of ./extract.pl aborted due to compilation errors. I try to use it against my .bashrc and function hawtio, which is hawtio () { /usr/lib/jvm/java-11-openjdk-amd64/bin/java -Dhawtio.proxyAllowlist=* -Dhawtio.proxyDisableCertificateValidation=true -jar ~/Documents/AMQ/hawtio-jolokia/hawtio-app-2.15.2.jar}
    – WesternGun
    Feb 22, 2023 at 19:50
  • sorry, copy-and-paste error. Insert a line before line 72 with my $function;, then delete the my from the beginning of line 72. Also, there's an unescaped { immediately after an escaped $ symbol on line 72, near the end of the regex. Insert a backslash between $ and } (i.e. change it to \$\{)....the missing ` doesn't prevent the script from working, but does produce a warning when the script is test-compiled with perl -w -c`. i'll edit the script in my answer.
    – cas
    Feb 22, 2023 at 23:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .