6

I have a large number of scripts that I need to modify. Each script probably calls 5 to 10 other scripts, which in turn might call several other scripts, and who knows how deep that rabbit hole runs.

Is there any way to get a list of scripts called by a particular script?

I'm thinking something like:

/home/root/ $ showscripts mytargetscript

the output would be something like:

/home/root/mytargetscript
/home/root/asubscript
/home/root/bsubscript
...

If I've offended the Unix gods, I offer my penance in advance. I answer lots of questions on dba.se.

2
  • 2
    Are you able to run the scripts? Can you trace them using eg truss for exec system calls? Jun 4, 2015 at 14:57
  • 2
    The Unix gods are pleased. For now.
    – mikeserv
    Jun 4, 2015 at 15:40

4 Answers 4

5

There is no generic solution as there are countless ways a script could be using to call other scripts. You can do a grep which may work for your scripts but not in general.

Which scripts does this call?

$(find / -executable -name "*.sh" -print0 | shuf -z -n 1)

If you're able to actually run these scripts you could trace them in two ways.

set -x

will make your script print each command it executes in expanded form. You could then check those commands for ones that run scripts.

strace -ff

Overkill but strace gives you everything a process does and with the -ff option it follows the rabbit hole nearly to its end as well. I say nearly because there are ways to get around it. Does it follow daemons?

Grep the strace for calls to open() or exec*() and filter it for files that are scripts and you might be somewhere near a complete picture [for that one run of the script you made - not counting scripts that are only called under other conditions].

$ strace -ff ./testscript.sh |& grep 'open.*\.sh"' 
open("./testscript.sh", O_RDONLY)       = 3
[pid 24486] open("./CD-DVD Image erstellen.sh", O_RDONLY) = 3

So you can be creative with your solutions, just don't expect one that fits every situation.

6
  • Please see a "generic" solution in my answer.
    – lcd047
    Jun 4, 2015 at 15:58
  • @lcd047 I've seen your answer, that's why I didn't include a grep example. But grep does not find any but the most primitive calls (which may be all that's wanted here, but who knows) and there is a possibility of false matches. Jun 4, 2015 at 16:09
  • If the scripts are not purposefully obfuscated, there should be no false negatives. There is indeed a possibility of false positives, in situations like # this script is commented out and echo "havea nice script".
    – lcd047
    Jun 4, 2015 at 16:17
  • It's not obfuscation if a script does for script in hooks/*.sh. Scripts call scripts like that all the time. And that's what I meant with "no generic solution". There is only so much you can do with grep while not knowing anything about the script in question. Jun 4, 2015 at 16:34
  • Hmm, true, that too is pretty common.
    – lcd047
    Jun 4, 2015 at 16:39
3

Asusming all scripts live in the same directory, they don't have tabs or newlines in their names, and you have the list of the "interesting" ones in a file scripts.txt, one per line, and also assuming your shell can do <(...) process substitutions:

#! /bin/sh
while read -r s; do
    fgrep -o -w -f <(fgrep -v -w "$s" scripts.txt) "$s" /dev/null | \
        sort -u | \
        tr : '\t' >>calls.txt
done <scripts.txt

This builds a file calls.txt. The file is formed of tab-separated pairs script1 script2, that describe relations "script1 calls script2".

You need to feed calls.txt to the following Perl script:

#!/usr/bin/env perl
use v5.10;                  
use strict;                 
use warnings;               

use Graph::Directed;        

my $g = Graph::Directed->new;

while (<>) {                
    chomp;                  
    $g->add_edge( split /\t/ );
}                           

for ( sort $g->vertices ) { 
    say "$_: " . join(', ', sort $g->all_successors($_));
}

The script builds a directed graph out of the call relations, and then prints the successors for all vertices (i.e. scripts).

Of course, the script needs the Perl module Graph. Assuming you have the cpanm script, you can install the required module by running cpanm Graph.

10
  • 1
    I don't believe this recursively descends? Jun 4, 2015 at 14:55
  • 1
    @Colin'tHart Right, it doesn't. But you can infer the answer from it with the Pel module Graph::Directed. I'll edit my answer.
    – lcd047
    Jun 4, 2015 at 15:00
  • unfortunately our scripts do not end in .sh. they have no extension at all. :-( Jun 4, 2015 at 15:29
  • @MaxVernon Well, you do have some way to get the list of scripts names in a file scripts.txt one per line, right?
    – lcd047
    Jun 4, 2015 at 15:39
  • 1
    @Colin'tHart Updated.
    – lcd047
    Jun 4, 2015 at 15:40
0

Given a script of

$ cat the_script
ls
/bin/ls
touch ./xx.xx
ls
/bin/ls
ls
ls
  /bin/ls
ls
    /bin/ls(tabs)

Then

$ cat the_script | grep ^[[:space:]]*\t*\/

produces

/bin/ls
/bin/ls
  /bin/ls
    /bin/ls

and thus will show those calls if they start at the begging of lines (allows for spaces or tabs)

You can also identify executable files for the 'list of files to do this for' instead of maintaining a hard-coded list with, e.g.

$ find -type f -perm /u=x,g=x,o=x
./test.sh
./the_script

and you could pipe

0

One possible way (untested) is the following.

Summary: you have a lot of shell scripts, all calling each other. You want to know what the calling relationship between these scripts is.

Let's assume that the scripts are all invoked as separate programs, since Gilles tells me that in that case the called script is a child process of the calling script.

Then put some code in each script, that writes information like process id, parent process id, and the name of the script to a database. This code could be common to all the scripts. Then post-process the database information after running your script(s), to determine the calling relationship between the scripts.

You must log in to answer this question.

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