Is it possible to write one script that runs in bash/shell and PowerShell?
It is possible; I don't know how compatible this is, but PowerShell treats strings as text and they end up on screen, Bash treats them as commands and tries to run them, and both support the same function definition syntax. So, put a function name in quotes and only Bash will run it, put "exit" in quotes and only Bash will exit. Then write PowerShell code after.
NB. this works because the syntax in both shells overlaps, and your script is simple - run commands and deal with variables. If you try to use more advanced script (if/then, for, switch, case, etc.) for either language, the other one will probably complain.
Save this as dual.ps1
so PowerShell is happy with it, chmod +x dual.ps1
so Bash will run it
#!/bin/bash
function DoBashThings {
wget http://www.example.org/my.script -O my.script
# set a couple of environment variables
export script_source=http://www.example.org
export some_value=floob
# now execute the downloaded script
bash ./my.script
}
"DoBashThings" # This runs the bash script, in PS it's just a string
"exit" # This quits the bash version, in PS it's just a string
# PowerShell code here
# --------------------
Invoke-WebRequest "http://www.example.org/my.script.ps1" -OutFile my.script.ps1
$env:script_source="http://www.example.org"
$env:some_value="floob"
PowerShell -File ./my.script.ps1
then
./dual.ps1
on either system.
Edit: You can include more complex code by commenting the code blocks with a distinct prefix, then having each language filter out its own code and eval
it (usual security caveats apply with eval), e.g. with this approach (incorporating suggestion from Harry Johnston ):
#!/bin/bash
#posh $num = 200
#posh if (150 -lt $num) {
#posh write-host "PowerShell here"
#posh }
#bash thing="xyz"
#bash if [ "$thing" = "xyz" ]
#bash then
#bash echo "Bash here"
#bash fi
function RunBashStuff {
eval "$(grep '^#bash' $0 | sed -e 's/^#bash //')"
}
"RunBashStuff"
"exit"
((Get-Content $MyInvocation.MyCommand.Source) -match '^#posh' -replace '^#posh ') -join "`n" | Invoke-Expression
Run bash script from Windows PowerShell
You should put the script as argument for a *NIX shell you run, equivalent to the *NIXish
sh myscriptfile
Running Powershell script in bash in single line
I'd like to offer an alternative approach to this, which is even more useful when you have a complex set of commands that you'd like to run as a one-liner.
Both PowerShell and PowerShell Core support an -EncodedCommand parameter.
The article above details the parameter as:
Accepts a Base64-encoded string version of a command. Use this parameter to submit commands to PowerShell that require complex, nested quoting. The Base64 representation must be a UTF-16LE encoded string.
With the EncodedCommand parameter, it's possible for us to pass a single Base64 string to the PowerShell or PWSH program and it will decode it and interpret it as though the original commands were run.
I've found this extremely useful in the past when dealing with nested statements and script blocks containing quotes and apostrophes.
As detailed above, the EncodedCommand parameter expects the commands to be converted to a UTF-16LE Base64 string.
There are various online convertors that will handle the conversion if you wish (one such converter can be found here: PowerShell Encoder)
As an example, let's pretend we want to run the following commands:
Import-Module ./myFile.psm1
Set-Values -Body 10
Upload-Values
$result = Confirm-Upload
if ($result -eq "OK") {
Write-Host 'Upload Successful'
} else {
Write-Host 'Upload Failed'
}
After conversion to the Base64 string, we end up with this string:
SQBtAHAAbwByAHQALQBNAG8AZAB1AGwAZQAgAC4ALwBtAHkARgBpAGwAZQAuAHAAcwBtADEADQAKAFMAZQB0AC0AVgBhAGwAdQBlAHMAIAAtAEIAbwBkAHkAIAAxADAADQAKAFUAcABsAG8AYQBkAC0AVgBhAGwAdQBlAHMADQAKACQAcgBlAHMAdQBsAHQAIAA9ACAAQwBvAG4AZgBpAHIAbQAtAFUAcABsAG8AYQBkAA0ACgBpAGYAIAAoACQAcgBlAHMAdQBsAHQAIAAtAGUAcQAgACIATwBLACIAKQAgAHsADQAKACAAIAAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAnAFUAcABsAG8AYQBkACAAUwB1AGMAYwBlAHMAcwBmAHUAbAAnAA0ACgB9ACAAZQBsAHMAZQAgAHsADQAKACAAIAAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAnAFUAcABsAG8AYQBkACAARgBhAGkAbABlAGQAJwANAAoAfQA=
To execute this, we'd simply do the following:
pwsh -EncodedCommand SQBtAHAAbwByAHQALQBNAG8AZAB1AGwAZQAgAC4ALwBtAHkARgBpAGwAZQAuAHAAcwBtADEADQAKAFMAZQB0AC0AVgBhAGwAdQBlAHMAIAAtAEIAbwBkAHkAIAAxADAADQAKAFUAcABsAG8AYQBkAC0AVgBhAGwAdQBlAHMADQAKACQAcgBlAHMAdQBsAHQAIAA9ACAAQwBvAG4AZgBpAHIAbQAtAFUAcABsAG8AYQBkAA0ACgBpAGYAIAAoACQAcgBlAHMAdQBsAHQAIAAtAGUAcQAgACIATwBLACIAKQAgAHsADQAKACAAIAAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAnAFUAcABsAG8AYQBkACAAUwB1AGMAYwBlAHMAcwBmAHUAbAAnAA0ACgB9ACAAZQBsAHMAZQAgAHsADQAKACAAIAAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAnAFUAcABsAG8AYQBkACAARgBhAGkAbABlAGQAJwANAAoAfQA=
Running powershell script inside a bash script
Are you trying
.\scriptfile.ps1
?
That should be
./scriptfile.ps1
But also, when invoking powershell from a bash script, you'll need to either run the pwsh command like
pwsh ./scriptfile.ps1
or the first line of your Powershell script file should be a shebang (interpreter directive) like:
#!/usr/bin/env pwsh
See How can I use a shebang in a PowerShell script?
How can I direct PowerShell 7 to run a bash (Ubuntu) script in WSL2 on Windows 10?
I have a feeling I'm missing something incredibly simple
Not really. In my experience, calling one scripting language from another is rarely what-I-would-call "simple" ;-).
That's a good attempt to figure it out from the help, but unfortunately there's just not enough detail in the wsl --help
output to figure it out from that.
As noted in the help, though, there are a few constructs available for running commands in a WSL instance using the wsl.exe
command.
wsl <commandline>
: Runs the command line as an argument to the default shell.wsl -e <command>
: Runs the command in place of the shell
Note that, for the sake of safety, I'm going to replace your command-lines with more benign versions ;-).
for f in $(find -xdev -type l); do echo $f "----" $(readlink $f); done
Side note: As someone once pointed out to me when I used find
with for
-- Why is looping over find's output bad practice?, so it would really be better off as find -xdev -type l | xargs -I % sh -c "echo -n % '---- '; readlink %"
, but we're going to leave it in a for
loop to demonstrate part of the problem here.
The following two tries fail because ...
wsl --cd ~ -c 'for f in $(find -xdev -type l); do readlink $f; done'
-c
is not a flag that is understood bywsl.exe
wsl.exe --cd ~ 'for f in $(find -xdev -type l); do readlink $f; done'
That would be the equivalent of runningcd ~; for f in $(find -xdev -type l); do readlink $f; done
, which won't work either, of course.Actually, in retrospect, this might work for you. It failed for me because my default shell is Fish, but
wsl
does seem to attempt to run the default shell with-c
for whatever command-line is passed in.It may have failed for you because you weren't setting the directory (via
--cd
) on the same command-line before calling it.wsl.exe --cd ~ -e 'for f in $(find -xdev -type l); do readlink $f; done'
And, while you didn't mention trying this, this particular one won't work either, since
-e
needs to be a single "command", but that's a full commandline with shell builtins such asfor
.
"Aaargggh!", you've been saying, right? Catch 22? You need -c
to get to the shell, but the wsl
command can't pass it.
So, we use:
wsl --cd ~ -e sh -c 'for f in $(find -xdev -type l); do echo $f "----" $(readlink $f); done'
That:
- Changes to the home directory (you can replace with the
$FOLDER
variable from PowerShell, of course) - Executes the
sh
shell (you could also use Bash (or any other shell or command) if you need any of its constructs) - Passes the commandline via
-c
to the shell. This is a fairly normal argument for most shells, POSIX or otherwise.
Note (from experience) that quoting rules between PowerShell and WSL/sh can get fairly "unruly". I find that as the example gets more complicated, it's often better to put the shell commands in a script inside WSL, which you then execute from PowerShell. For example, something like:
wsl --cd ~ -e sh -c "~/.local/bin/myscript.sh"
--cd
note
Using two separate wsl
commands, like in your --cd
example:
wsl.exe --cd $Folder
wsl.exe echo "Hello World" # Never executes
Assuming you were executing that via a script, the first line:
- Changes to the specified directory
- Runs the default shell, so you are then in an interactive session
If you then exit the shell (Ctrl+D or exit
), then you should see the output from the second command.
You can also see this interactively if you run it from PowerShell via:
wsl --cd ~ ; wsl echo Hello
Single script to run in both Windows batch and Linux Bash?
What I have done is use cmd’s label syntax as comment marker. The label character, a colon (:
), is equivalent to true
in most POSIXish shells. If you immediately follow the label character by another character which can’t be used in a GOTO
, then commenting your cmd
script should not affect your cmd
code.
The hack is to put lines of code after the character sequence “:;
”. If you’re writing mostly one-liner scripts or, as may be the case, can write one line of sh
for many lines of cmd
, the following might be fine. Don’t forget that any use of $?
must be before your next colon :
because :
resets $?
to 0.
:; echo "Hi, I’m ${SHELL}."; exit $?
@ECHO OFF
ECHO I'm %COMSPEC%
A very contrived example of guarding $?
:
:; false; ret=$?
:; [ ${ret} = 0 ] || { echo "Program failed with code ${ret}." >&2; exit 1; }
:; exit
ECHO CMD code.
Another idea for skipping over cmd
code is to use heredocs so that sh
treats the cmd
code as an unused string and cmd
interprets it. In this case, we make sure that our heredoc’s delimiter is both quoted (to stop sh
from doing any sort of interpretation on its contents when running with sh
) and starts with :
so that cmd
skips over it like any other line starting with :
.
:; echo "I am ${SHELL}"
:<<"::CMDLITERAL"
ECHO I am %COMSPEC%
::CMDLITERAL
:; echo "And ${SHELL} is back!"
:; exit
ECHO And back to %COMSPEC%
Depending on your needs or coding style, interlacing cmd
and sh
code may or may not make sense. Using heredocs is one method to perform such interlacing. This could, however, be extended with the GOTO
technique:
:<<"::CMDLITERAL"
@ECHO OFF
GOTO :CMDSCRIPT
::CMDLITERAL
echo "I can write free-form ${SHELL} now!"
if :; then
echo "This makes conditional constructs so much easier because"
echo "they can now span multiple lines."
fi
exit $?
:CMDSCRIPT
ECHO Welcome to %COMSPEC%
Universal comments, of course, can be done with the character sequence : #
or :;#
. The space or semicolon are necessary because sh
considers #
to be part of a command name if it is not the first character of an identifier. For example, you might want to write universal comments in the first lines of your file before using the GOTO
method to split your code. Then you can inform your reader of why your script is written so oddly:
: # This is a special script which intermixes both sh
: # and cmd code. It is written this way because it is
: # used in system() shell-outs directly in otherwise
: # portable code. See https://stackoverflow.com/questions/17510688
: # for details.
:; echo "This is ${SHELL}"; exit
@ECHO OFF
ECHO This is %COMSPEC%
Thus, some ideas and ways to accomplish sh
and cmd
-compatible scripts without serious side effects as far as I know (and without having cmd
output '#' is not recognized as an internal or external command, operable program or batch file.
).
Windows Shell scripting and powershell
- Does the script for writing batch file is similar to shell script in Linux/Unix?
No. Batch syntax is completely different from Linux/Unix shell script syntax. Check the homepage of the awesome Rob van der Woude for tutorials on batch.
- Is using Powershell is an advantage or learning is limited to windows server configuration/version?
Using PowerShell is most likely an advantage. PowerShell is installed by default since Windows Vista/Server 2008, but is also available for Windows XP and Server 2003. If you have to decide whether you want to learn batch or PowerShell: go for PowerShell. It's far more versatile than batch.
How to run a bash script on wsl with powershell?
To run the script on wsl
you simply invoke bash
> bash simple_script.sh
hi from simple script
To save it in a variable and have it run as a bash
script within wsl
or powershell
, there is no need for Get-Content
> $simple_script = bash /mnt/c/Users/user-name/path/to/simple_script.sh
> Write-Output $simple_script
hi from simple script
NOTE: Powershell has an alias mapping echo
to Write-Output
, so you could also use echo
> $simple_script = bash /mnt/c/Users/user-name/path/to/simple_script.sh
> echo $simple_script
hi from simple script
You can also grab the content if that was your initial aim.
> Get-Content simple_script.sh
#!/bin/bash
echo "hi from simple script"
> $content = Get-Content .\simple_script.sh
> Write-Output $content
#!/bin/bash
echo "hi from simple script"
Related Topics
Bash: Head & Tail Behavior with Bash Script
Docker MACvlan Network, Unable to Access Internet
Inotify - How to Find Out Which User Has Modified File
Linux - Without Hardware Soundcard, Capture Audio Playback, and Record It to File
How to Change the Host Type for a 'Canadian Cross' Compilation of Gcc with Crosstool-Ng
Force Cmake to Use the Full Library Path
Return Code When Os Kills Your Process
Segmentation Fault on Printf - Nasm 64Bit Linux
Count the Number of Times a Word Appears in a File
What Is Export_Symbol_Gpl in Linux Kernel Code
How to Determine the Process Memory Limit in Linux
Running Docker on Ubuntu: Mounted Host Volume Is Not Writable from Container
.Bashrc Not Read When Shell Script Is Invoked from Desktop Shortcut
Linux - Watch a Directory for New Files, Then Run a Script
Paste Two Text Lists (One List a File) into One List Separated by Semicolon