3

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.

Elad Maimoni
  • 2,731
  • 2
  • 17
  • 27

2 Answers2

3

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.

    • 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:

$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 at least v7.2.3 - 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 at least PowerShell 7.2.3
wsl bash -c ($simple_script -replace '"', '\"')

Note that while it is tempting 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.2.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.

This problematic behavior is being discussed in GitHub issue #13579.

mklement0
  • 312,089
  • 56
  • 508
  • 622
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"

HatLess
  • 5,048
  • 4
  • 8
  • 28
  • My mistake was trying `bash ./simple_script.sh` instead of just `bash simple_script.sh`. This is confusing. – Elad Maimoni May 07 '22 at 14:50
  • 1
    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. – NotTheDr01ds May 08 '22 at 01:08
  • @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. – mklement0 May 11 '22 at 18:06