使用 WSL 运行 sh 脚本将返回"command not found"



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

wsl ls

它运行良好,但是如果我 script.sh 创建一个文件并尝试:

wsl script.sh

与内部:

ls

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

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

当然,我将脚本放在正确的文件夹中。导致问题的原因是什么?

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

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

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

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

首先,对你的问题的"简短回答",然后我将深入研究其他技术的解释和缺点。

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

ls

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

wsl -e sh script.sh

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

#!/usr/bin/env sh
ls

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

wsl -e ./script.sh

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

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

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

在不使用默认 Linux shell 的情况下执行指定的命令

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

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

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

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

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

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

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

哇! 右?! 所有这些?

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

<小时 />
wsl script.sh(您的原始示例)

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

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

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

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

<小时 />
wsl sh script.sh(或wsl sh ./script.sh)

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

  • 启动默认外壳 (bash)
  • 要求bash执行 (-c)sh script.sh
  • bash转身,execsh(用sh过程替换bash过程
  • 然后sh读取和解释(而不是执行)您的脚本。

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

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

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

<小时 />
wsl ./script.sh(不含社邦线)

还加载了两个炮弹。 请记住,这就像运行bash -c ./script.sh。 它:

  • 加载bash(或默认 shell,如果不同)
  • 告诉外壳 (Bash) 从当前目录执行./script.sh
  • Bash 看到文件中没有 shebang,因此它确定它将在 Bash 的"后备"外壳中运行它,即它本身。
  • 因此,Bash 随后exec一个新的bash进程,替换当前进程,在其中执行脚本。
<小时 />
wsl ./script.sh(带社邦线)

这与"no shebang"的情况非常相似,但Bash没有回到"后备",而是使用你在shebang线上告诉它的任何内容(在这种情况下sh)。

它仍然execsh的实例,替换父进程,调用进程bash

<小时 />
wsl -e sh -c ./script.sh

这必须起作用,对吧? 我的意思是,我们告诉 WSL 加载sh并执行脚本 - 它还能做什么?

是的,再次,它有效。 但同样,我们要装载外壳两次。 一次显式(通过-e),一次是 shell 确定如何执行脚本(通过 shebang 或回退)。

通过将脚本更改为:

ps -ef

没有了舍邦,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

随着一声嘘声,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

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

PID TTY          TIME CMD
2651 pts/1    00:00:00 test.sh
2652 pts/1    00:00:00   ps
<小时 />
等等,还有更多?!

如果这还不足以让你做 shell/script/shebang 的噩梦,那么只需快速说明一下,有时你会想要执行父 shell,即使这意味着然后转身加载一个子进程。

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

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

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

wsl -e bash -lic ./script.sh

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

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

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

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

sh script.sh

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

wsl sh script.sh

这应该可以解决问题。

我认为这个问题(和答案)混淆了多个单独的问题。 运行脚本的方式组合的数量意味着其他示例可能是主观的、不一致的,并且存在数量惊人的边缘情况,我相当确定操作系统/shell 版本组合之间存在差异。

这个答案试图证明和澄清其中的一些。

在这里,我们看看解析 bash 脚本与加载和运行可执行文件,并首先考虑 shell 如何找到该可执行文件。

....如果您愿意,您可以跳过其中的大部分内容,稍后再进入令人兴奋的部分。


考虑最初的问题,这个问题的原因是:

  1. Linux 只在 PATH 上查找可执行文件或脚本,除非 给出明确的命令路径(从命令行扩展)

  2. 得到的环境(shell等)取决于你如何运行bash(或 sh)。形成的路径在此基础上被破坏

  3. 壳体
  4. 行为与并非所有壳体之间存在隐藏差异 在所有发行版上都配置相同,无论是在编译时还是 在安装/配置期间

(1) Linux 如何查找命令

即使文件是可执行的(脚本或二进制文件),您也必须使用完整路径(扩展后)让操作系统运行它。

/path/my_command./my_command有效,但my_command不起作用。

对于外壳来说,这是不同的,例如bash需要给定的文件名称作为参数是一个酸脚本,而不是可执行文件(除非你使用 -c)

例如,想象文件夹/bin~/.local/binPATH

  • bash path/to/my_script将起作用
  • ./my_script也将起作用(如果 CWD 是路径/到)
  • 除非my_script住在~/.local/bin,否则my_script不会起作用

(2) bash 调用的工作原理

有许多不同的系统和用户登录脚本,并且根据sh(实际上是dash),bashbash假装sh中的哪一个,命令环境中包含零个或多个。

对我来说(在 Ubuntu 22.04 上),有四个文件会影响环境,尽管并非所有发行版都相同。

/
  • etc/bash.bashrc
  • /
  • etc/profile
  • ~/。轮廓
  • ~/.巴什尔克

和两个可能的炮弹

/
  • bin/sh ->/usr/bin/dash
  • /
  • bin/bash

似乎我的发行版中没有使用sh的"posix"模式bash,尽管没有什么可以阻止它的使用。

(3)"隐藏"的差异

  • 例如,Ubuntu默认不使用旧sh实际上 使用dashsh的后期实现,几乎但不完全是 同样(过去对我来说很痛苦)

  • bash可以显式或通过推理来模拟sh,这取决于 关于操作系统实例的配置方式。

  • 有一些编译时开关可以对 同一个外壳的行为与谁建造它的行为不同。

  • 然后是捆绑 shell 命令和 在此过程中改变行为。

  • 可以将 shell 行为配置为对环境变量敏感 而那个环境是否被崇拜 bu 子壳是一个 配置参数本身。


当我使用 Ubuntu 时,我只会考虑 bash(在 bash 模式下)和 sh 的破折号实现。

真正使用哪种取决于上面的外壳以及 bash 的称呼方式。 (如果您需要更多,请参阅非常简洁的男人狂欢)

我们可以看看 shell 调用
的四种模式

  • 登录/互动
  • 非登录/交互式
  • 登录/非交互式
  • 登录/非交互式

什么环境(例如$PATH)结果取决于几个因素,例如设置了什么set标志,如何调用shell,调用的环境以及可执行文件($0)的调用

。因为sh通常是bash的符号链接或别名(或在这种情况下dash),所以可能是bash假装它是sh。它还取决于以哪种模式启动破折号/bash/sh。还有一些环境变量和其他 bash 开关会改变行为更多,只是为了使事情复杂化,为了简洁起见,忽略了这些开关。

因此,忽略这些复杂性,要回答一半的问题,只需自己尝试以下操作(示例在powershell中,但我认为cmd也应该相同)

令人兴奋的一点....

<小时 />

~/工作/wsl_sh/LS-sb.sh(模式 766)

echo "Running 'ls' in $SHELL($SHLVL)"
ls -1
echo "I am $0"

~/工作/wsl_sh/LS+SB.sh(模式766)

#!/bin/bash
source ./ls-sb.sh

在我们跳到 Windows 之前,让我们尝试一些东西,因为它有一个有趣的结果

## Using bash
$ cd ~/work/wsl_sh
$ ./ls-sb.sh
Running ls in /bin/bash(2)
ls+sb.sh
ls-sb.sh
I am ./ls-sb.sh
$ ./ls+sb.sh
Running ls in /bin/bash(2)
ls+sb.sh
ls-sb.sh
I am ./ls+sb.sh
## I also played about with combinations of sh and path and -c
## getting different and surprising results.
bash -c <script>
sh -c <script>
bash -c ./<script>
bash ./<script>
bash <script>
etc...

惊喜1:不需要shebang(破折号相同)。

惊喜 2:bash中的炮弹数量总是 2,而您有时期望只有 1 个,而在sh中,它也并不总是像您想象的那样。

我认为外壳和/或操作系统中的某些内容会向前看并优化调用。欢迎对此提出建议...

回到测试...

了解上述内容后,您可以将bash替换为下面的sh(或其他),并了解正在发生的事情。

我们将首先使用envps来窥视环境。 我还打印了命令$0,以便您可以看到如何调用 shell。

(这个任务变得很大,所以我缩小了下面脚本的大小。我可能需要一些时间来自动化它并创建一个结果表。

有:

四种可能的模式 [][-i][-
  • l][-i -l]
  • 两种可能的炮弹[bash][sh]
  • 运行脚本 [-c ] []
    的两种方法(-c 可以运行可执行文件,而不必是脚本)
  • 四个命令来尝试 [env][ps][ls-sb.sh][ls+sb.sh]
  • 最后,我们可以在有和没有显式路径的情况下使用 [][./]

4 x 2 x 2 x4 x 2 = 128 种组合!!

这是很多组合,很容易找到更多,所以我不会在这里列出所有这些,但您可以自己尝试一些。

# For me, wsl screws up starting if I am not on C:
C:
# Looking at env
wsl --shell-type standard --cd "~" -e bash -c 'echo ":$0:"; pwd; env'
wsl --shell-type standard --cd "~" -e bash -i -c 'echo ":$0:"; pwd; env'
wsl --shell-type standard --cd "~" -e bash -l -c 'echo ":$0:"; pwd; env'
wsl --shell-type standard --cd "~" -e bash -i -l -c 'echo ":$0:"; pwd; env'
# looking at ps
wsl --shell-type standard --cd "~" -e bash -c 'echo ":$0:"; pwd; ps au|grep $USER'
wsl --shell-type standard --cd "~" -e <shell> <switches> -c 'echo ":$0:"; pwd; ps au|grep $USER'
...
... etc

# Looking at the scripts run as commands
# Run as a command without shebang (explicit path)
wsl --cd "~/work/wsl_sh" -e <shell> <switches> -c 'echo ":$0:"; pwd;./ls-sb.sh"
# Run as command with shebang (explicit path)
wsl --cd "~/work/wsl_sh" -e <shell> <switches> -c 'echo ":$0:"; pwd;./ls+sb.sh"
...
... etc
# Again as command, but without explicit path (4 switch and 2 shell combinations)
...
... etc

# Looking at the scripts run as scripts and no explicit path (4 switch and 2 shell combinations)
wsl --cd "~/work/wsl_sh" -e <shell> <switches> 'ls+sb.sh'
wsl --cd "~/work/wsl_sh" -e <shell> <switches> 'ls-sb.sh'
...
... etc
# Again as scripts but with explicit path, etc.....

你会发现那些有效的是令人惊讶的,而那些生成更少或更多的贝壳并不总是很明显。


注意 1:在上述几种情况下,--cd "~" 是必需的,但并非全部(我没有研究过这一点),尽管有登录脚本,但似乎 wsl 以某种方式更改了 cwd。也可以随意将其添加为变量。

注2:我包含了不必要的默认--shell-type standard,以说明如果您改用loginnone,行为会再次更改。更多组合啊

注3:.(点空间)是某些外壳中source的缩写。

尝试sh script.sh或者你可以做./script.sh,它会有所不同,你选择哪一个适合你。

最新更新