How to run a bash script on wsl with powershell?
Asked Answered
T

4

12

On my current directory on Windows, I have the following script file simple_script.sh:

#!/bin/bash
echo "hi from simple script"

I wish to run this script on wsl via the powershell command line.

Using the wsl command, I could not find a way to tell wsl to invoke the script code.

The following command works (I think)

wsl bash -c "echo hi from simple script"

However when trying to load the script content into a variable and running it does not work as expected:

$simple_script = Get-Content ./simple_script.sh
wsl bash -c $simple_script

Fails with:

bash: -c: option requires an argument

I tried a few variants. using Get-Content with the -Raw flag seems to make the first word in the string to print (but not the whole string). commands that don't contain '"' characters seems to work sometimes. But I haven't found a consistent way.

A similar looking question doesn't seem to work directly with the wsl, and doesn't seem to run a script file that resides on the Windows file system.

Tizzy answered 7/5, 2022 at 10:27 Comment(0)
B
23

The robust and efficient way to execute your shebang-line-based shell script from Windows is via wsl.exe -e

wsl -e ./simple_script.sh # !! ./ is required

Note:

  • Without ./ to explicitly indicate that the executable is located in the current directory, the command would fail quietly (only executables located in directories listed in the PATH environment variable can be invoked by name only).

  • -e bypasses involvement of a shell on the Linux side, and instead lets a Linux system function interpret the shebang-line-based plain-text file, which automatically honors the specified interpreter.

    • Important: If you get ERROR: CreateProcessEntryCommon:502: execvpe ./t.sh failed 8,[1] the implication is that your .sh file is either lacking a shebang line or its shebang line is malformed; be sure that your file starts with a line like #!/bin/bash at the very start of the file, and that the file is UTF-8-encoded without a BOM and uses Unix-format LF-only newlines.
      If your script lacks a shebang line and you cannot/don't want to add one, but you know what shell it was written for, invoke it via that shell's executable, e.g.
      wsl -e bash ./simple_script.sh
  • Perhaps surprisingly, WSL considers all files located in the Windows file-system, including plain-text ones, to have the executable bit set, which you can easily verify with wsl -e ls -l ./simple_script.sh


As for what you tried:

# !! WRONG
$simple_script = Get-Content ./simple_script.sh
wsl bash -c $simple_script

The primary problem is that Get-Content by default returns an array of lines, and attempting to use that array as an argument to an external program such as wsl causes the array's elements to be passed as individual arguments.

The immediate fix is to use the -Raw switch, which makes Get-Content return the file content as a single, multi-line string.

However, due to a highly unfortunate, long-standing bug, PowerShell - up to v7.2.x - requires manual \-escaping of embedded " characters in arguments passed to external programs.

Therefore:

# Using -Raw, read the file in full, as a single, multi-line string.
$simple_script = Get-Content -Raw ./simple_script.sh

# !! The \-escaping is needed up to PowerShell 7.2.x
wsl bash -c ($simple_script -replace '"', '\"')

Note that while it is tempting to try to bypass the need to escape by providing the script text via the pipeline (stdin), this does not work as expected, as of PowerShell 7.3.3:

# !! Tempting, but does NOT work.
Get-Content -Raw ./simple_script.sh | wsl -e bash

The reason this doesn't work is that PowerShell invariably appends a Windows-format newline (CRLF) to what is being sent to external programs via the pipeline, which Bash doesn't recognize.


[1] System error 8 is ENOEXEC, the Exec format error, indicating that while the target file has the appropriate permissions for being executed, its format isn't recognized. This could be due to file corruption, being an executable for the wrong platform or - in the case at hand - due to being a text file that lacks a well-formed shebang line.

Bram answered 11/5, 2022 at 17:34 Comment(7)
Brooo!!!!!!!!!!! You are a legend for this. If you ever need something I can give you just know... IOU 1Compelling
This is awesome. Is there any way to retrieve the output of the shell script once it completes? I have a script that outputs a series of HTML color HEX codes that I need to use further on in my PowerShell script.Haircloth
Also, is there a way to pass arguments to the shell script in this way? I need to pass an image file to the script.Haircloth
@fmotion1, just append the arguments to the command line. Capturing output works as with any command in PowerShell, e.g., $output = wsl -e ./simple_script.sh arg1 arg2, though note that this only covers success-stream / stdout output. To capture stderr output too use a 2> redirection.Bram
I tried this recently, and the -e flag needs a shell specified to run properly. i.e. wsl -e bash ./simple_script.sh Otherwise I would get <3>WSL (7606) ERROR: CreateProcessEntryCommon:502: execvpe ./simple_script.sh failed 8 <3>WSL (7606) ERROR: CreateProcessEntryCommon:505: Create process not expected to returnPlasticine
@A.Penner, that implies that your .sh is either lacking a shebang line or its shebang line is malformed. In that case you must indeed specify the shell executable explicitly. I've updated the answer to clarify that.Bram
@Bram Thanks, it turns out the Windows line ending were enough to mangle my line endings. Once corrected, it worked exactly as you describe.Plasticine
S
2

I was having trouble getting the above steps to work for me. It turned out to be an issue with the default Linux distribution in WSL. These steps resolved it.

Step 1: Check current distros

❯ wsl --list
Windows Subsystem for Linux Distributions:
docker-desktop-data (Default)
Ubuntu
docker-desktop

That docker-desktop-data default seemed to be the problem.

Step 2: Change the default

> wsl --setdefault Ubuntu

After that everything started working.

See also
How to set default distro using WSL2 on Windows 10

Setback answered 20/9, 2022 at 19:32 Comment(2)
Ah, it was the docker-desktop-data problem again. In that case, I'd also encourage you to vote for this issue and/or the (Microsoft) proposed solution in this comment. I'd love to see this implemented so that folks don't run into this particular problem in the future.Mislead
Also note that you can run the script in any installed distribution (whether or not it is the default) using wsl -d Ubuntu -e ./script.sh (substituting the actual distribution name for Ubuntu if needed.Mislead
B
0

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"

Balthazar answered 7/5, 2022 at 12:44 Comment(3)
My mistake was trying bash ./simple_script.sh instead of just bash simple_script.sh. This is confusing.Tizzy
As a heads-up, the bash command is deprecated in WSL. It is recommended to use the wsl command instead - It's far more flexible.Mislead
@EladMaimoni, bash ./simple_script.sh works too, and is actually preferable, because, unlike bash simple_script.sh, it doesn't fall back to looking for a simple_script.sh script in the PATH if it isn't in the current dir. However, you generally shouldn't pass a shell script with a shebang line to a specific shell for direct execution, unless you're sure that the same shell is also the one designated in the shebang line. In short: use wsl -e ./simple_script.sh instead.Bram
O
0

This worked for me. This is an alternative to accepted answer. You can run multiple commands with Powershell installed in WSL.

On this example:

  1. Send arguments to wsl($wslParameters).
  2. Inside WSL. I create the script file.
  3. Inside WSL. Change permissions of the created script.
  4. Inside WSL. Execute the created script.
  5. Inside WSL. Print UNIX username using & operator and whoami command.
  6. Inside WSL. Print working directory using sh command and pwd command.
  7. Inside WSL. Print Name parameter using sh and echo command.
$wslParameters = @{
    ScriptName = "MyScript.sh"
    ScriptCode = @"
        #!/bin/bash
        echo "¡Hello, world!"
"@
    Name       = "Joma"
}

& wsl pwsh -Command {
    $params = $args[0]
    $scriptPath = "$($env:HOME)/$($params.ScriptName)"
    [System.IO.File]::WriteAllText("$scriptPath", "$($params.ScriptCode)")
    & sh -c "chmod +x $scriptPath"
    & sh -c $scriptPath
    Write-Host "Wsl User: " -NoNewline ; & whoami
    Write-Host "Working directory: " -NoNewline ; & sh -c pwd
    & sh -c "echo Parameter Name: $($params.Name)"
} -args $wslParameters

Output

¡Hello, world!
Wsl User: x
Working directory: /mnt/c/Users/Megam/Desktop
Parameter Name: Joma
Oestrone answered 4/4 at 23:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.