Check if bash script was invoked from a shell or another script/application
Asked Answered
F

4

18

I am writing a bash script to redirect output from another command to the proper location. Basically, when the script is invoked from a shell/commandline I want to send the output to STDOUT. But, when the bash script is executed from some other application (e.g. another bash script, some application, or in my case from the awesome-prompt plugin in my Awesome Window Manager) I want to redirect the output somewhere else.

Is there any way in bash to see how a script was invoked?

Ferriage answered 23/11, 2010 at 22:55 Comment(3)
The other way of looking at it, is, why don't you use a wrapper script which pipes the output itself? So, Awesome Window Manager calls script-wrapper.sh , and this contains the line "./script.sh >> awesome.log"Sofiasofie
As a user of shell scripts, i prefer it when scripts don't try to be too clever. If i want the output in a file, i am perfectly capable of putting it in a file myself. Providing a wrapper or a flag to the script to send output to a file (eg -o filename) would be ideal - easy, but explicit.Mra
Good points, but I want this to be simple. The command in question is Taskwarrior. I just want to be able to type task add blah blah and not worry where I typed it. If I need to type a slew of redirects or options everytime, I won't use it because it's not convenient enough.Ferriage
M
32

Try this:

ps -o stat= -p $PPID

If the result contains "s" (lowercase) it was either run from the command line or backgrounded from within a script. To tell those two apart:

ps -o stat= -p $$

will contain a "+" if it was not backgrounded.

Here's a table:

Run          $$    $PPID
CL           S+    Ss
CL&          S     Ss+
Script       S+    S+
Script&      S     S
Script(&)    S     Ss
Script&(&)   S     NULL

Where (&) means the child script was backgrounded and & means the parent script (which is what "Script" refers to) that ran it was backgrounded. CL means command line. NULL means that ps output a null and that $PPID is "1".

From man ps:

   s    is a session leader
   +    is in the foreground process group

It should be noted that this answer is based on GNU ps, but the man pages for BSD (including OS X) indicate similar functionality. And GNU ps is a hybrid that includes BSD functionality, among others.

Mclendon answered 23/11, 2010 at 23:29 Comment(4)
"Script" in the table means the parent script, rather than the script that is the subject of the question.Mclendon
In case somebody needs this, I'm getting another result (Ssl or Rsl) for . file.sh / source file.sh in a fresh terminal session (Ctrl+Alt+T) and also when run from within a bash in a fresh terminal session (fresh terminal, then bash, then ./file.sh/bash file.sh -> Ss, source file.sh -> S) and from yet another layer (double-bash) only S in all cases.Improvisatory
Result 1: script 1: . script 2 script 2: read -rp "$(ps -o stat= -p $$) $(ps -o stat= -p $PPID)" result: Ss S+ (i.e., indicating CL) Result 2: $ script2: S+ Ss Result 3: $ read -rp "$(ps -o stat= -p $$) $(ps -o stat= -p $PPID)" Ss+ S (i.e., indicating ?) -1Compassionate
@bvargo: I'm sorry, but it's not clear what you're doing or what you're asking.Mclendon
T
12

I believe that what you really want to know is whether stdout is a terminal or not. If it is then you can (almost) safely assume that it's an interactive session. Try the following snippet:

if [[ -t 1 ]]; then
        echo "Terminal"
else
        echo "Not-a-terminal"
fi

The [[ -t 1 ]] command above is what checks if the file descriptor 1 (i.e. stdout) is a terminal or not.

EDIT:

Please note that this will indicate a non-terminal stdout if you pipe the output to some other program. In that case you might want a more versatile condition that will also check the standard input (file descriptor 0):

[[ -t 0 || -t 1 ]]
Tickle answered 23/11, 2010 at 23:29 Comment(4)
But you may want to know if it's run from a command line even when stdout is not to a terminal (such as when it's in a pipe).Mclendon
@Dennis: I don't think that there is a universal or magical solution for this. The PPID method is effective, but it would also fail if the script was called through another script.Tickle
Are you talking about a third level? Script calling script calling script? You could still use the technique in my answer to determine whether the script of interest was run from the command line.Mclendon
@Dennis: Yes, it seems so... checking for a + in the flags of either the script or its parent seems to do the job for most cases. Only exception seems to be in the case of a caller script being backgrounded. Perhaps a merging of all methods would be most versatile ?Tickle
G
1

This is a function I adapted from another post about this topic. It matches all the parent processes up to the top against the items listed in the $shells variable. When it is done $iscli is set to either 0 or 1. If it is set to 0, then you know it was run from a shell or what you consider shell enough for this purpose. If it is set to 1, then you know a program was involved that is not approved. I use this for scripts I want to run in a shell and via PHP when I want different output to be provided to each.

You will of course need to call the function initially without any parameters before you need $iscli to have a value.

function top_level_parent_pid {

    scriptname="${0##*/}"
    shells="^bash|^init|^screen|^sh|^ssh|^su|${scriptname}"

    pid=${1:-$$}
    pidname="`ps --no-heading -o %c -p ${pid}`"
    stat=($(</proc/${pid}/stat))
    ppid=${stat[3]}
    ppidname="`ps --no-heading -o %c -p ${ppid}`"
    isclitest="`echo "${ppidname}" | grep -iv -E "${shells}"`"

    until [ "${ppid}" -eq "1" ] || [ "${iscli}" = "1" ]; do

            if [[ -n "${isclitest}" ]]; then

                    iscli="1"

                 else

                    iscli="0"


                    top_level_parent_pid ${ppid}
            fi
    done
}
Gosse answered 9/3, 2012 at 3:33 Comment(0)
S
1

The Bash variable SHLVL is another option.

Whether this simple solution works for you depends on whether or not your two calling methods have different shell depths. When they do, this method is quick and easy!

I was just trying to solve this problem myself tonight, and didn't know how to do it in Bash. So I compared the outputs of "env" in both use cases using diff. I saw that SHLVL was set to 2 from the command line, and 3 when run from another shell script.

# Avoid clearing the screen when called from another script
if [ ! "$SHLVL" -gt "2" ]; then

    clear

fi

I used ! -gt 2 ("not greater than 2") to ensure my check wouldn't break in the case of a nested script-within-a-script situation, where the SHLVL increases to 4 and beyond.

If you check SHLVL on the actual command line (and from custom functions or aliases on the command line), the SHLVL variable will be set to 1. From non-interactive shells, such as cronjobs, I'm not sure if SHLVL will be set to 0 or 1.

Note: Posting on an old topic because it still shows up on web searches in 2024, and this simple solution hasn't been mentioned here. I'm not sure if Bash added SHLVL, or if it was a feature of Unix sh.

Strobila answered 6/8 at 9:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.