Link to home
Start Free TrialLog in
Avatar of Les Ostness
Les OstnessFlag for United States of America

asked on

Is there a way to auto indent echo statements depending the order of what script called another script?

When you have script_1 call script_2, which in turn calls script_3, Is there a way to auto indent echo statements depending the order of what script calls the other? It would be nice if the script could also print the script that called it.
I'm using bash on linux.

example output
Running script_1
   Running script_2
      Running script_3

example output 2
Running script_1
   Running script_2  Called by script_1
      Running script_3 Called by script_2
Avatar of Bill Prew
Bill Prew

I'm not aware of anyway to do that automatically, but I know there are experts more knowledgeable than me on Linux scripting.

For output that you control from the scripts like echoing information messages you could keep track of an indentation level in an environment variable and increment and decrement it as you enter and leave called scripts, but that assumes all the scripts are under your control.  Then in each script you look at the level and add that number of spaces to the left of informational messages.

But that won't effect normal output from any commands or programs that the called scripts perform, their output will still be at the left of the line typically.

I don't think it changes anything, but it would be good to also specify what shell you are using.


»bp
Avatar of Les Ostness

ASKER

I found some references to using $PPID. Which I think is part of the answer. Now I have to figure out how to count the levels of each script.
The $PPID variable holds the parent process ID. So you could parse the output from ps to get the command.
 PARENT_COMMAND=$(ps -o comm= $PPID)
Yes.

Easy way to do this is to pass an --indent=$level option to each script, where each script pulls the value, increments $level, then passes --indent=$level to the next script + so forth.

I've used this approach many times in scripts.

In my case I use PERL, so my print statements look like this...

my $offset = 3;
... ... ...
print ($offset * $level) x " " . $text . "\n";

Open in new window


I normally just maintain $level as the actual level + then use $offset for padding, otherwise math can become very odd to change across many scripts.
One option is to use the "pstree" command to get a list of all of the parents of the current script. The process ID (PID) of the current script in bash is the $$ variable. For example. "pstree -s $$" here gives "systemd───sshd───sshd───sshd───bash───pstree" (each process separated by three hyphens). A quick awk counts the number of entries, and you can use that to generate the indent level.

    pstree -s $$ | awk -F--- '{print NF;exit}'

If this script calls another one, there would be one more entry in the pstree output, so the count will go up by 1.

On my machine, my initial script had a count of 6 (because I was using SSH to connect to the machine) - if, for example, this script was running as a cron job or directly logged in to the console, the number of entries at the top level could be different.
For the name of the current script, use the $0 argument. You can get just the last part of the name with the basename command.

    script_name=$(basename $0)

For the name of the calling script, use the 'ps' command with, as you suggest, $PPID for the parent process's ID. The command below gives the full argument list (or at least, some of it if it is too long!), so will have the "/bin/bash" if you are using shell scripts.

    parent_name=$(ps --no-headers -o "%a" $PPID)

In my test script, I got "/bin/bash ./shell_lvl1.sh" as the output one level down.

Edit: Your 'ps -o comm= $PPID' version is better as it just prints the shell script name if you have nested shell scripts
A shell variant on the perl suggestion of  David would be:

add the following to the start of all involved script:

if [ "$indenting"  == "" ] ; then indenting=" " ; else indenting="   $indenting" ; fi
export indenting
echo "${indenting}Running:" $( basename $0 )

Then use in each echo:
echo "${indenting}whatever the was before indenting... "

So there is an indenting prefix to any output..   output of all kinds of programs would not be indented though...
That would need some help like:

somecommand  | sed -e "s/^/${indenting}/"

This will restore the indenting level when returning from a script without extra work..
pstree is not the right way towards the solution I think. BP's suggestion is more along what is being asked. Have each script increment the 'indent' counter at the beginning of the script and decrement it at the end of the script. The echo commands could then be replaced by calling a function that echoes the current 'indent' amount of tabs/spaces followed by the text to echo.
Decrementing is not needed for shell scripts as they have their own "environment" so it should restore on return.

see example: t1.sh
#!/bin/sh

if [ "$indenting"  == "" ] ; then indenting=" " ; else indenting="   $indenting" ; fi
export indenting
echo "${indenting}Running:" $( basename $0 )

./t2.sh

echo "${indenting}the End"
exit

Open in new window

and t2.sh:
#!/bin/sh

if [ "$indenting"  == "" ] ; then indenting=" " ; else indenting="   $indenting" ; fi
export indenting
echo "${indenting}Running:" $( basename $0 )

ls -l |  sed -e "s/^/${indenting}/"
exit

Open in new window


And it's output:
$ ./t1.sh
 Running: t.sh
    Running: t2.sh
    total 8
    -rwxr-xr-x 1 root root 201 Jul 23 15:52 t1.sh
    -rwxr-xr-x 1 root root 199 Jul 23 15:52 t2.sh
 the End

Open in new window

That depends on how the scripts call the other scripts, if you call like this:

. ./t2.sh

then t2 knows the variables from the previous script.

Or just use export when increasing the variable.
Just run each script through sed

Replace
"Sh subscript.sh "

With
"Sh subscript.sh PIPE sed 's/^/  /' "

This will indent the subscript by 2 spaces

You  an easily build a callscript() func that displays the callecd script name and pervorms the above

obviously replace PIPE with a pipe chararter. I m on a mobile phone....
WOW this got more attention than I expected. I love all the ideas and suggestions. My priorities have changed, but I will get back to this. I think I got the answers I was looking for. I just don't have time to test them right now. I'd like to leave this unresolved for the next few days. I'm curious what other people might contribute to this. Thanks again for all the great input.
Hi Les (et al),

Incrementing the indentation with each script call isn't tough, but so far we've ignored the hard part -- decrementing when the script exits.

I don't know of any way to easily manage the decrement.  Scripts tend to not flow completely top to bottom, but have random exits (returns) embedded in them.  And scripts tend to call other scripts at random locations, not just the last line, so the indentation is wrong upon the return.

You can probably encapsulate the increment/decrement of the indentation with ALIAS -- it'll look cleaner to execute incr_indent and decr_indent in the script, but you'll still need to edit them into your scripts at the necessary locations and modify everything that writes to stdout to use the indentation.  Another complication is that an unexpected exit will leave the indentation at a place that's not intended.

Doing this right is not trivial.  But it does have its merits.  Instead of brute forcing this into every script that you run, perhaps creating a new stream and writing to it with the shell managing it and inserting the indentation.  But that's not trivial either...

Fun exercise.  :)
Kent
Both noci's way and mine handle that.

In his case, it will work because he indents inside the inner script and the indentation level is naturally restored as the script ends. With mine it will work because sed scripts are launched for each subcommand so the lauched seds basically match the process tree

There are many other ways including aliasing echo inside each script using the previous echo spacing offset
ASKER CERTIFIED SOLUTION
Avatar of skullnobrains
skullnobrains

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks again for all your input. I really liked noci's solution, but runsub by skullnobrains worked great and is my solution.
It indented all the echo statements in the subscripts

DEBUG IS OFF   ./level_1.sh 143

RUNNING /DIR1/DIR2/DIR3/work/level_1.sh on ddbaap01

Start Date & Time
Thu Jul 25 09:46:22 CDT 2019

./level_1.sh 178
>>> ENTERING RUBSCRIPT level_2.sh
        DEBUG IS OFF   level_2.sh 143

        RUNNING /DIR1/DIR2/DIR3/work/level_2.sh on ddbaap01

        Start Date & Time
        Thu Jul 25 09:46:22 CDT 2019

        level_2.sh 178
        >>> ENTERING RUBSCRIPT level_3.sh
                DEBUG IS OFF   level_3.sh 143

                RUNNING /DIR1/DIR2/DIR3/work/level_3.sh on ddbaap01

                Start Date & Time
                Thu Jul 25 09:46:22 CDT 2019

                level_3.sh 178
        <<<DONE
<<<DONE