Home > Software engineering >  How can I direct PowerShell 7 to run a bash (Ubuntu) script in WSL2 on Windows 10?
How can I direct PowerShell 7 to run a bash (Ubuntu) script in WSL2 on Windows 10?

Time:04-26

I want to put together a PowerShell script that invokes bash/wsl and runs the following code on the working directory in bash:

for f in $(find -type l);do cp --remove-destination $(readlink $f) $f;done;

All I can seem to do is get wsl to change directories:

param (
    [Parameter(Mandatory=$true,Position=0)]
    [String]$Folder
)

wsl.exe --cd $Folder

I've tried:

wsl -c 'for f in $(find -type l);do cp --remove-destination $(readlink $f) $f;done;'

wsl.exe 'for f in $(find -type l);do cp --remove-destination $(readlink $f) $f;done;'

Also, for some reason, I can't get any additional commands to work after I've called wsl.exe the first time. For instance, the following doesn't work:

param (
    [Parameter(Mandatory=$true,Position=0)]
    [String]$Folder
)

wsl.exe --cd $Folder
wsl.exe echo "Hello World" # Never executes

I'm pretty inexperienced with linux and Ubuntu for that matter, so I have a feeling I'm missing something incredibly simple - thus this post.

How can I execute a bash/wsl2 command from Windows Powershell 7?

Any help greatly appreciated.

CodePudding user response:

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 by wsl.exe

  • wsl.exe --cd ~ 'for f in $(find -xdev -type l); do readlink $f; done'

    That would be the equivalent of running cd ~; 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 as for.

"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
  • Related