我已经安装了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,要么是 shellexec
shell 的 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.sh
或wsl ./script.sh
)。
更多关于执行脚本和解释脚本之间区别的阅读(尽管它没有使用确切的术语)可以在这个优秀的Unix和Linux堆栈答案中找到。
<小时 />wsl sh script.sh
(或wsl sh ./script.sh
)
其中任何一个都将运行您的脚本,但它们实际上加载了两次 shell。 这:
- 启动默认外壳 (
bash
) - 要求
bash
执行 (-c
)sh script.sh
bash
转身,exec
的sh
(用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
)。
它仍然exec
sh
的实例,替换父进程,调用进程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 如何找到该可执行文件。
....如果您愿意,您可以跳过其中的大部分内容,稍后再进入令人兴奋的部分。
考虑最初的问题,这个问题的原因是:
Linux 只在 PATH 上查找可执行文件或脚本,除非 给出明确的命令路径(从命令行扩展)
你得到的环境(shell等)取决于你如何运行bash(或 sh)。形成的路径在此基础上被破坏
壳体行为与并非所有壳体之间存在隐藏差异 在所有发行版上都配置相同,无论是在编译时还是 在安装/配置期间
(1) Linux 如何查找命令
即使文件是可执行的(脚本或二进制文件),您也必须使用完整路径(扩展后)让操作系统运行它。
即/path/my_command
和./my_command
有效,但my_command
不起作用。
对于外壳来说,这是不同的,例如bash
需要给定的文件名称作为参数是一个酸脚本,而不是可执行文件(除非你使用 -c)
例如,想象文件夹/bin
和~/.local/bin
是PATH
bash path/to/my_script
将起作用./my_script
也将起作用(如果 CWD 是路径/到)- 除非my_script住在
~/.local/bin
,否则my_script
不会起作用
(2) bash 调用的工作原理
有许多不同的系统和用户登录脚本,并且根据sh
(实际上是dash
),bash
或bash
假装sh
中的哪一个,命令环境中包含零个或多个。
对我来说(在 Ubuntu 22.04 上),有四个文件会影响环境,尽管并非所有发行版都相同。
/- etc/bash.bashrc /
- etc/profile
- ~/。轮廓
- ~/.巴什尔克
和两个可能的炮弹
/- bin/sh ->/usr/bin/dash /
- bin/bash
似乎我的发行版中没有使用sh
的"posix"模式bash
,尽管没有什么可以阻止它的使用。
(3)"隐藏"的差异
例如,Ubuntu默认不使用旧
sh
实际上 使用dash
,sh
的后期实现,几乎但不完全是 同样(过去对我来说很痛苦)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
(或其他),并了解正在发生的事情。
我们将首先使用env
和ps
来窥视环境。 我还打印了命令$0
,以便您可以看到如何调用 shell。
(这个任务变得很大,所以我缩小了下面脚本的大小。我可能需要一些时间来自动化它并创建一个结果表。
有:
- 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
,以说明如果您改用login
或none
,行为会再次更改。更多组合啊
注3:.
(点空间)是某些外壳中source
的缩写。
尝试sh script.sh
或者你可以做./script.sh
,它会有所不同,你选择哪一个适合你。