bash script into windows 10 powershell version - bash

My Python2.7 and pypy(using virtualenv) are all under windows 10 environment. From a README file in simulation software, below includes a 'for loop" script in Bash command. How would I convert below 'for loop" (three lines) Bash commands into equivalent Windows 10 Powershell commands?
"It's better to run several processes in parallel, for instance:
$ for i in {1..10}; do
$ time $pypy epto.py conf_epto/ $i > conf_epto/run-$i.log $
$ done
And then to obtain stats for all runs:
$pypy genStats.py conf_epto 10
This will output the stats to stdout and also generate dumps in gnuplot format."
I tried to run Powershell commands but face error:
(my-pypy-env) PS C:\Users\Acer\dev\pypy27home\my-pypy-env\SimpleDA-master> for ( $i = 1; $i -le 10; $i++) {
>> $pypy epto.py conf_epto/ $i > conf_epto/run-$i.log $
>> done}
At line:2 char:7
+ $pypy epto.py conf_epto/ $i > conf_epto/run-$i.log $
+ ~~~~~~~
Unexpected token 'epto.py' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken

foreach ($i in 1..10) {
Measure-Command { & $pypy epto.py conf_epto/ $i > conf_epto/run-$i.log } | Out-Host
}
& $pypy genStats.py conf_epto 10
foreach ($i in 1..10) loops over the elements of array 1..10, created with PowerShell's range operator (..); the body of compound statements such as foreach is always enclosed in { ... } in PowerShell.
Measure-Command is PowerShell's equivalent to Bash's time builtin; the command whose execution to measure is passed as a script block ({ ... }).
The command inside the script block basically works the same as in Bash, except that in Windows PowerShell > creates "Unicode" - UTF16-LE - files by default (in PowerShell Core it is UTF-8 without a BOM). > is an effective alias of the Out-File cmdlet; to use a different encoding, pipe to it and use the -Encoding parameter (e.g., ... | Out-File -Encoding utf8 conf_epto/run-$i.log), though note that in Windows PowerShell -Encoding utf8 invariably creates a UTF-8 file with a BOM.
time outputs its result directly to the terminal rather than to stdout; so does Out-Host; it bypasses PowerShell's stdout equivalent, the success [output] stream.
PowerShell requires use of &, the call operator, for executing a command whose name or path is specified as a variable ($pypy); this requirement stems from PowerShell having two distinct parsing modes.

Related

How can i use system() with rxrepl in WinCC OA?

I try to use:
string result;
string path = "C:/winccoa.projects/filters/bin/tools/rxrepl.exe";
string cmd = "'opcki' | " + path + " -s 'op' -r 'tata'";
system(cmd, result);
DebugN(result);
But in LogViewer i see nothing, instead ["tatacki"]
Why? What i doing wrong?
In PowerShell that works fine:
PS C:\> 'opcki' | C:/winccoa.projects/filters/bin/tools/rxrepl.exe -s "op" -r "tata"
tatacki
I'm assuming that WinCC's system() function targets cmd.exe, not powershell.exe (which is typical, because historically cmd.exe has been the default shell, and APIs are unlikely to change, so as to maintain backward compatibility).
Therefore, formulate your command for cmd.exe:
string cmd = "echo opcki | " + path + " -s op -r tata";
Not the use of echo to produce output and the omission of single-quoting ('...'), which cmd.exe doesn't recognize.
If embedded quoting were needed, you'd have to use `" inside "..." PowerShell strings (or use '...' PowerShell strings (whose content is taken literally) and embed " chars. as-is).

Bash script to PowerShell NET-SNMP

I have an issue changing an script I did in bash to powershell, the script is the following:
#! /bin/sh
for IPVAR in 172.27.41.202 172.27.41.203
do
TIEMPO=$(date +"%m-%d-%y")
FILENAME=${IPVAR}_${TIEMPO}
date +"%c" >> $FILENAME.txt
snmpget -v 2c -c public $IPVAR -mALL 1.3.6.1.4.1.41413.1.1.0 1.3.6.1.4.1.41413.1.4.0 1.3.6.1.4.1.41413.1.2.0 1.3.6.1.4.1.41413.1.3.0 1.3.6.1.4.1.41413.10.3.4.1.1.1 1.3.6.1.4.1.41413.10.3.4.1.2.1 1.3.6.1.4.1.41413.10.3.4.1.3.1 1.3.6.1.4.1.41413.10.3.4.1.4.1 1.3.6.1.4.1.41413.10.3.4.1.5.1 1.3.6.1.4.1.41413.10.3.4.1.6.1 1.3.6.1.4.1.41413.10.3.4.1.7.1 1.3.6.1.4.1.41413.10.3.4.1.8.1 1.3.6.1.4.1.41413.10.3.4.1.9.1 1.3.6.1.4.1.41413.10.3.4.1.10.1 >> $FILENAME.txt
done
In my Linux enviroment works fine but I installed NET-SNMP in a Windows Server because there is where we need the files to be but I can seem to make it work I did this:
$IPS = (10.96.90.2)
$TIEMPO = get-date -f yyyy-MM-dd
Foreach ($IPVAR in $IPS) {snmpget -v 2c -c public -m ALL $IPVAR 1.3.6.1.4.1.41413.1.1.0 1.3.6.1.4.1.41413.1.4.0 1.3.6.1.4.1.41413.1.2.0 1.3.6.1.4.1.41413.1.3.0 1.3.6.1.4.1.41413.10.3.4.1.1.1 1.3.6.1.4.1.41413.10.3.4.1.2.1 1.3.6.1.4.1.41413.10.3.4.1.3.1 1.3.6.1.4.1.41413.10.3.4.1.4.1 1.3.6.1.4.1.41413.10.3.4.1.5.1 1.3.6.1.4.1.41413.10.3.4.1.6.1 1.3.6.1.4.1.41413.10.3.4.1.7.1 1.3.6.1.4.1.41413.10.3.4.1.8.1 1.3.6.1.4.1.41413.10.3.4.1.9.1 1.3.6.1.4.1.41413.10.3.4.1.10.1 >> "$IPVAR_$TIEMPO".txt}
If I run only the "snmpget" command it works fine but I have troubles with the scripting part here.
Hope you can help me.
Regards,
Try the code below (this hasn't been tested as I don't have snmpget, but the method works with other command line apps):
$IPS = #('172.27.41.202', '172.27.41.203')
$IPS | ForEach-Object {
$snmpgetParams = #(
'-v', '2c' ,'-c' ,'public' ,'-m' ,'ALL', $_, '1.3.6.1.4.1.41413.1.1.0 1.3.6.1.4.1.41413.1.4.0 1.3.6.1.4.1.41413.1.2.0 1.3.6.1.4.1.41413.1.3.0 1.3.6.1.4.1.41413.10.3.4.1.1.1 1.3.6.1.4.1.41413.10.3.4.1.2.1 1.3.6.1.4.1.41413.10.3.4.1.3.1 1.3.6.1.4.1.41413.10.3.4.1.4.1 1.3.6.1.4.1.41413.10.3.4.1.5.1 1.3.6.1.4.1.41413.10.3.4.1.6.1 1.3.6.1.4.1.41413.10.3.4.1.7.1 1.3.6.1.4.1.41413.10.3.4.1.8.1 1.3.6.1.4.1.41413.10.3.4.1.9.1 1.3.6.1.4.1.41413.10.3.4.1.10.1'
)
$TIEMPO = Get-Date -f yyyy-MM-dd
$FILENAME="$_`_$TIEMPO`.txt"
snmpget #snmpgetParams | Set-Content $FILENAME -Force
}
Line 1 declares an array of IP addresses.
Line 2 starts a foreach loop which will iterate through each IP in the $IPS array.
Lines 3,4,5 create an array of parameters to pass to the snmpget command. The $_ parameter is the current IP address within the loop.
Line 7 sets the $TIEMPO variable with the date.
Line 8 sets the $FILENAME variable with the IP address, followed by an underscore, followed by the date. The backticks ` tell PowerShell to not treat the following characters as part of the preceding variable name. An example filename: 172.27.41.202_2016-08-31.txt
Line 10 calls the snmpget command. The #snmpgetParams 'splats' the parameter array. The output is piped into the Set-Content command, which, with the Force option creates or overwrites the file contents for that IP & date.
Line 11 closes the loop.

How do I pass a literal double quote from PowerShell to a native command?

I'd like to print a string literal in AWK / gawk using the PowerShell command line (the specific program is unimportant). However, I think I misunderstand the quoting rules somewhere along the line -- PowerShell apparently removes double quotes inside single quotes for native commands, but not when passing them to commandlets.
This works in Bash:
bash$ awk 'BEGIN {print "hello"}'
hello <-- GOOD
And this works in PowerShell -- but importantly I have no idea why the escaping is needed:
PS> awk 'BEGIN {print \"hello\"}'
hello <-- GOOD
This prints nothing in PowerShell:
PS> awk 'BEGIN {print "hello"}'
<-- NOTHING IS BAD
If this really is the only way of doing this in PowerShell, then I'd like to understand the chain of quoting rules that explains why. According to the PowerShell quoting rules at About Quoting Rules, this shouldn't be necessary.
BEGIN SOLUTION
The punchline, courtesy of Duncan below, is that you should add this function to your PowerShell profile:
filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') }
Or specifically for AWK:
filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }
END SOLUTION
The quotes are properly passed to PowerShell's echo:
PS> echo '"hello"'
"hello" <-- GOOD
But when calling out to an external "native" program, the quotes disappear:
PS> c:\cygwin\bin\echo.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
Here's an even cleaner example, in case you're concerned that Cygwin might have something to do with this:
echo #"
>>> // program guaranteed not to interfere with command line parsing
>>> public class Program
>>> {
>>> public static void Main(string[] args)
>>> {
>>> System.Console.WriteLine(args[0]);
>>> }
>>> }
>>> "# > Program.cs
csc.exe Program.cs
.\Program.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
DEPRECATED EXAMPLE for passing to cmd, which does its own parsing (see Etan's comment below):
PS> cmd /c 'echo "hello"'
"hello" <-- GOOD
DEPRECATED EXAMPLE for passing to Bash, which does its own parsing (see Etan's comment below):
PS> bash -c 'echo "hello"'
hello <-- BAD, WHERE DID THE QUOTES GO
Any solutions, more elegant workarounds, or explanations?
The problem here is that the Windows standard C runtime strips unescaped double quotes out of arguments when parsing the command line. PowerShell passes arguments to native commands by putting double quotes around the arguments, but it doesn't escape any double quotes that are contained in the arguments.
Here's a test program that prints out the arguments it was given using the C stdlib, the 'raw' command line from Windows, and the Windows command line processing (which seems to behave identically to the stdlib):
C:\Temp> type t.c
#include <stdio.h>
#include <windows.h>
#include <ShellAPI.h>
int main(int argc,char **argv){
int i;
for(i=0; i < argc; i++) {
printf("Arg[%d]: %s\n", i, argv[i]);
}
LPWSTR *szArglist;
LPWSTR cmdLine = GetCommandLineW();
wprintf(L"Command Line: %s\n", cmdLine);
int nArgs;
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if( NULL == szArglist )
{
wprintf(L"CommandLineToArgvW failed\n");
return 0;
}
else for( i=0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]);
// Free memory allocated for CommandLineToArgvW arguments.
LocalFree(szArglist);
return 0;
}
C:\Temp>cl t.c "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
t.c
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:t.exe
t.obj
"C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Running this in cmd we can see that all unescaped quotes are stripped, and spaces only separate arguments when there have been an even number of unescaped quotes:
C:\Temp>t "a"b" "\"escaped\""
Arg[0]: t
Arg[1]: ab "escaped"
Command Line: t "a"b" "\"escaped\""
0: t
1: ab "escaped"
C:\Temp>t "a"b c"d e"
Arg[0]: t
Arg[1]: ab
Arg[2]: cd e
Command Line: t "a"b c"d e"
0: t
1: ab
2: cd e
PowerShell behaves a bit differently:
C:\Temp>powershell
Windows PowerShell
Copyright (C) 2012 Microsoft Corporation. All rights reserved.
C:\Temp> .\t 'a"b'
Arg[0]: C:\Temp\t.exe
Arg[1]: ab
Command Line: "C:\Temp\t.exe" a"b
0: C:\Temp\t.exe
1: ab
C:\Temp> $a = "string with `"double quotes`""
C:\Temp> $a
string with "double quotes"
C:\Temp> .\t $a nospaces
Arg[0]: C:\Temp\t.exe
Arg[1]: string with double
Arg[2]: quotes
Arg[3]: nospaces
Command Line: "C:\Temp\t.exe" "string with "double quotes"" nospaces
0: C:\Temp\t.exe
1: string with double
2: quotes
3: nospaces
In PowerShell, any argument that contains spaces is enclosed in double quotes. Also the command itself gets quotes even when there aren't any spaces. Other arguments aren't quoted even if they include punctuation such as double quotes, and and I think this is a bug PowerShell doesn't escape any double quotes that appear inside the arguments.
In case you're wondering (I was), PowerShell doesn't even bother to quote arguments that contain newlines, but neither does the argument processing consider newlines as whitespace:
C:\Temp> $a = #"
>> a
>> b
>> "#
>>
C:\Temp> .\t $a
Arg[0]: C:\Temp\t.exe
Arg[1]: a
b
Command Line: "C:\Temp\t.exe" a
b
0: C:\Temp\t.exe
1: a
b
The only option since PowerShell doesn't escape the quotes for you seems to be to do it yourself:
C:\Temp> .\t 'BEGIN {print "hello"}'.replace('"','\"')
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}"
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
To avoid doing that every time, you can define a simple function:
C:\Temp> function run-native($command) { & $command $args.replace('\','\\').replace('"','\"') }
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And "another"'
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Arg[2]: And "another"
Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" "And \"another\""
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
2: And "another"
N.B. You have to escape backslashes as well as double quotes otherwise this doesn't work (this doesn't work, see further edit below):
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And \"another\"'
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Arg[2]: And \"another\"
Command Line: "C:\Temp\t.exe" "B EGIN {print \"hello\"}" "And \\\"another\\\""
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
2: And \"another\"
Another edit: Backslash and quote handling in the Microsoft universe is even weirder than I realised. Eventually I had to go and read the C stdlib sources to find out how they interpret backslashes and quotes:
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal "
N backslashes ==> N backslashes */
So that means run-native should be:
function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
and all backslashes and quotes will survive the command line processing. Or if you want to run a specific command:
filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }
(Updated following #jhclark's comment: it needs to be a filter to allow piping into stdin.)
You get different behavior, because you're using 4 different echo commands, and in different ways on top of that.
PS> echo '"hello"'
"hello"
echo is PowerShell's Write-Output cmdlet.
This works, because the cmdlet takes the given argument string (the text within the outer set of quotes, i.e. "hello") and prints that string to the success output stream.
PS> c:\cygwin\bin\echo '"hello"'
hello
echo is Cygwin's echo.exe.
This doesn't work, because the double quotes are removed from the argument string (the text within the outer set of quotes, i.e. "hello") when PowerShell calls the external command.
You get the same result if for instance you call echo.vbs '"hello"' with WScript.Echo WScript.Arguments(0) being the content of echo.vbs.
PS> cmd /c 'echo "hello"'
"hello"
echo is CMD's built-in echo command.
This works, because the command string (the text within the outer set of quotes, i.e. echo "hello") is run in CMD, and the built-in echo command preserves the argument's double quotes (running echo "hello" in CMD produces "hello").
PS> bash -c 'echo "hello"'
hello
echo is bash's built-in echo command.
This doesn't work, because the command string (the text within the outer set of quotes, i.e. echo "hello") is run in bash.exe, and its built-in echo command does not preserve the argument's double quotes (running echo "hello" in bash produces hello).
If you want Cygwin's echo to print outer double quotes you need to add an escaped pair of double quotes to your string:
PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"
I would've expected this to work for the bash-builtin echo es well, but for some reason it doesn't:
PS> bash -c 'echo "\"hello\""'
hello
Quoting rules can get confusing when you're calling commands directly from PowerShell. Instead, I regularly recommend that people use the Start-Process cmdlet, along with its -ArgumentList parameter.
Start-Process -Wait -FilePath awk.exe -ArgumentList 'BEING {print "Hello"}' -RedirectStandardOutput ('{0}\awk.log' -f $env:USERPROFILE);
I don't have awk.exe (does that come from Cygwin?), but that line should work for you.

UNIX format files with Powershell

How do you create a unix file format in Powershell? I am using the following to create a file, but it always creates it in the windows format.
"hello world" | out-file -filepath test.txt -append
As I understand, the new line characters CRLF make it to be a Windows format file whereas the unix format needs only a LF at the end of the line. I tried replacing the CRLF with the following, but it didn't work
"hello world" | %{ $_.Replace("`r`n","`n") } | out-file -filepath test.txt -append
There is a Cmdlet in the PowerShell Community Extensions called ConvertTo-UnixLineEnding
One ugly-looking answer is (taking input from dos.txt outputting to unix.txt):
[string]::Join( "`n", (gc dos.txt)) | sc unix.txt
but I would really like to be able to make Set-Content do this by itself and this solution does not stream and therefore does not work well on large files...
And this solution will end the file with a DOS line ending as well... so it is not 100%
I've found that solution:
sc unix.txt ([byte[]][char[]] "$contenttext") -Encoding Byte
posted above, fails on encoding convertions in some cases.
So, here is yet another solution (a bit more verbose, but it works directly with bytes):
function ConvertTo-LinuxLineEndings($path) {
$oldBytes = [io.file]::ReadAllBytes($path)
if (!$oldBytes.Length) {
return;
}
[byte[]]$newBytes = #()
[byte[]]::Resize([ref]$newBytes, $oldBytes.Length)
$newLength = 0
for ($i = 0; $i -lt $oldBytes.Length - 1; $i++) {
if (($oldBytes[$i] -eq [byte][char]"`r") -and ($oldBytes[$i + 1] -eq [byte][char]"`n")) {
continue;
}
$newBytes[$newLength++] = $oldBytes[$i]
}
$newBytes[$newLength++] = $oldBytes[$oldBytes.Length - 1]
[byte[]]::Resize([ref]$newBytes, $newLength)
[io.file]::WriteAllBytes($path, $newBytes)
}
make your file in the Windows CRLF format. then convert all lines to Unix format in new file:
$streamWriter = New-Object System.IO.StreamWriter("\\wsl.localhost\Ubuntu\home\user1\.bashrc2")
$streamWriter.NewLine = "`n"
gc "\\wsl.localhost\Ubuntu\home\user1\.bashrc" | % {$streamWriter.WriteLine($_)}
$streamWriter.Flush()
$streamWriter.Close()
not a one-liner, but works for all lines, including EOF. new file now shows as Unix format in Notepad on Win11.
delete original file & rename new file to original, if you like:
ri "\\wsl.localhost\Ubuntu\home\user1\.bashrc" -Force
rni "\\wsl.localhost\Ubuntu\home\user1\.bashrc2" "\\wsl.localhost\Ubuntu\home\user1\.bashrc"
Two more examples on how you can replace CRLF by LF:
Example:
(Get-Content -Raw test.txt) -replace "`r`n","`n" | Set-Content test.txt -NoNewline
Example:
[IO.File]::WriteAllText('C:\test.txt', ([IO.File]::ReadAllText('C:\test.txt') -replace "`r`n","`n"))
Be aware, this does really just replace CRLF by LF. You might need to add a trailing LF if your Windows file does not contain a trailing CRLF.

conditional execution (&& and ||) in powershell

There's already question addressing my issue (Can I get && to work in Powershell?), but with one difference. I need an OUTPUT from both commands. See, if I just run:
(command1 -arg1 -arg2) -and (command2 -arg1)
I won't see any output, but stderr messages. And, as expected, just typing:
command1 -arg1 -arg2 -and command2 -arg1
Gives syntax error.
2019: the Powershell team are considering adding support for && to Powershell - weigh in at this GitHub PR
Try this:
$(command -arg1 -arg2 | Out-Host;$?) -and $(command2 -arg1 | Out-Host;$?)
The $() is a subexpression allowing you to specify multiple statements within including a pipeline. Then execute the command and pipe to Out-Host so you can see it. The next statement (the actual output of the subexpression) should output $? i.e. the last command's success result.
The $? works fine for native commands (console exe's) but for cmdlets it leaves something to be desired. That is, $? only seems to return $false when a cmdlet encounters a terminating error. Seems like $? needs at least three states (failed, succeeded and partially succeeded). So if you're using cmdlets, this works better:
$(command -arg1 -arg2 -ev err | Out-Host;!$err) -and
$(command -arg1 -ev err | Out-Host;!$err)
This kind of blows still. Perhaps something like this would be better:
function ExecuteUntilError([scriptblock[]]$Scriptblock)
{
foreach ($sb in $scriptblock)
{
$prevErr = $error[0]
. $sb
if ($error[0] -ne $prevErr) { break }
}
}
ExecuteUntilError {command -arg1 -arg2},{command2-arg1}
Powershell 7 preview 5 has them.
I don't know why this was deleted with no notification or explanation.
https://devblogs.microsoft.com/powershell/powershell-7-preview-5/ This will give the output of both commands, as the question requested.
echo 'hello' && echo 'there'
hello
there
echo 'hello' || echo 'there'
hello
To simplify multistep scripts where doThis || exit 1 would be really useful, I use something like:
function ProceedOrExit {
if ($?) { echo "Proceed.." } else { echo "Script FAILED! Exiting.."; exit 1 }
}
doThis; ProceedOrExit
doNext
# or for long doos
doThis
ProceedOrExit
doNext
Update: PowerShell [Core] 7.0 introduced && and || support - see this answer.
Bash's / cmd's && and || control operators have NO Windows PowerShell equivalents, and since you cannot define custom operators, there are no good workarounds.
The | Out-Host-based workaround in Keith Hill's answer is a severely limited in that it can only send normal command output to the console (terminal), preventing the output from being sent on through the pipeline or being captured in a variable or file.
Find background information in this answer.
The simplest solution is to use
powershell command1 && powershell command2
in a cmd shell. Of course, you can't use this in a .ps1 script, so there's that limitation.
Little longer way is see below
try {
hostname
if ($lastexitcode -eq 0) {
ipconfig /all | findstr /i bios
}
} catch {
echo err
} finally {}
With Powershell 7.0 released, && and || are supported
https://devblogs.microsoft.com/powershell/announcing-powershell-7-0/
New operators:
Ternary operator: a ? b : c
Pipeline chain operators: || and &&
Null coalescing operators: ?? and ??=

Resources