paulski82 has asked for the wisdom of the Perl Monks concerning the following question:

In summary, I need a portable way to test from inside my Perl script if the script was run using a login (su - <username>) vs non-login (su <username>) where the script is the shell of the user I'm trying to su to.

I can do this easily in C using the following code:

/* testsh2.c */ #include <stdlib.h> #include <stdio.h> int main(int argc, char** argv) { int i = 0; for (;i < argc; i++) { printf("argv[%d]: %s\n", i, argv[i]); } exit(0); }

Password file is as follows (this is a Red Hat Enterpise Linux 5 server). The /tmp/testsh2 is the C code above compiled.

root@aster /tmp# cat /etc/redhat-release Red Hat Enterprise Linux Server release 5.8 (Tikanga) root@aster /tmp# cat /etc/passwd| grep paultest paultest:x:401:401::/home/paultest:/tmp/testsh2

Here's the output when I run the C program.

root@aster /tmp# su - paultest argv[0]: -testsh2 root@aster /tmp# su paultest argv[0]: testsh2

As you can see, I can just test if the first character argv[0] is a '-'.

I'm having trouble doing the same under Perl.

The variable $0 doesn't have a '-' at the start of it under Perl and the ARGV array doesn't have the program name as the first element like in C.

Is there any way to get this information under Perl?

NOTE: I have searched the archives for things like 'login shell', but there are too many hits, as you'd expect.

Replies are listed 'Best First'.
Re: How do I test if my PERL script was run using a login vs a non-login shell
by aaron_baugher (Curate) on May 23, 2012 at 04:14 UTC

    The variable $^X contains the name of the perl executable, and perlvar says it comes from C's argv[0], so that seems like it should be the answer. It doesn't seem to include the hyphen like it does in C for some reason, though.

    As an aside, on FreeBSD I do get the hyphen (in C) in a login shell and not otherwise, but I get "su" and "-su" instead of the name of the shell like you get in Linux. So the hyphen part may be portable, but it doesn't look like the rest is.

    One option would be to look for an environment variable that's set in a login shell and not otherwise. Hard to say how portable that would be, though. It's pretty hard to make anything universally portable when you're talking about the shell, since there are so many different ones, even on the same OS.

    Aaron B.
    Available for small or large Perl jobs; see my home node.

      The variable $^X contains the name of the Perl executable, and perlvar says it comes from C's argv[0], so that seems like it should be the answer. It doesn't seem to include the hyphen like it does in C for some reason, though.

      I did search the perlvar man pages and noted the same as you that $^X doesn't have the starting '-'. I'm beginning to think that Perl is not the language to be writing a shell in. I could write the C code but it's way harder to manage.

      As an aside, on FreeBSD I do get the hyphen (in C) in a login shell and not otherwise, but I get "su" and "-su" instead of the name of the shell like you get in Linux. So the hyphen part may be portable, but it doesn't look like the rest is.

      As long as I can get the '-' I can fudge the rest.

      One option would be to look for an environment variable that's set in a login shell and not otherwise. Hard to say how portable that would be, though. It's pretty hard to make anything universally portable when you're talking about the shell, since there are so many different ones, even on the same OS.

      My script will be the shell so I'm not sure of any variable that's going to be reliable. Unless when Perl is called it runs under a shell itself and I can make the decision based on the shell Perl runs under. That may be good enough.

      I may have to do a hack like:

      If bash and VAR1 is set assume login OR If ksh and VAR2 is set assume login.

      But that's kinda ugly and I may just end up writing the whole shell in C.

        You can find out what environment variables are set for your shell in different cases by making your shell:

        #!/usr/bin/perl print "$_: $ENV{$_}\n" for keys %ENV;

        Aaron B.
        Available for small or large Perl jobs; see my home node.

        Unless when PERL is called it runs under a shell itself and I can make the decision based on the shell PERL runs under.

        This is somewhat off the topic, but could you please stop shouting the language's name every time you use it?

Re: How do I test if my Perl script was run using a login vs a non-login shell
by Mr. Muskrat (Canon) on May 23, 2012 at 18:53 UTC

      I actually want to know both these things since shells behave differently based on whether they are login or non-login, and/or interactive, non-interactive. See the following from the bash man page for an example (bash 3.2):

      When bash is invoked as an interactive login shell, or as a non +-interactive shell with the --login option, it first reads and execu +tes commands from the file /etc/profile, if that file exists. A +fter reading that file, it looks for ~/.bash_profile, ~/.bash_login, +and ~/.profile, in that order, and reads and executes commands from + the first one that exists and is readable. The --noprofile option +may be used when the shell is started to inhibit this behavior. When a login shell exits, bash reads and executes comman +ds from the files ~/.bash_logout and /etc/bash.bash_logout, if the fi +les exists. When an interactive shell that is not a login shell is started, + bash reads and executes commands from ~/.bashrc, if that file exis +ts. This may be inhibited by using the --norc option. The --r +cfile file option will force bash to read and execute commands from f +ile instead of ~/.bashrc. When bash is started non-interactively, to run a shell script, +for example, it looks for the variable BASH_ENV in the environme +nt, expands its value if it appears there, and uses the expanded +value as the name of a file to read and execute. Bash behaves as if +the following command were executed: if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi but the value of the PATH variable is not used to search for th +e file name.

      So your link is useful for solving the interactive, non-interactive part of the puzzle, but I'm still keen to know how to solve the login/non-login part of the puzzle.

      Thanks for the link.

        Here's a solution I came up with in case it helps someone else:
        my $p; my login_shell = 0; # NOTE: PERL ARGV[0] does not supply the '-' suffix like in # C, which would allow us to do a reliable test to check if # we are running inside a login shell. So we need to guess # this using some hacks. # HACK 1 # This is a hack to guess whether we are an interactive # shell in an SSH session based on the SSH_TTY environment # variable being set or not. # NOTE: If people are accessing the system via other # mechanisms like telnet etc. this won't catch those cases. # NOTE: OpenSSH's sshd will prefix shell with '-' for # interactive shells but not for non-interactive shells # e.g. shells called using -c <command>. # For the gory details see: # session.c:void do_child(Session *, const char *); if ($ENV{'SSH_TTY'}) { $login_shell = 1; } # End if. # HACK 2 # This is a hack to check whether this shell was called via # su and the shell was a login shell. Get the PID of the # parent process of current instance of this process. my $parent_pid = getppid(); } # Get the executable and args of the parent process. my $process = ""; my $command = "/bin/ps -eo pid,args"; open(FP, "$command|") || die("$!\n"); while (my $proc_line = <FP>) { chomp($proc_line); $proc_line =~ s/^\s+//g; $proc_line =~ s/\s+$//g; my ($pid, $cmd, @list) = split(/\s+/, $proc_line); $cmd = (($p = rindex( $cmd, '/' )) >= 0) ? substr($cmd, $p + 1) : $cmd; my $args = ''; foreach (@list) { $args .= "$_ "; } $args =~ s/\s+$//g; if ($pid eq $parent_pid) { $process = "$cmd $args"; chomp($process); last; } } close(FP); # If the parent process was an su command with a login # shell, then this shell should be a login shell too. if ($process =~ /^su /) { if (($process =~ / - /) or ($process =~ / -[A-z]*l[A-z]* /) or ($process =~ / --login /)) { $login_shell = 1; } }
Re: How do I test if my PERL script was run using a login vs a non-login shell
by Anonymous Monk on May 23, 2012 at 03:32 UTC

    What is the difference?

    Call perl from that program, giving it whatever indicator you need  exec "perl", "...file.pl", "--", "--withfoobar";

      The difference is that this login shell will be installed on 600 odd servers with different operating systems and I don't want to manage compiling C code across different operating systems.

        The difference is that this login shell will be installed on 600 odd servers with different operating systems and I don't want to manage compiling C code across different operating systems.

        :) You'll notice my question came before my suggestion, it wasn't about my suggestion

        Let me elaborate, what is the difference between using a login vs a non-login shell? What effect does it have upon the execution of a program? Does it set some variables?

        I doubt the only effect is a dash in ARGV

        You could probably examine some perlvar or some POSIX call, but you'd have to know more about what su does -- I don't know too much about su.