使用 WSL 运行 sh 脚本返回“未找到命令”

Alb*_*o B 17 bash command-line sh windows-subsystem-for-linux

我已经安装了 wsl,如果从 cmd 提示符运行:

wsl ls
Run Code Online (Sandbox Code Playgroud)

它工作得很好,但如果我创建一个文件 script.sh 并尝试:

wsl script.sh
Run Code Online (Sandbox Code Playgroud)

与里面:

ls
Run Code Online (Sandbox Code Playgroud)

或任何其他 linux 命令,我得到:

/bin/bash: script.sh: command not found
Run Code Online (Sandbox Code Playgroud)

当然,我的脚本位于正确的文件夹中。是什么原因导致这个问题?

编辑:非常感谢您的回答。我是否可以将 .sh 文件关联到 wsl,以便它们通过双击自动运行?

Not*_*1ds 20

从表面上看,这是一个非常简单的问题。我已经回答了一些有关通过wsl命令执行命令的问题。然而,这个问题有点复杂。您通过以下方式为问题添加了两个维度:

  • 尝试在没有路径的情况下执行脚本。
  • 不使用shebang 线

这些都不是必需的,但如果您不真正了解幕后发生的事情,它们确实会引起问题。

首先,对你的问题进行“简短回答”,然后我将深入探讨其他技术的解释和缺点。

您有一个简单的脚本script.sh,其中包含以下内容:

ls
Run Code Online (Sandbox Code Playgroud)

您的脚本位于当前目录中。wsl要通过命令(从 PowerShell、CMD 或其他 Windows 进程)运行它,请使用:

wsl -e sh script.sh
Run Code Online (Sandbox Code Playgroud)

但不要这样做。养成使用 Shebang 线的习惯。将脚本编辑为:

#!/usr/bin/env sh
ls
Run Code Online (Sandbox Code Playgroud)

将其设置为对您的默认用户可执行(看来您已经拥有)。然后通过以下方式启动它:

wsl -e ./script.sh
Run Code Online (Sandbox Code Playgroud)

据我所知,这是唯一可以有效运行脚本的两种技术。从命令启动它的所有其他方法wsl都会导致加载多个shell——要么是 shell 内的 shell,要么是 shell exec'ing shell。

为什么-e?它有什么作用?

wsl -e(或长格式wsl --exec),根据帮助:

不使用默认的 Linux shell 执行指定的命令

这允许我们完全跳过 shell 或指定不同的 shell,而不会产生运行默认 shell 的开销。

所以当你运行时wsl -e sh script.sh,:

  • 告诉 WSL 立即执行sh
  • 告诉您sh阅读并解释每一行script.sh(请参阅下文以更好地理解“阅读和解释”)

当您运行wsl -e ./script.sh带有 shebang 行的脚本时,:

  • 告诉 WSL 立即执行./script.sh
  • WSL 的/init进程(实际上在所有这些情况下运行,但这是我们真正需要明确提及的唯一一次)看到脚本的第一行是 shebang,然后直接在该 shell 中加载并运行脚本。
为什么其他启动方法不起作用?

解释为什么不应该使用其他技术才是真正开始变得复杂的地方。你必须明白:

  • 当您要求 shell执行未提供路径的脚本文件时会发生什么?
  • 另一方面,当您要求 shell 解释提供路径的脚本文件时会发生什么?请注意,它可能因外壳而异,因此我们将坚持使用,bash因为这就是您正在使用的外壳。
  • 对于您的情况,WSL 试图做什么?
  • 当您要求 shell 使用 shebang 行执行脚本时会发生什么?
  • 另一方面,正如您所做的那样,如果您要求 shell 执行没有shebang 行的脚本,会发生什么?
  • -e命令的参数——wsl使用它与不使用它会发生什么?当然,我们已经在上面介绍过这一点。

哇!正确的?!所有的?

因此,考虑到这一点,让我举一些执行脚本的其他方法的示例,为什么它们不起作用,或者为什么它们工作效率较低:


wsl script.sh(你原来的例子)

您可能认为这与从 Linux/WSL 内部运行相同bash script.sh,但事实并非如此。

bash script.sh(在 WSL 内部)有效,因为它正在读取脚本中的每一行并将其解释bash为要由其自身执行的命令。由于它不执行脚本,因此不需要路径。shell 只读取当前目录中的文件。

wsl script.sh(WSL 之外)不起作用,因为它实际上尝试使用默认用户的 shell(在本例中)执行脚本bash。这大致相当于bash -c script.sh从 WSL/Linux 内部进行调用。要执行文件,任一文件需要位于搜索路径(由PATH环境变量表示)中的目录中,或者您需要提供文件的绝对或相对路径(例如bash -c ./script.shwsl ./script.sh)。

有关执行脚本和解释脚本之间差异的更多阅读(尽管它没有使用确切的术语)可以在这个优秀的 Unix & Linux 堆栈答案中找到。


wsl sh script.sh(或者wsl sh ./script.sh

其中任何一个都会运行您的脚本,但它们实际上会加载 shell 两次。这:

  • 启动您的默认 shell ( bash)
  • 要求bash执行( -c)sh script.sh
  • bash转身 and exec's sh(用进程替换bash进程sh
  • 然后sh 读取并解释(而不是执行)您的脚本。

因为sh正在读取和解释您的脚本,所以它可以从当前目录执行此操作,而无需路径(或文件位于 上的目录中)$PATH

但加载两个不同的 shell 是没有意义的。原始版本wsl -e sh script.sh仅运行shbash完全跳过,从而节省了加载时间。

注意:在这种情况下,是否有 shebang 行并不重要,因为 shebang 仅在执行脚本时发挥作用。 在阅读和解释sh时将该行视为注释并跳过它。


wsl ./script.sh(没有 Shebang 线)

还装载两个炮弹。请记住,这就像跑步bash -c ./script.sh。它:

  • 加载bash(或您的默认 shell,如果不同的话)
  • 告诉 shell (Bash)./script.sh从当前目录执行
  • Bash 发现文件中没有 shebang,因此它确定将在 Bash 的“后备”shell(即它本身)中运行它。
  • 因此,Bashexec是一个新bash进程,替换当前进程,并在其中执行脚本。

wsl ./script.sh(用舍邦线)

它与“no shebang”情况非常相似,但 Bash 不会退回到“后备”,而是使用您在 shebang 行上告诉它的任何内容(sh在本例中)。

它仍然exec是一个实例sh,替换父进程,调用bash进程。


wsl -e sh -c ./script.sh

这一定行得通,对吧?我的意思是,我们告诉 WSL 加载sh并执行脚本——它还能做什么?

是的,再次,它有效。但我们再次加载 shell 两次。一次显式(通过-e),一次 shell 确定如何执行脚本(通过 shebang 或后备)。

通过将脚本更改为:

ps -ef
Run Code Online (Sandbox Code Playgroud)

没有 shebang,wsl -e sh -c ./script.sh返回:

  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
Run Code Online (Sandbox Code Playgroud)

使用 shebang,wsl -e sh -c ./script.sh返回:

  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
Run Code Online (Sandbox Code Playgroud)

根据我们的提议wsl -e ./script.sh,我们看到:

  PID TTY          TIME CMD
 2651 pts/1    00:00:00 test.sh
 2652 pts/1    00:00:00   ps
Run Code Online (Sandbox Code Playgroud)
但是等等,还有更多?!

如果这还不足以给您带来 shell/script/shebang 噩梦,那么请注意,有时您想要执行父 shell,即使这意味着转身并加载子进程。

如果您的脚本需要启动文件中的某些内容,则可能会出现这种情况。执行上述任何命令行时,WSL 将 shell 作为非登录、非交互式 shell 启动。

这意味着您的~/.bashrc和不会被处理,如果您对它们进行了脚本期望的~/.bash_profile更改(例如 或其他一些环境变量),这可能会导致一些混乱。PATH

在这种情况下,请调用类似以下内容:

wsl -e bash -lic ./script.sh
Run Code Online (Sandbox Code Playgroud)

这将强制“登录,交互式”shell 并处理您的启动配置。

请注意,也可能可以修改您的 shebang 行来强制执行此操作,但我将跳过任何相关说明,因为这个答案/书籍/论文已经变得足够长了;-)

不过,如果您需要更多阅读,我会向您指出这个问题


Dom*_*que 3

启动脚本的一般方法不是简单的script.sh,而是:

sh script.sh
Run Code Online (Sandbox Code Playgroud)

因此,使用wsl,您可以尝试:

wsl sh script.sh
Run Code Online (Sandbox Code Playgroud)

这应该够了吧。

  • 我认为 OP 希望将其作为 bash 脚本运行。因此 `wsl bash script.sh` 是必要的。 (3认同)