Robert P
Robert P

Reputation: 15968

How can I find out what script, program, or shell executed my Perl script?

How would I determine what script, program, or shell executed my Perl script?

Example: I might want to have human readable output if executed from shell (customized for each type of shell), a different type of output if called as a script from another perl script, and a machine readable format if executed from a program such as a continuous integration server.

Motivation: I have a tool that changes its output based on which shell executes it. I'd normally implement this behavior as an option to the script, but this tool's design doesn't allow for options. Other shells have environment variables that indicate what shell is running. I'm working on a patch to support Powershell, which has no such special variable.

Edit: Many of these answers happen to be linux specific. Unfortuantely, Powershell is for Windows. getppid, the $ENV{SHELL} variable, and shelling out to ps won't help in this case. This script needs to run cross-platform.

Upvotes: 7

Views: 3587

Answers (5)

Sinan Ünür
Sinan Ünür

Reputation: 118128

This is on Windows XP with PowerShell v2.0, so take it with a grain of salt.

In a cmd.exe shell, I get:

PSModulePath=C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\

whereas in the PowerShell console window, I get:

PSModulePath=E:\Home\user\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsP owerShell\v1.0\Modules\

where E:\Home\user is where my "My Documents" folder is. So, one heuristic may be to check if PSModulePath contains a user dependent path.

In addition, in a console window, I get:

!::=::\

in the environment. From the PowerShell ISE, I get:

!::=::\
!C:=C:\Documents and Settings\user

Upvotes: 1

x0n
x0n

Reputation: 52420

If you're running PowerShell 2.0 or above (most likely), you can infer the shell as a parent process by examining the environment variable %psmodulepath%. By default, it points to the system modules under %windir%\system32\windowspowershell\v1.0\modules; this is what you would see if you examine the variable from cmd.exe.

However, when PowerShell starts up, it prepends the user's default module search path to this environment variable which looks like: %userprofile%\documents\windowspowershell\modules. This is inherited by child processes. So, your logic would be to test if %psmodulepath% starts with %userprofile% to detect powershell 2.0 or higher. This won't work in PowerShell 1.0 because it does not support modules.

Upvotes: 1

flesk
flesk

Reputation: 7579

You use getppid(). Take this snippet in child.pl:

my $ppid = getppid();
system("ps --no-headers $ppid");

If you run it from the command line, system will show bash or similar (among other things). Execute it with system("perl child.pl"); in another script, e.g. parent.pl, and you will see that perl parent.pl executed it.

To capture just the name of the process with arguments (thanks to ikegami for the correct ps syntax):

my $ppid = getppid();
my $ps = `ps --no-headers -o cmd $ppid`;
chomp $ps;

EDIT: An alternative to this approach, might be to create soft links to your script, make the different contexts use different links to access your script and inspect $0 to build logic around that.

Upvotes: 5

Schwern
Schwern

Reputation: 164809

I would suggest a different approach to accomplish your goal. Instead of guessing at the context, make it more explicit. Each use case is wholly separate, so have three different interfaces.

  1. A function which can be called inside a Perl program. This would likely return a Perl data structure. This is far easier, faster and more reliable than parsing script output. It would also serve as the basis for the scripts.

  2. A script which outputs for the current shell. It can look at $ENV{SHELL} to discover what shell is running. For bonus points, provide a switch to explicitly override.

  3. A script which can be called inside a non-Perl program, such as your continuous integration server, and issue machine readable output. XML and/or JSON or whatever.

2 and 3 would be just thin wrappers to format the data coming out of 1.

Each is tailored to fit its specific need. Each will work without heuristics. Each will be far simpler than trying to guess the context and what the user wants.

If you can't separate 2 and 3, have the continuous integration server set an environment variable and look for it.

Upvotes: 3

Marius Kjeldahl
Marius Kjeldahl

Reputation: 6824

Depending on your environment, you may be able to pick it up from the environment variables. Consider the following code:

/usr/bin/perl -MData::Dumper -e 'print Dumper(\%ENV);' | grep sh

On my Ubuntu system, it gets me:

'SHELL' => '/bin/bash',

So I guess that says I'm running perl from a bash shell. If you use something else, the SHELL variable may give you a hint.

But let's say you know you're in bash, but perl is run from a subshell. Then try:

/bin/sh -c "/usr/bin/perl -MData::Dumper -e 'print Dumper(\%ENV);'" | grep sh

You will find:

      '_' => '/bin/sh',
      'SHELL' => '/bin/bash',

So the shell is still bash, but bash has a variable $_ which also show the absolute filename of the shell or script being executed, which may also give a valuable hint. Similarily, for other environments there will most probably be clues left in the perl %ENV hash that should give you valuable hints.

Upvotes: 1

Related Questions