WSL下拖拽运行.sh脚本:路径转换与批处理实战
2026/9/16 5:40:14 网站建设 项目流程

在 Windows 上装了 WSL 之后,我养成了一个习惯:能丢给 Linux 跑的脚本,绝不在 Windows 上硬折腾。redis 没有官方 Windows 版,我用 WSL 起;elasticsearch 在 Windows 下各种权限和目录坑,我也丢进 WSL;binwalk 这东西不装进 Linux 工具链基本没法愉快分析固件;还有日常启停 jar 包的 manage.sh、backup.sh,全都是在 WSL 里敲命令。但问题也跟着来了:每次要跑一个 SH 脚本,我得先打开终端,敲wsl回车,再 cd 到/mnt/c/...那一长串路径,然后bash xxx.sh。路径一旦深一点,或者目录名带中文,那个输入体验真的让人崩溃。

后来我花了一点时间,做了一个很土但非常实用的工具:把 .sh 文件直接从 Windows 资源管理器拖到一个批处理图标上,它会自动把 Windows 路径转成 WSL 能识别的/mnt/c/...路径,然后调用 WSL 里的 bash 执行这个脚本。整个过程不需要打开终端、不需要手敲路径、不需要记发行版名称。这篇文章就把这套方案的原理、完整脚本、踩坑记录都摊开讲清楚,Windows 上用 WSL 跑 SH 脚本的朋友可以直接抄作业。

1. 方案设计:三个痛点逼出来的“拖拽执行”

1.1 为什么我不再手动敲 WSL 命令

先说说我日常工作里最常见的几个场景,你应该也有同感。

第一个是启动服务。比如我需要在 WSL 里启动 redis:wsl进去之后,redis 的启动脚本放在/etc/init.d/redis-server或者/opt/redis/start.sh,每次都要先切到对应目录,再执行。第二个是分析类脚本,我把解包好的固件放在D:\firmware\extract下面,想把 binwalk 跑一遍,就得记着cd /mnt/d/firmware/extract,再执行一串分析参数。第三个是项目部署脚本,比如deploy.sh要更新 jar 包、重启服务,这类脚本里面全是相对路径和当前目录判断,如果你的工作目录不在它期望的位置,脚本直接罢工。

这些场景共同的特点是:脚本本身已经写好了,问题出在“怎么把它跑起来”。手动敲命令不光是慢,还容易出低级错误。最常见的错误就是在wsl窗口里输入D:\xxx或者C:/xxx这种路径,Linux shell 根本不认识反斜杠和盘符,直接报No such file or directory。而且每次都要重复从 Windows 路径到 Linux 路径的脑内换算,烦也烦死了。

后来我就想,Windows 用户本来就有一种非常自然的交互方式:把一个文件拖到某个程序图标上,让这个程序来处理它。我为什么不做一个接收器,让 .sh 文件拖上去之后,自动完成“路径转换 + 调用 WSL + 执行脚本”这一整套动作?事实证明这个方向完全正确,用了几周之后,我基本再也没手动敲过wsl命令来跑脚本。

1.2 三条路线怎么选:bat 接收器 / 右键菜单 / 发送到

在动手之前,我整理了三条可行的方案。这里先给你一张对比表,后面会详细展开。

方案实现难度操作体验是否需要管理员适用场景
拖到专用 .bat 上执行需要先拖拽,然后等待运行结果不需要最通用,适合所有 .sh 文件
右键菜单“在 WSL 中运行”右键点一下就行,最顺手不需要(改 HKCU 即可)适合高频使用的 .sh 文件
发送到菜单右键 -> 发送到 -> 选择工具不需要不想改注册表的折中选择

我的建议是先做方案一,也就是拖到 bat 上执行。原因很简单:bat 接收器是整个方案的逻辑核心,右键菜单和发送到菜单最后都只是“调用 bat 的入口”而已。你把 bat 写对、调通,后面两个方案基本就是复制粘贴的事情。

方案二是注册表右键菜单,修改的是HKEY_CURRENT_USER\Software\Classes下面的用户级文件关联,不会动系统级配置,所以不需要管理员权限。但它有一个副作用:如果要让右键菜单稳定出现,通常要把 .sh 文件的默认关联改成我们定义的 ProgID,这样的话双击 .sh 文件也会变成“直接运行”,而不是用编辑器打开。这个事情后面我会专门说明怎么权衡。

方案三是发送到菜单,把 bat 的一个快捷方式放进shell:sendto目录,然后右键任意 .sh 文件,通过“发送到”来执行。这个方案不改任何文件关联,最安全,也最适合“偶尔用一下”的人。

2. 原理拆解:WSL 和 Windows 之间到底怎么“传话”

2.1 wsl.exe 命令行接口

想要让 Windows 这边控制 WSL 干活,核心就是wsl.exe这个命令行程序。它最基础的用法是直接敲wsl进去一个交互式 shell,但对我们来说更重要的是非交互式执行命令。

基本格式是:

wsl.exe [选项] [命令行]

常用的几个选项:

  • -d <发行版>--distribution <发行版>:指定使用哪个 WSL 发行版,比如-d Ubuntu-24.04。如果不指定,就用默认发行版。
  • -u <用户名>--user <用户名>:以指定用户身份执行,不指定的话默认是发行版的默认用户。
  • -e <命令>--exec <命令>:不通过 shell 直接执行命令。注意这个选项和直接拼接命令行的行为不太一样。

我们最常写的形式是:

wsl.exe -d Ubuntu-24.04 bash -lc "你要执行的命令"

这里bash -lc的意思是启动一个登录 shell 并执行后面的命令。-l会加载用户的环境变量和 profile,-c表示“由字符串指定命令”。很多脚本依赖PATHJAVA_HOME这类环境变量,所以-lc比不加-l更接近你手动打开 WSL 窗口再跑命令的效果。

比如我想验证 WSL 里能不能拿到 Linux 路径,直接执行:

wsl.exe -d Ubuntu-24.04 bash -lc "echo hello from wsl"

如果配置没问题,你会在当前 Windows 终端里看到hello from wsl。只要这一句通了,后面所有事情就都通了。

这里有一个非常关键的点:wsl.exe是 Windows 这边的原生命令,它启动 WSL 虚拟机时需要一点时间,所以第一次调用会比较慢,大概一到两秒。这很正常,后面我们也会针对这一点做一些体验上的优化。

2.2 路径转换:C:\ 变 /mnt/c/ 的来龙去脉

WSL 里的 Linux 子系统访问 Windows 磁盘,并不是像 Windows 程序一样直接读C:\,而是把 Windows 的盘符挂载到了 Linux 文件系统的/mnt/目录下。这是 WSL 的一个内置挂载规则:C:\对应的就是/mnt/c/D:\对应的就是/mnt/d/,以此类推。

所以同样一个文件,在 Windows 资源管理器里显示的是:

D:\firmware\extract\fw.bin

但在 WSL 的 bash 里,它的路径是:

/mnt/d/firmware/extract/fw.bin

这两个路径的关系看起来很直接,但让用户每次手动转换是很痛苦的。好在批处理可以做一个很简单的字符串替换。

Windows 路径长这样:C:\Users\admin\test.sh,我们要做的就是两件事:

  1. 把所有反斜杠\替换成正斜杠/,得到C:/Users/admin/test.sh
  2. 把开头的盘符C:改写成/mnt/c,也就是在盘符前面加/mnt/,并去掉冒号。

从一个真实路径看会更清楚:

Windows 路径替换反斜杠后WSL 路径
C:\Users\admin\test.shC:/Users/admin/test.sh/mnt/c/Users/admin/test.sh
D:\firmware\extract\run.shD:/firmware/extract/run.sh/mnt/d/firmware/extract/run.sh
E:\工具脚本\deploy.shE:/工具脚本/deploy.sh/mnt/e/工具脚本/deploy.sh

批处理里做这个转换,核心命令就是set的替换功能。%winpath:\=/%会把变量winpath里的所有\替换成/,然后我们再截取盘符和剩余路径拼起来。这段代码看起来有点绕,但其实逻辑非常简单,后面会给完整脚本。

当然,WSL 本身也提供了一个更权威的转换命令wslpath,可以在 WSL 里把 Windows 路径转成 Linux 路径:

wsl.exe wslpath -u "C:\Users\admin\test.sh"

输出结果是/mnt/c/Users/admin/test.sh。不过我在工具里并没有优先用wslpath,原因有两个:一是它会额外多启动一次 WSL 进程,第一次调用会更慢;二是批处理里做纯字符串替换,不依赖 WSL 是否就绪,逻辑更稳定。后面遇到特别复杂的路径再临时用wslpath兜底就行。

2.3 拖到 bat 上之后,参数经历了什么

Windows 的批处理文件有一个特性:当你把一个或多个文件拖到.bat文件图标上松开鼠标,资源管理器会把文件的完整路径作为参数传给这个批处理。这些参数在批处理里可以通过%1%2这样的位置变量来取,%*表示所有参数的原始字符串。

关键在于带空格的路径。比如你拖了一个D:\我的脚本\deploy test.sh,资源管理器传给批处理时,实际上会把路径用引号包起来,所以%1的值是:

"D:\我的脚本\deploy test.sh"

注意,%1本身就带着引号。如果你直接拿%1去拼接路径,就会出现引号嵌套的问题。批处理里有一个专门去掉引号的语法:%~1,它的意思是“取%1的值并去掉首尾的引号”。所以:

set "winpath=%~1"

这一句是用变量winpath保存去引号后的 Windows 路径,后面所有的字符串替换都基于这个变量来做。为什么用set "变量=值"而不是直接set 变量=值?因为我担心路径里可能出现特殊字符,用引号包裹整个赋值语句,可以避免&%这类符号被 cmd 当成命令分隔符或变量引用处理。这是一个非常重要的防御性写法。

另外,拖拽多个文件到 bat 上时,%*会包含所有路径,每个路径还是带引号的。这时候可以用for循环逐个处理。这个后续在多文件升级版里会说。

只要理解了这三件事:wsl.exe怎么调用、Windows 路径怎么转成 WSL 路径、拖拽后的参数怎么正确处理,整个工具的核心技术点就通了。剩下的就是把它们组装起来。

3. 动手实现:一套能直接复制的“拖拽运行”方案

3.1 环境检查与测试脚本准备

在写脚本之前,先确认一下你的 WSL 环境是不是正常。打开任意终端,执行:

wsl -l -v

这个命令会列出当前系统里安装的 WSL 发行版和版本号。比如我的输出是:

NAME STATE VERSION * Ubuntu-24.04 Running 2

注意 VERSION 这一列,最好是 2。WSL2 的性能和兼容性比 WSL1 好很多,尤其涉及到文件读写和大多数 Linux 工具链的时候。如果发现自己的发行版是 1,可以考虑转换:

wsl --set-version Ubuntu-24.04 2

如果还没有安装任何发行版,用这一条命令装一个 Ubuntu 24.04 就行:

wsl --install -d Ubuntu-24.04

安装过程会要求你设置 Linux 用户名和密码,这个账号就是后面执行脚本时默认使用的用户。

然后我们创建一个测试脚本。在 Windows 任意目录,比如D:\wsl-test\下面新建一个hello.sh,内容随便写点东西:

#!/bin/bash echo "Hello from WSL" echo "Current path: $(pwd)"

先打开 WSL 窗口手动执行一下:

bash /mnt/d/wsl-test/hello.sh

能正常输出,说明文件系统挂载和执行权限都没问题。这里要注意一件事:如果这个文件是从 Windows 记事本创建的,大概率是 CRLF 换行符,也就是每一行结尾既有回车符\r又有换行符\n,而 Linux 只认\n。如果不做处理,执行时会报类似$'\r': command not found的错。这个坑我们后面专门讲,现在先记住有这个风险。

3.2 核心脚本 run-with-wsl.bat(单文件版)

核心工具就是一个批处理文件,名字随意,我叫它run-with-wsl.bat。完整代码放在这里,你可以直接复制保存。

@echo off chcp 65001 >nul setlocal enabledelayedexpansion rem Configure your WSL distro here, leave empty to use the default set "WSL_DISTRO=Ubuntu-24.04" if "%~1"=="" ( echo Drag a .sh file onto this batch file. pause exit /b 1 ) set "winpath=%~1" echo Input: %winpath% set "linuxpath=%winpath:\=/%" if /i "!linuxpath:~0,2!"=="C:" ( set "drive=!linuxpath:~0,1!" set "linuxpath=/mnt/!drive!/!linuxpath:~2!" ) else ( echo Unsupported path. Only drive-letter paths are supported. pause exit /b 1 ) echo WSL path: !linuxpath! rem Convert CRLF to LF first, then run the script if defined WSL_DISTRO ( wsl.exe -d "%WSL_DISTRO%" bash -lc "sed -i 's/\r$//' '!linuxpath!' && bash '!linuxpath!'" ) else ( wsl.exe bash -lc "sed -i 's/\r$//' '!linuxpath!' && bash '!linuxpath!'" ) echo. echo Script finished. pause

下面逐段解释这个脚本做了什么。

第一行@echo off是关闭命令回显,不然每执行一句命令都会刷一大片出来。第二行chcp 65001 >nul把控制台代码页切到 UTF-8,这样 WSL 里输出中文或者特殊符号时不容易乱码。第三行setlocal enabledelayedexpansion开启延迟变量展开,因为我们后面会在括号块里动态改变和读取变量的值。

WSL_DISTRO这一行是唯一的配置项。如果你只有一套发行版,而且不想关心名字,直接改成set "WSL_DISTRO=",留空即可。留空之后,脚本就会走 else 分支,直接调用 WSL 默认发行版。如果你想指定发行版,就用wsl -l -q查到准确名字填进去。

然后判断%~1是否为空,如果没有拖文件,提示用户并暂停退出。这里pause很重要,否则窗口会一闪而过,你都看不清发生了什么。

路径转换的核心代码是这三行:

set "linuxpath=%winpath:\=/%" if /i "!linuxpath:~0,2!"=="C:" ( set "drive=!linuxpath:~0,1!" set "linuxpath=/mnt/!drive!/!linuxpath:~2!" )

第一行把反斜杠全部变成正斜杠。第二行判断前两个字符是不是C:,这里C:只是一个示例判断,实际上它可以覆盖所有单字母盘符,因为我用if /i做了大小写不敏感判断。如果是C:,就取第一个字符作为盘符,然后从第三个字符开始截取剩余路径,拼成/mnt/C/...的格式。

这里有个细节:/mnt/C里面盘符是大写。WSL 的 DrvFs 挂载默认对盘符大小写不敏感,所以/mnt/C/mnt/c都能访问,实际测试没有问题。如果你有强迫症非要用小写,可以在批处理里用小写映射表,但我觉得没必要。

接下来就是真正执行命令。这里我做了一个我认为非常实用的处理:在运行脚本之前,先自动把 CRLF 换行符转成 LF。

wsl.exe -d "%WSL_DISTRO%" bash -lc "sed -i 's/\r$//' '!linuxpath!' && bash '!linuxpath!'"

sed -i 's/\r$//'是原地删除每行结尾的\r,这样不管你的脚本是在 Windows 记事本里写的,还是从别处拷贝出来的,到了 WSL 这边都能正常执行。&&表示前一个命令成功后才继续执行后面的bash。这个处理的代价是会原样修改你的 .sh 文件,让它的换行符彻底变成 LF。如果你不希望改动原始文件,可以把命令换成:

wsl.exe -d "%WSL_DISTRO%" bash -lc "bash <(tr -d '\r' < '!linuxpath!')"

这个写法用进程替换把去除\r后的内容直接喂给 bash,不落盘、不改文件。但它有个副作用:脚本里如果用了相对路径访问同目录下的其他文件,当前工作目录可能对不上,脚本行为会不一样。所以我的个人习惯是直接sed -i,一劳永逸。

把脚本保存好之后,直接把D:\wsl-test\hello.sh拖到run-with-wsl.bat图标上,你会看到弹出一个 cmd 窗口,输出类似于:

Input: D:\wsl-test\hello.sh WSL path: /mnt/d/wsl-test/hello.sh Hello from WSL Current path: /mnt/d/wsl-test Script finished.

注意pwd输出的是/mnt/d/wsl-test,这说明 WSL 自动把当前工作目录映射到了你 Windows 文件所在的目录,这个行为对脚本里使用相对路径特别友好。

保存 bat 文件时要注意编码。如果你在 bat 里写了中文提示,我建议把整个文件保存成 ANSI 编码,或者干脆像我上面给出的脚本一样,提示信息全用英文。因为chcp 65001只影响控制台输出,对 cmd 解析批处理文件本身的作用并不完全可靠,混合编码很容易出现乱码或者奇怪的报错。

3.3 多文件拖拽的升级版

单文件版本够用,但有时候我们需要一次性跑多个脚本。比如我拿到一批固件样本,每个样本有一个独立的analyze.sh,我希望能一次把三个文件都拖到 bat 上,逐个执行。

多文件版本用for循环遍历参数即可。代码升级如下:

@echo off chcp 65001 >nul setlocal enabledelayedexpansion set "WSL_DISTRO=Ubuntu-24.04" if "%~1"=="" ( echo Drag one or more .sh files onto this batch file. pause exit /b 1 ) for %%F in (%*) do ( call :run_one "%%~F" ) echo. echo All scripts finished. pause exit /b 0 :run_one set "winpath=%~1" set "linuxpath=%winpath:\=/%" if /i "!linuxpath:~0,2!"=="C:" ( set "drive=!linuxpath:~0,1!" set "linuxpath=/mnt/!drive!/!linuxpath:~2!" ) else ( echo Unsupported path: !winpath! exit /b 0 ) echo. echo Running: !linuxpath! if defined WSL_DISTRO ( wsl.exe -d "%WSL_DISTRO%" bash -lc "sed -i 's/\r$//' '!linuxpath!' && bash '!linuxpath!'" ) else ( wsl.exe bash -lc "sed -i 's/\r$//' '!linuxpath!' && bash '!linuxpath!'" ) exit /b 0

核心变化是把执行逻辑封装成一个子过程:run_one,主流程用for %%F in (%*)遍历所有拖进来的文件,逐个调用。注意call :run_one "%%~F"这里先去掉引号,再由call把参数安全地传进子过程,这样即使在子过程里重新加引号,也不会出现嵌套问题。

多文件场景下有一个值得注意的行为:每个脚本的执行是串行的,前一个跑完才跑下一个。这符合大多数人的预期。如果你的脚本本身需要交互输入,建议还是单个文件拖拽,否则多个脚本交互会混在一起。

3.4 进阶:给 .sh 文件加“在 WSL 中运行”右键菜单

拖拽方案虽然好用,但有些人更喜欢右键菜单。尤其当你每天都要跑同一个位置的若干脚本时,右键点一下肯定比拖拽更快。

我采用的方式是修改用户级文件关联注册表,让.sh文件关联到一个自定义 ProgID,然后在这个 ProgID 下面注册右键动作。因为用的是HKEY_CURRENT_USER\Software\Classes,所以不需要管理员权限。

把下面内容保存成一个.reg文件,双击导入。注意把命令里的路径改成你放置run-with-wsl.bat的实际路径。

Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Classes\.sh] @="WSLScript" [HKEY_CURRENT_USER\Software\Classes\.sh\OpenWithProgids] "WSLScript"="" [HKEY_CURRENT_USER\Software\Classes\WSLScript] @="WSL SH Script" [HKEY_CURRENT_USER\Software\Classes\WSLScript\DefaultIcon] @="%SystemRoot%\\System32\\wsl.exe,0" [HKEY_CURRENT_USER\Software\Classes\WSLScript\shell\open\command] @="\"C:\\tools\\run-with-wsl.bat\" \"%1\"" [HKEY_CURRENT_USER\Software\Classes\WSLScript\shell\runwsl] @="Run with WSL" [HKEY_CURRENT_USER\Software\Classes\WSLScript\shell\runwsl\command] @="\"C:\\tools\\run-with-wsl.bat\" \"%1\""

这个注册表文件做了几件事:

  1. .sh扩展名的默认 ProgID 设置成WSLScript。从这一刻起,双击任何 .sh 文件,Windows 就会去查WSLScript这个 ProgID 下的open命令,也就是执行run-with-wsl.bat
  2. 定义了DefaultIcon,让 .sh 文件在资源管理器里显示成 wsl.exe 的图标,从视觉上提醒你这是“会用 WSL 执行的脚本”。
  3. 定义了runwsl这个 shell 动词,右键菜单里会出现“Run with WSL”选项,效果和双击一样。

导入之后,如果发现右键菜单没有立刻生效,可以注销再登录,或者重启 explorer.exe:

taskkill /f /im explorer.exe && start explorer.exe

这里有一个非常重要的权衡,必须说清楚:修改默认关联之后,双击 .sh 文件将不再用 VSCode、记事本之类的编辑器打开,而是直接执行。如果你只是希望偶尔用 WSL 跑一下,更推荐用“发送到”方案,因为那不会改变默认关联。

如果你确实需要保留编辑器打开能力,可以把open命令指向编辑器,比如 VSCode,然后新增一个runwsl动词作为右键菜单项。具体做法是把上面.reg文件里的...\shell\open\command改成:

[HKEY_CURRENT_USER\Software\Classes\WSLScript\shell\open\command] @="\"C:\\Users\\你的用户名\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe\" \"%1\""

但这样双击又会变成编辑器打开,和“直接运行”正好相反。反正鱼和熊掌不可兼得,大家根据自己的习惯选择就行。

3.5 最简单的部署:把工具放进“发送到”菜单

如果你只是想试用一下,又不想改任何注册表,发送到菜单是最友好的方式。

Win + R,输入:

shell:sendto

回车后会打开“发送到”文件夹。把run-with-wsl.bat文件复制进去,或者创建一个快捷方式放进去,名字改成“运行到 WSL”之类的直观名称。之后选中任意一个 .sh 文件,右键,选择“发送到 -> 运行到 WSL”,批处理就会收到文件路径并执行。

这个方案的好处是完全不干扰现有的文件关联,对 .sh 文件的默认打开方式没有任何影响。坏处是右键菜单多了一层“发送到”,操作路径比直接右键运行长一步。但作为入门体验,它已经足够好了。

4. 实操中的坑与排查技巧

4.1 CRLF 换行符:最常见的第一个坑

我刚做这个工具的时候,第一次拖一个在 Windows 记事本里编写脚本运行,WSL 那边直接给我来了个莫名奇妙的错误:

$'\r': command not found

这个错误非常典型。Windows 记事本默认使用 CRLF 作为换行符,也就是每一行结尾是\r\n两个字符。Linux 的 bash 只认\n,当你把 CRLF 文件拿给 bash 时,每一行末尾多出的那个\r就会被当成命令的一部分。最常见的结果就是最后一行的命令后面莫名其妙跟着一个\r,bash 找不到叫这个名字的命令,于是报错。

解决办法有三个:

第一种,在 WSL 里安装 dos2unix,然后手动转换:

dos2unix /mnt/d/wsl-test/hello.sh

第二种,直接用 sed 原地转换,这也是我在工具里采用的方式:

sed -i 's/\r$//' /mnt/d/wsl-test/hello.sh

第三种,使用tr但不修改原文件:

bash <(tr -d '\r' < /mnt/d/wsl-test/hello.sh)

我的建议是让 bat 自动执行第二种。因为脚本本身是给 Linux 用的,保持 LF 换行才是标准状态。第一次运行之后,文件就彻底正常了,以后再拖也不会再触发这个问题。

4.2 路径里有空格、中文和特殊符号

Windows 路径里带空格非常常见,比如D:\My Scripts\deploy test.sh。只要你的批处理正确地使用了%~1,空格不是问题。资源管理器传参数时已经给路径加上了引号,%~1能正确取到完整路径。

中文路径也是可以的。Windows 路径中的中文字符在传给 WSL 之后,只要 WSL 的 locale 是 UTF-8,绝大多数情况下都能正常处理。保险起见,建议在 WSL 里执行locale看输出,如果是C.UTF-8或者en_US.UTF-8,中文路径基本没问题。如果出现乱码,修改 WSL 的/etc/default/locale,确保是 UTF-8 即可。

真正麻烦的是&%这类字符。&在 cmd 里是命令连接符,如果你直接写set var=%1,当路径里有&时,后面的内容会被当成新的命令执行,非常危险。这也是我在脚本里一直坚持set "var=%~1"的原因,引号把整个赋值语句的保护起来,&就只是路径里的一个普通字符。%在 cmd 里是变量引用符号,出现在路径里也会被展开,处理起来更麻烦一些。我的建议是:如果路径里出现了%,尽量不要走 bat 方案,直接手动改一下文件名,省得和自己较劲。

4.3 wsl.exe 启动慢、找不到发行版

第一次拖拽执行的时候,你会明显感觉窗口卡了大概一两秒才出结果,这是 WSL 冷启动的正常现象。如果你的 WSL 发行版刚好处于停止状态,wsl.exe 会先启动子系统,再执行命令,耗时自然会长一些。如果经常要高频调用,可以在 Windows 开机时让 WSL 保持运行,或者接受这个延迟。多数情况下,一两秒的延迟对跑脚本来说完全可接受。

如果执行时遇到:

There is no distribution installed.

说明你的 WSL 里还没有任何发行版,先执行:

wsl --install -d Ubuntu-24.04

如果是:

The specified distribution is not valid.

说明WSL_DISTRO配置错了。用wsl -l -q查看准确的发行版名字,注意大小写。比如Ubuntu-24.04中间是短横线,UbuntuUbuntu-24.04是两个不同的发行版,别混了。

还有一个小概率问题:Windows 提示 “WSL needs updating”。这是因为系统的 WSL 内核太老。执行wsl --update更新到最新版即可。如果更新速度很慢,可以考虑检查 Windows 更新和网络,但不影响这个脚本本身的逻辑。

4.4 窗口闪退和输出乱码

如果你拖拽后窗口一闪而过,什么都没看到,最常见的原因是批处理最后没有pause。这个工具里我在正常结束和异常分支都加了pause,所以理论上不会闪退。如果还是闪退,你可以先打开一个 cmd 窗口,手动执行一下:

run-with-wsl.bat "D:\wsl-test\hello.sh"

这样窗口不会关闭,能看到具体报错信息。逐行排查路径转换和wsl.exe调用语句。

关于乱码,主要是控制台代码页的问题。Windows 默认代码页可能是 936(GBK),而 WSL 输出的是 UTF-8 字符序列,两者不对应就会出现中文乱码。脚本开头的chcp 65001 >nul就是干这个的。但要注意,如果 bat 文件里的提示文字本身是 UTF-8 编码,而 cmd 在读取批处理时的代码页还是 936,那么提示文字反而可能乱码。最稳妥的办法是:bat 文件里的提示一律用英文,或者把 bat 保存成 ANSI 编码,只在执行 WSL 命令时切到 UTF-8。

另外,如果你的脚本里有需要交互式输入的地方,比如read -p "input: ",拖拽运行也可以正常交互,因为 wsl.exe 会继承当前 cmd 窗口的标准输入输出。你会在窗口里看到提示,直接打字回车就行。唯一的限制是脚本执行结束后窗口会停在pause那里,需要按任意键才会关闭,这正好方便你查看输出。

4.5 常见问题速查表

现象原因解决办法
拖拽后提示 Input 为空没有文件被正确传入确认直接拖 .sh 文件到 bat 图标上,看%~1是否为空
WSL 报$'\r': command not foundCRLF 换行符脚本已自动执行sed -i 's/\r$//',如果还报错,手动跑一次 sed
输出中文乱码控制台代码页不对脚本开头有chcp 65001,确认 bat 文件编码没有冲突
找不到发行版发行版未安装或名字写错执行wsl -l -q查名字,检查WSL_DISTRO配置
窗口一闪而过批处理缺少 pause正常脚本已加 pause,手动执行可看错误
/mnt/C/xxx访问失败个别 WSL 版本对盘符大小写敏感改成小写盘符路径,或者用wslpath -u转换

5. 还能怎么玩:不止是跑 .sh 脚本

这套“拖拽 + WSL 执行”的思路并不局限于 .sh 脚本。原理搞明白之后,你可以把 bat 改成支持任意扩展名,或者用同样的方式跑 Python 脚本:

wsl.exe -d "%WSL_DISTRO%" bash -lc "python3 '!linuxpath!'"

也可以拖一个 jar 包过去,让 WSL 里的 java 环境跑起来:

wsl.exe -d "%WSL_DISTRO%" bash -lc "java -jar '!linuxpath!'"

我实际工作中用这个工具最多的场景,是配合那些部署在 Linux 上的服务管理脚本。比如我有一台负责跑内部工具的机器,经常要更新一个 Spring Boot 的 jar,传统的操作是登录 Linux、把 jar 传到指定目录、执行 restart.sh。现在我直接在 Windows 这边把deploy.sh拖到 bat 上,WSL 会自动挂载 Windows 目录、执行脚本,脚本里再通过 scp 或 rsync 把 jar 推到目标机器,整个过程不用手动进 WSL 窗口敲一行字。

binwalk 也是我很常用的场景。Windows 下根本没有像样的 binwalk 体验,WSL 里装好之后,我的分析脚本写成一个 .sh,拖进去就跑,自动解包固件、输出分析报告。redis、elasticsearch 这类服务的启动/停止脚本同样适合这种玩法,WSL 里装好服务,Windows 这边写一个start.shstop.sh,日常操作就像点按钮一样简单。

这套方案对路径转换的要求很直接,但如果你有更复杂的路径处理需求,比如 UNC 路径或者网络驱动器,推荐在 bat 里优先使用wslpath做转换,会更可靠:

for /f "delims=" %%P in ('wsl.exe wslpath -u "!winpath!" 2^>nul') do set "linuxpath=%%P"

我自己是先用字符串替换,遇到特殊情况再手动用 wslpath 补一下,两条路都留着,工具就更通用。

最后再分享一个细节。很多 .sh 脚本在开头会写#!/bin/bash,这个 shebang 在 Linux 里是给内核看的。但当我们用bash script.sh的方式调用时,shebang 并不重要。真正重要的是脚本是否有可执行权限。如果你在 Windows 下给脚本加了执行权限,拖到 bat 里仍然走bash xxx.sh,所以权限有没有都无所谓。但如果你哪天直接在 WSL 里执行./xxx.sh,那就要注意先chmod +x xxx.sh了。这一点经常被人忽略,顺手记一下就行。

我用这个拖拽运行工具已经攒了很久,现在基本离不开它。最重要的收获不是省了多少敲命令的时间,而是它彻底解决了 Windows 和 WSL 两个世界之间“路径转换”这个恼人的心智负担。你只需要关注脚本本身要干什么,剩下的交给一个小小的 bat 文件去处理。如果你也在 WSL 里跑一堆脚本,强烈建议花十分钟把这个工具搭起来,相信我,用上之后你就再也不想手敲那些又长又容易出错的命令了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询