Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running sh script with WSL returns "command not found"

I have wsl installed, if from the cmd prompt I run:

wsl ls

it works perfectly, but if if i create a file script.sh and try:

wsl script.sh

with inside:

ls

or any other linux command, I get:

/bin/bash: script.sh: command not found

I have the script in the right folder of course. What causes the issue?

EDIT: Thanks a lot for the answers. Is i possible to associate .sh files to wsl so that they run automatically with double-click?

like image 574
Alberto B Avatar asked Sep 08 '25 06:09

Alberto B


1 Answers

On the surface, this is such a simple question. I've answered a number of questions on executing commands via the wsl command. This one, however, is subtly complex. You've added two dimensions to the problem by:

  • Attempting to execute the script without a path.
  • Not using a shebang line

Neither of these is required, but they do cause issues if you aren't really aware of what is happening behind the scenes.

First, the "short answer" to your question, then I'll dig in to the explanation and drawbacks of other techniques.

You have a simple script script.sh with just the following:

ls

You have the script in the current directory. To run it via the wsl command (from PowerShell, CMD, or other Windows processes), use:

wsl -e sh script.sh

But don't do that. Get in the habit of using a shebang line. Edit the script to be:

#!/usr/bin/env sh
ls

Set it to be executable for your default user (which it appears that you already have). Then start it via:

wsl -e ./script.sh

As far as I know, those are the only two techniques that will run your script efficiently. Every other method of starting it from the wsl command will result in multiple shells loading -- Either shells inside of shells, or shells exec'ing shells.

Why -e? What does it do?

wsl -e (or the long-form wsl --exec), according to the help:

Execute[s] the specified command without using the default Linux shell

That allows us to skip the shell entirely or specify a different shell, without incurring the overhead of running the default shell.

So when you run wsl -e sh script.sh, that:

  • Tells WSL to immediately execute sh
  • Tells sh to read and interpret each line of script.sh (see below for a better understanding of "read and interpret")

When you run wsl -e ./script.sh on a script with a shebang line, that:

  • Tells WSL to immediately execute ./script.sh
  • WSL's /init process (which actually runs in all of these cases, but this is the only time we really need to mention it explicitly) sees the first line of the script is a shebang, then loads and runs the script in that shell directly.
Why don't other methods of launching work?

To explain why other techniques shouldn't be used is where it truly starts to get complicated. You have to understand:

  • What happens when you ask your shell to execute a script file for which you haven't provided a path?
  • On the other hand, what happens when you ask your shell to interpret a script file for which you haven't provided a path? Note that it can differ depending on the shell, so we'll stick with bash since that's what you are using.
  • Which is WSL attempting to do in your case?
  • What happens when you ask your shell to execute a script with a shebang line?
  • On the other hand, as you are doing, what happens if you ask a shell to execute a script without a shebang line?
  • The -e argument to the wsl command -- What happens with it vs. without it? We've already covered this above, of course.

Wow! Right?! All of that?

So with that in mind, let me give some examples of other ways of executing your script, why they don't work, or why they work less efficiently:


wsl script.sh (your original example)

You might think this would be the same as running bash script.sh from inside Linux/WSL, but it's not quite.

bash script.sh (inside WSL) works, because it is reading and interpreting each line in the script as a command to be executed by bash itself. Since it is not executing the script, it doesn't require a path. The shell just reads the file in the current directory.

wsl script.sh (outside WSL) doesn't work, because it is actually attempting to execute the script using your default user's shell (bash in this case). It's the rough equivalent of calling bash -c script.sh from within WSL/Linux. To execute a file, either file needs to be in a directory in the search path (represented by the PATH environment variable) or you need to provide an absolute or relative path to the file (e.g. bash -c ./script.sh or wsl ./script.sh).

More reading on the difference between executing a script and interpreting it (although it doesn't use that exact terminology) can be found in this excellent Unix & Linux stack answer.


wsl sh script.sh (or wsl sh ./script.sh)

Either of these will run your script, but they actually load the shell two times. This:

  • Starts your default shell (bash)
  • Asks bash to execute (-c) sh script.sh
  • bash turns around and exec's sh (replacing the bash process with the sh process
  • Then sh reads and interprets (rather than executes) your script.

Because sh is reading and interpreting your script, it can do that from the current directory without requiring a path (or for the file to be in a directory on the $PATH).

But it just doesn't make sense to load two different shells. The original wsl -e sh script.sh only runs the sh and skips bash entirely, saving on load time.

Note: Whether or not you have a shebang line doesn't matter in this case, because the shebang only comes into play when executing the script. sh sees that line as a comment when reading and interpreting and just skips it.


wsl ./script.sh (without a shebang line)

Also loads two shells. Remember, this is like running bash -c ./script.sh. It:

  • Loads bash (or your default shell, if it is different)
  • Tells the shell (Bash) to execute ./script.sh from the current directory
  • Bash sees that there is no shebang in the file, so it determines that it is going to run it in it Bash's "fallback" shell, which is itself.
  • So Bash then exec's a new bash process, replacing the current one, in which to execute the script.

wsl ./script.sh (with a shebang line)

It's very similar to the "no shebang" case, but instead of dropping back to the "fallback", Bash uses whatever you tell it to on the shebang line (sh in this case).

It still exec's an instance of sh, replacing the parent, calling bash process.


wsl -e sh -c ./script.sh

That's got to work, right? I mean, we're telling WSL to load sh and execute the script -- What else could it do?

Yes, again, it works. But again, we're loading the shell twice. Once explicitly (via -e) and again once the shell determines how to execute the script (via shebang or fallback).

It's easiest to see this by changing the script to be:

ps -ef

Without a shebang, wsl -e sh -c ./script.sh returns:

  PID TTY          TIME CMD
 2638 pts/1    00:00:00 sh
 2639 pts/1    00:00:00   sh
 2640 pts/1    00:00:00     ps

With a shebang, wsl -e sh -c ./script.sh returns:

  PID TTY          TIME CMD
 2643 pts/1    00:00:00 sh
 2644 pts/1    00:00:00   test.sh
 2645 pts/1    00:00:00     ps

With our proposed wsl -e ./script.sh, we see:

  PID TTY          TIME CMD
 2651 pts/1    00:00:00 test.sh
 2652 pts/1    00:00:00   ps

But wait, there's more?!

If this isn't enough to give you shell/script/shebang nightmares, then just one quick note that sometimes you will want to execute the parent shell, even if it means then turning around and loading a subprocess.

This can be the case if your script needs something from your startup files. When executing any of the previous command lines, WSL starts the shell as a non-login, non-interactive shell.

This means that your ~/.bashrc and ~/.bash_profile aren't processed, which can lead to some confusion if there is a change you made in them (e.g. the PATH or some other environment variable) that your script expects.

In that case, call something like:

wsl -e bash -lic ./script.sh

This will force a "login, interactive" shell and process your startup config.

Note that it's probably also possible to modify your shebang line to force this, but I'm going to skip any instruction on that, since this answer/book/treatise has become quite long enough ;-)

For more reading, though, I'll point you to this question if you need it.

like image 125
NotTheDr01ds Avatar answered Sep 11 '25 02:09

NotTheDr01ds