Command output printed instead of captured in Powershell - powershell

In general, I know how to capture the output of a command into a variable in Powershell:
> $output = python3 --version
> Write-Host $output
Python 3.7.0
Python 3 prints its version but it's captured into the variable $output and nothing is displayed on the console. As expected:
> $output = python3 --version
> Write-Host $output
Python 3.7.0
But in the case of Python 2, it doesn't work. Python 2 prints its version but its displayed on the console instead of captured and the variable $output is empty.
> $output = python2 --version
Python 2.7.15
> Write-Host $output
>
I have tried some alternative syntaxes but so far nothing helped:
> $output = (python2 --version) # use a subcommand
Python 2.7.15
> $output = "$(python2 --version)" # enclose the subcommand into a string
Python 2.7.15
If I use it in Write-Command, the output looks like the following:
> Write-Host "Python version: $(python2 --version)"
Python 2.7.15
Python version:
>
How is Python 2 printing its version that it's not captured into a variable and is it possible to capture it anyhow?

It's caused by Python 2 which prints version to stderr instead of stdout. Assignment of program call's output captures stdout only which is empty in this case. On the other side, stderr is printed to the console by default, that's why the $output variable is empty and the version is printed to the console. See below for details.
tl; dr:
# Redirect stderr to the success output stream and convert to a string.
$output = (python --version 2>&1).ToString()
# $? is now $True if python could be invoked, and $False otherwise
For commands that may return multiple stderr lines, use:
PSv4+, using the .ForEach() method:
$output = (python --version 2>&1).ForEach('ToString')
PSv3-, using the ForEach-Object cmdlet (whose built-in alias is %):
# Note: The (...) around the python call are required for
# $? to have the expected value.
$output = (python --version 2>&1) | % { $_.ToString() }
# Shorter PSv3+ equivalent, using an operation statement
$output = (python --version 2>&1) | % ToString
As Mathias R. Jessen notes in a comment on the question and you yourself clarified in terms of versions, python 2.x - surprisingly - outputs its version information to stderr rather than stdout, unlike 3.x.
The reason you weren't able to capture the output with $output = ... is that assigning an external program call's output to a variable only captures its stdout output by default.
With a PowerShell-native command, it captures the command's success output stream - see about_redirection.
The primary fix is to use redirection 2>&1 to merge PowerShell's error stream (PowerShell's analog to stderr, to which stderr output is mapped if redirected) into PowerShell's success output stream (the stdout analog), which is then captured in the variable.
This has two side effects, however:
Since PowerShell's error stream is now involved due to the 2>&1 redirection (by default, stderr output is passed through to the console), $? is invariably set to $False, because writing anything to the error stream does that (even if the error stream is ultimately sent elsewhere, as in this case).
Note that $? is not set to $False without the redirection (except in the ISE, which is an unfortunate discrepancy), because stderr output then never "touches" PowerShell's error output stream.
In the PowerShell console, when calling an external program without a 2> redirection, $? is set to $False only is if its exit code is nonzero ($LASTEXITCODE -ne 0).
Therefore, use of 2>&1 solves one problem (inability capture output) while introducing another ($? incorrectly set to $False) - see below for a remedy.
It isn't strings that get written to the success output stream, but [System.Management.Automation.ErrorRecord] instances, because PowerShell wraps every stderr output line in one.
If you only use these instances in a string context - e.g., "Version: $output", you may not notice or care, however.
.ToString() (for a single output line) and .ForEach('ToString') / (...) | % ToString (for potentially multiple output lines) undo both side effects:
They convert the [System.Management.Automation.ErrorRecord] instances back to strings.
They reset $? to $True, because the .ToString() / .ForEach() calls / the use of (...) around the command are expressions, which are themselves considered successful, even if they contain commands that themselves set $? to $False.
Note:
If executable python cannot be located, PowerShell itself will throw a statement-terminating error, which means that the assignment to $output will be skipped (its value, if any existed, will not change), and by default you get noisy error output and $? will be set to $False.
If python can be invoked and it reports an error (which is unlikely with --version), as indicated via nonzero exit code, you can inspect that exit code via the automatic $LASTEXITCODE variable (whose value won't change until the next call to an external program).
That something as simple as putting (...) around a command makes $? invariably return $True (except if a statement- or script-terminating error occurs) is taken advantage of in the above approaches, but is generally obscure behavior that could be considered problematic - see this discussion on GitHub.

Related

How can I identify if the executed ghostscript command returned an error in Perl?

I am having problem to get ghostscript's error return code to know if it failed or not,
I tried corrupting a PostScript file (messed it up on a file editor) and ran it, but I could not get a 1 or non-0 return.
After reading some manual and topics about it(hardly) here, this is what I currently do:
my $cmd = "/apps/gs/ghostpdl-9.52/bin/gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=ps2write -sstdout=%stderr -sOutputFile=$output -sPAPERSIZE=a4 -dFIXEDMEDIA -f $psfile";
logmsg("command: $cmd");
my $rc = `$cmd`;
logmsg("rc: $rc");
if($rc != 0){
...
}
but $rc doesn't have any value so I couldn't satisfy $rc != 0
Hopefully someone could help.
Thanks
If the command outputs an error message to STDERR (and many do), then you need to append 2>&1 to the command in order to redirect STDERR to STDOUT for capture by the backticks, like so:
my $output = `$cmd 2>&1`;
Then check $output for some kind of error or success message. I suggest printing it in order to see to see what it contains.
Note that the return value of backticks is the output of the command, not it's return status! If you really want the native return status of the command executed by the backticks, you can get that information using $? or ${^CHILD_ERROR_NATIVE}. See perldoc perlvar for details.

I have a system call that somehow is causing malformed header from script Bad header

I have a Perl CGI program. myperlcgi.pl
Within this program I have the following:
my $retval = system('extprogram')
extprogram has a print statement within.
The output from extprogram is being included within myperlcgi.pl
I tried adding
> output.txt 2>&1
to system call but did not change anything.
How do I prevent output form extprogram being used in myperlcgi.pl.
Surprised that stdout from system call is being used in myperlcgi.pl
The system command just doesn’t give you complete control over capturing STDOUT and STDERR of the executed command.
Use backticks or open to execute the command instead. That will capture the STDOUT of the command’s execution. If the command also outputs to STDERR, then you can append 2>&1 to redirect STDERR to STDOUT for capture in backticks, like so:
my $output = `$command 2>&1`;
If you really need the native return status of the executed command, you can get that information using $? or ${^CHILD_ERROR_NATIVE}. See perldoc perlvar for details.
Another option is to use the IPC::Open3 Perl library, but I find that method to be overkill for most situations.

Copy output and extract number from it in perl

I am working on a perl script in which I will run a command and get a output like : your id is <895162>. I will store this string and read the number from this string only . The problem is my main command will run in shell using the system command from perl .
like :
#ids.csh is "echo your id is <1123221>"
my $p = system ("./ids.csh 2>&1 > /dev/null");
print "$p\n";
$p =~ s/[^0-9]//g;
but the output is not copying to the $p file , Where I am going wrong ?
system runs a command but doesn't capture it. For that, you want qx/backticks:
my $p = `./ids.csh 2>/dev/null`;
As Len Jaffe said, you probably want to throw away stderr output (rather than displaying it to your screen or wherever your stderr is going), but not stdout (that contains the message you want to capture).
Note that when qx fails, it can do so for several different reasons and constructing a meaningful error message is not trivial. If you run into problems, consider using IPC::System::Simple's capture() instead.
You have redirected all of the output to /dev/null, which means that all of your output is being discarded.
I think you probably mean:
./ids.csh 2>/dev/null
Which will redirect stderr to /dev/null while leaving stdout unchanged.

Exit code from qx// within Perl's Simple HTTP Server is always -1?

I'm trying to use Perl's HTTP::Server::Simple::CGI module to run a command on a remote machine and return its output using qx//, but $? only ever seems to return -1 - whether the command succeeds or not (and I'm quite keen to know whether it succeeds or not).
In particular, I've changed the line setting $who in the example on the HTTP::Server::Simple::CGI documentation page http://metacpan.org/pod/HTTP::Server::Simple#EXAMPLE to this command:
my $who = qx/cat some_file/."\nReturns $?";
but $? displays as -1 whether the file exists or not. It's obviously basically working because the file's contents are shown if it exists. Contrast this with
perl -le 'print qx/cat some_file/."\nReturns $?"'
which shows it returning 256 on failure or 0 on success. And incidentally in the error case, it reports the failure to stderr - not something the Simple case does.
Is there something special I should be doing with $??
I'm on Perl 5.14.2, Ubuntu 12.04.
I tracked through HTTP::Server::Simple::CGI and it seems that it sets $SIG{'CHLD'}='IGNORE' as a precaution against zombie processes.
Setting local $SIG{'CHLD'}='DEFAULT' immediately before my invocation of qx// allows me access to the return code.

What is it the executive command put between double slashes for in perl?

I'm trying to build gccg(gccg.sourceforge.net) on my mac pro with mountain lion installed.
I've tried to install every things according to the manual, but it gave me some error like:
gccg_package.pl: gccg_package requires gunzip at gccg_package.pl line 1224.
Then I'm trying to root this problem, and recursively find the root of this:
($_ = `gunzip --version 2>&1`) && /gunzip / or die "$0: gccg_package requires gunzip";
I output the each part of the operands with this && operators, and make sure that "/gunzip /" returns false here. The problem here is that I don't what does those statements mean here?
Sorry for my newbie with perl, I've tried to find some tutorial but seems it's not so easy to search anyway.
In short, it causes the program to end if the output of gunzip --version 2>&1 doesn't include the string gunzip.
$_ = `gunzip --version 2>&1` executes the shell command in the backticks, captures its output, stores the output in $_, and returns true if output was returned.
/gunzip / is a match operator. It returns true if content of $_ contains the string gunzip.
die throws an exception.
I am a bit rusty in perl but:
the first part ($_ = `gunzip --version 2>&1`) executes gunzip --version 2>&1 and stores the output in $_
the && is used as a short cut operated, if the left hand side returns true the right hand side will be executed.
the right hand side /gunzip / is a regular expression, in perl if there is no variable (scalar)
declared for an regex it will be performed on the default variable $_
So this will just search the oputput of the first part for "gunzip " and return true if it contains it
The last part or die "$0: gccg_package requires gunzip"; will only be executed if one of the 2 previous parts returned false.
for more information on Short Circuit operations check out http://www.perlmonks.org/?node_id=301355
Basically, this line tells you that you should install gunzip.
gunzip is a Unix command ; what this lines does is that it first assigns to the variable $_ the result of the gunzip --version part ; then it runs the regular expression search /gunzip / on that variable, to check that the result of the command contains gunzip (as opposed to gunzip, without a space, which would also be found if the command's result was gunzip: command not found). And finally, if the search is not successful, e.g. gunzip was not executed for one reason or the other, the program stops (die) after printing the message that you read.