#!/usr/bin/perl # A lesson from the School of Hard Knocks. # # Any of the 3 ways of executing a command, system(), open(...|), or # backticks, will launch a shell and run ones .profile & .kshrc if # there are shell characters in the command string. For example, # system("ls .kshrc") Does not invoke .kshrc # but system("ls .kshrc; ls .profile") Does invoke .kshrc # and system("ls .kshrc | grep foo") Also calls your .kshrc # # The problem is, it does this without the benefit of a console, # so if you had some command like a who or whoami in your .kshrc # (like I did to set my PS1 string and check for extra stayalives # and see where I'm logged in from), then you get the following # errors, which are lots of "fun" to track down. # process not attached to terminal # Usage: who [-AabdHilmpqrsTtuw] [am {i,I}] [file] # A Accounting entries # a All (AbdHlprTtu) options # b Boot time # d Dead processes # It was the whoami command inside my .kshrc that was complaining. sub runcmd_via_system { # The Programming Perl (camel) book has this to say about using System, # Avoid unnecessary system calls. The system operator has to fork a # subprocess and execute the program you specify. Or worse, execute # a shell to execute the program you specify. This can easily execute # a million instructions. local($command) = @_; # I.E. the passed arguments to this subroutine local($status); return(0) if ($noexec); # Special test-run-only-Don't-execute-any-commands flag. print("runcmd_via_system: Running $command\n") if ($verbose); $status = system($command); # There's another distinction between giving the system call, one string versus # a list of strings. From the "Learning Perl" (llama) book, page 144 says # "Giving system a list rather than giving it a simple string saves one # shell process as well, so do this when you can. (Actually, when the # one-argument form of system is simple enough, Perl itself optimizes # away the shell invocation entirely, calling the resulting program # directly as if you had used the multiple-argument invocation.)" # # But I tried to see these two different actions, but couldn't see any difference # in these invocations. They all looked alike, process-wise (using ps -ef). # $status = system("sleep","1000"); # Multiple arguments, avoids a subshell. # $status = system("sleep 1000"); # Single argument, spawns a subshell, except # something this simple, gets optimized. # # In the "Programming Perl" (camel) book, there's a better explanation on page 191 # under the open section, that says # Any pipe command containing shell metacharacters is passed to /bin/sh for # execution; otherwise it is executed directly by Perl. # # Beware though if you try to verify this action. This for example, # $status = system("sleep \$junk"); # or $status = system("ps -ef | grep pts/2"); # Apparently get optimized too, but they really don't. Perl really does call # /usr/bin/sh -c sleep $junk # which you can verify with your own /usr/bin/sh, ala # #!/bin/ksh # echo "This is my own sh, called with parms >$@<" # The way the real /usr/bin/sh apparently works is, it execs the last command piece, # replacing itself. You can see this in action with something like # $status = system("sleep 1000;ps -ef | grep pts/2"); # Run this then from another window, do a ps -ef. Note the PIDs & starting times. # For example (sorted from parent to children), # jasper 20622 20322 0 Jan 02 pts/2 0:00 /bin/ksh # jasper 16114 20622 0 16:21:57 pts/2 0:00 ksh # jasper 29730 16114 0 16:43:23 pts/2 0:00 perl raj # jasper 23144 29730 0 16:43:23 pts/2 0:00 sh -c sleep 1000;ps -ef | grep pts/2 # jasper 23900 23144 0 16:43:23 pts/2 0:00 sleep 1000 # Now kill the sleep process, and look closely at the output of the ps -ef command. # In this case, it was # jasper 20622 20322 0 Jan 02 pts/2 0:00 /bin/ksh # jasper 16114 20622 0 16:21:57 pts/2 0:00 ksh # jasper 29730 16114 0 16:43:23 pts/2 0:00 perl raj # jasper 23144 29730 0 16:43:23 pts/2 0:00 grep pts/2 # jasper 23902 23144 6 16:44:13 pts/2 0:00 ps -ef # See what /usr/bin/sh did? It spawned a new process for the ps -ef, but replaced (exec'd) # itself with the last piece of the command, the grep pts/2. Note the starting times for # the last two processes. # $status = system("ps", "-ef", "|", "grep", "pts/2"); # This is coded wrong & gives an error. # With the Perl system call, you cannot get the output of the command # (unless you play redirect games with STDIO & STDERR). It all goes out # to this program's STDOUT & STDERR (typically, the console). Thus using # system, probably isn't a good, generic way of executing a command. # # The system call returns a 2-byte value, a 1-byte exit status (return code) and # a 1-byte signal. Further details below. $save_errno = $!; # Always zero as far as I can tell. Useless. $exit_status = $status >> 8; $signal = $status & 0xff; printf("runcmd_via_system: Exit status is %#02x and signal is %#02x:\n", $exit_status,$signal); if ($exit_status == 0 && $signal == 0) { # I.E. if $status == 0 print "runcmd_via_system: Command finished normally & had exit value = 0\n"; } elsif ($signal == 0) { print "runcmd_via_system: \"$command\" command failed with exit status = $exit_status. \$! =>$save_errno<\n"; } else { # We've got a non-zero signal. if ($signal & 0x80) { # x80 of the signal (decimal 128) indicates a core file. print "runcmd_via_system: \"$command\" produced a core file. \$! =>$save_errno<\n"; $signal &= 0x7f; # The other 7 bits is the real signal, e.g. if kill -3 } # $signal was equal to hex 83, but is now just hex 3. # By the way, a Ctrl-c out of a running program, gives a $signal == 2. print "runcmd_via_system: \"$command\" command got signal $signal. \$! =>$save_errno<\n"; } return($?); # $? after a system call, is the same thing the system call returns, # in our case, $status. } sub runcmd_via_pipe { # @_ is an array, a list of things. For example, # - if this program (raj) is called via raj ls -l raj # - and this routine is called via $morestuff = runcmd_via_pipe(@ARGV); # - then from inside this routine, @_ is a 3-element array, with # @_[0] =>ls< # @_[1] =>-l< # @_[2] =>raj< # @_[3] =>< # print("runcmd_via_pipe: \@\_ =>@_\n"); # print(" \@_[0] =>@_[0]<\n"); # print(" \@_[1] =>@_[1]<\n"); # print(" \@_[2] =>@_[2]<\n"); # print(" \@_[3] =>@_[3]<\n"); # But when you do an assignment like # local($command) = @_; # then $command only picks up @_[0], not the whole array. # This # local $command = @_; # isn't right. This returns only the length of @_, which is 3 in our example. # One way to get the whole array, separated by spaces, is to force a scalar context. local $command = "@_"; # The passed args to this subroutine, all in 1 string. return(0) if ($noexec); # Special test-run-only-Don't-execute-any-commands flag. print("runcmd_via_pipe: Running $command\n") if ($verbose); $?=0; # Proof to myself that these aren't set. If I didn't do this, I $!=0; # picked up whatever they happened to be prior to getting here. # Although I use "... or die ..." ('cause the book said to, that's why), I can't # cause it to actually fail & die, so I don't know when that would ever execute. open(CMDPIPE, "$command |") or die "runcmd_via_pipe: Error opening $command "; $status = $?; # Unlike the system call above, $? is not set with open. $save_errno = $!; # Also $! is not set as far as I can tell. # As a matter of fact, as far as I can tell, there's # no way using open/pipes, to collect any kind of # exit status. print "runcmd_via_pipe: \$status = $status and \$save_errno =>$save_errno<\n"; if ($status != 0) {print "runcmd_via_pipe: \$status = $status\n"}; if ($save_errno != 0) {print "runcmd_via_pipe: \$save_errno =>$save_errno<\n"}; while ($inputline = ) { chop($inputline); print "runcmd_via_pipe: $inputline\n"; } } sub runcmd_via_backticks { local $command = "@_"; # The passed args to this subroutine, all in 1 string. local $command_output; return(0) if ($noexec); # Special test-run-only-Don't-execute-any-commands flag. print("runcmd_via_backticks: Running $command\n") if ($verbose); $string_command_output = `$command`; $status = $?; # Like the system() call above, but unlike the open(), # when using backticks, $? is set and checkable. # The "Programming Perl" (camel) book says on page 134, # that $? is "the status returned by the last pipe close, # backtick command, or system operator." $save_errno = $!; # This is still always zero, though. Hmmmm. print "runcmd_via_backticks: \$status = $status and \$save_errno =>$save_errno<\n"; if ($status != 0) {print "runcmd_via_backticks: \$status = $status\n"}; if ($save_errno != 0) {print "runcmd_via_backticks: \$save_errno = $save_errno\n"}; print "runcmd_via_backticks: String Context: =>$string_command_output<\n"; # For commands that you know are going to return a single line, this construct is ok, # but for multiple-line output, you get all output lines in one big string. # Even so, this is ok if say, you intend to parse out one thing, i.e. # ($rootvg_PP_size) = `lsvg rootvg` =~ /PP SIZE:\s*(\d*)/; # # Big, Confusing Parenthetical Comment Following: # Actually, the above line, took me some time to get right. Although this # print `lsvg rootvg` =~ /PP SIZE:\s*(\d*)/; # worked and if 32 MB PP size, returns 32 (with no CR. You have to do this # print `lsvg rootvg` =~ /PP SIZE:\s*(\d*)/ , "\n"'; # to get the CR), when I tried assigning that same line to a variable, ala # $rootvg_PP_size = `lsvg rootvg` =~ /PP SIZE:\s*(\d*)/; # print "$rootvg_PP_size\n"; # I only got 1. # # To get what you want, you can force a list context by # ($rootvg_PP_size) = `lsvg rootvg` =~ /PP SIZE:\s*(\d*)/; # print "$rootvg_PP_size\n"; # This works and returns, assigns, and prints 32, like we want. I learned this # trick from page 51 of the "Learning Perl" (llama) book, where it says by specifying # a list on the left hand side of the assignment equal sign, we get the first element of # the list, silently discarding any other elements there may be. But that part # about "silently discarding other elements" doesn't test true either. What is # returned by the right-hand side? A list, yes, but how many elements are in the # list? As many as there are pattern matches? What if I had two matches, say we # wanted the number of FREE PPs as well, we could have # ($rootvg_PP_size,$free_PPs) = `lsvg rootvg` =~ /PP SIZE:\s*(\d*).*FREE PPs:\s*(\d*)/s; # print "There were $free_PPs, $rootvg_PP_size-MB PPs free in rootvg.\n"; # This is correct (note the ending "s" on that first line), but when I "broke" it # by removing the parenthesis and only having one variable on the left, ala # $rootvg_PP_size = `lsvg rootvg` =~ /PP SIZE:\s*(\d*).*FREE PPs:\s*(\d*)/s; # I still got only 1, not 2. Hmmmmm. Doesn't that say the list is only 1 element # long? No, 'cause if I say # @tar=`lsvg rootvg` =~ /PP SIZE:\s*(\d*).*FREE PPs:\s*(\d*)/s; # this @tar array seems to be 2 elements long. I don't understand. # # Why? Because on the left hand side of the assignment operator (=), # is a scalar, thus the right side is evaluated in a scalar context. # # Evidently, the backticks with the =~ operator is a list, and # I was forcing a scalar context by assigning it to a string variable, so I was # getting the number of elements in the list, 1 in this example?? I'm not sure, # 'cause as I was trying to validate this, I read that the =~ operator returns # the number of remembered matches, also 1 in this example. Aha, so I thought, # what if I had two matches, say wanted the number of FREE PPs as well, we could # have # ($rootvg_PP_size,$free_PPs) = `lsvg rootvg` =~ /PP SIZE:\s*(\d*).*FREE PPs:\s*(\d*)/s; # print "There were $free_PPs, $rootvg_PP_size-MB PPs free in rootvg.\n"; # which is correct (note the ending "s" on that first line), but if when I "broke" # it by removing the parenthesis, # $rootvg_PP_size = `lsvg rootvg` =~ /PP SIZE:\s*(\d*).*FREE PPs:\s*(\d*)/s; # I still got only 1. Hmmmmm. But if I did this, # $count=($rootvg_PP_size) = `lsvg rootvg` =~ /PP SIZE:\s*(\d*)/; # count would be 1, or # $count=($rootvg_PP_size,$free_PPs) = `lsvg rootvg` =~ /PP SIZE:\s*(\d*).*FREE PPs:\s*(\d*)/s; # count would be 2. But exactly why, what the difference in context is, I dunno. # # End Big Parenthetical Comment: # On the other hand, we can loop through each line individually like so # foreach $_ (`$command`) { # # With backticks, each line has a new line appended to it, so get rid of it. # chomp; # chomp is a "safer" version of chop. # print "runcmd_via_backticks: List Context =>$_<\n"; # # Do what we wish with each line. # } # But this doesn't allow us to check the exit status, so we have to do it this way. @command_output_lines = `$command`; chomp(@command_output_lines); # In list mode, chomp works on each element. $status = $?; $save_errno = $!; if ($status != 0) {print "runcmd_via_backticks: \$status = $status\n"}; if ($save_errno != 0) {print "runcmd_via_backticks: \$save_errno = $save_errno\n"}; foreach $_ (@command_output_lines) { print "runcmd_via_backticks: List Context =>$_<\n"; # Do what we wish with each line. } } sub getvgppsize { local($vgname) = @_; local($inputline); # Get volume group information open(TPIPE, "lsvg $vgname |"); while ($inputline = ) { chop($inputline); last if ($inputline =~ /PP SIZE:/); } close(TPIPE); if (!defined($inputline)) { print "getvgppsize: couldn't get PP information for $vgname\n"; return(0); } $inputline =~ /PP SIZE:\s*(\d*)/; return($1); } sub lsitab { # This makes the $ident variable, local in scope, and initializes it # to the passed parameter, @_, which is the inittab entry to look for. local($ident) = @_; # This builds up our lsitab command in a global(!) variable called # $command. (You'd think that after taking pains to make the $ident # variable local, that they would have done the same for this $command # variable as well.) By the way, this could all be done in one line, # i.e. return(runcmd_via_system(sprintf("lsitab %s", $ident))); $command = sprintf("lsitab %s", $ident); return(runcmd_via_system($command)); } # Start of mainline. $verbose=1; # 0 is false. Anything else is true. $noexec=0; # If true, won't really execute any commands. $stuff = lsitab($ARGV[0]); print "Rick, the lsitab routine returned >$stuff<\n"; print "\n---------------------------------------------------------------------\n"; $morestuff = runcmd_via_pipe(@ARGV); print "Rick, the runcmd_via_pipe routine returned >$morestuff<\n"; print "\n---------------------------------------------------------------------\n"; $morestuff = runcmd_via_backticks(@ARGV); print "Rick, the runcmd_via_backticks routine returned >$morestuff<\n";