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?

Sander Marechal
  • 22,978
  • 13
  • 65
  • 96
  • 1
    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" – laher Nov 23 '10 at 23:22
  • 1
    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. – Tom Anderson Nov 24 '10 at 16:50
  • 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. – Sander Marechal Nov 26 '10 at 00:53

3 Answers3

31

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.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 3
    "Script" in the table means the parent script, rather than the script that is the subject of the question. – Dennis Williamson Nov 24 '10 at 02:40
  • 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. – Peter Badida Apr 15 '22 at 02:07
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 ]]
thkala
  • 84,049
  • 23
  • 157
  • 201
  • 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). – Dennis Williamson Nov 23 '10 at 23:41
  • @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. – thkala Nov 23 '10 at 23:59
  • 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. – Dennis Williamson Nov 24 '10 at 00:34
  • @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 ? – thkala Nov 24 '10 at 00:59
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
}
Brian Mains
  • 50,520
  • 35
  • 148
  • 257