
双击.sh文件弹出记事本的那一刻大概每一个在 Windows 上折腾 WSL 的人都经历过。明明 WSL 装好了、Ubuntu 也跑起来了可辛辛苦苦写的 shell 脚本却只能在终端里手敲路径执行。网上搜到的方案大多是“打开 WSL 窗口cd 到目录bash 脚本名”用一次两次还好天天用就会觉得这步操作纯属浪费时间。这篇就是想把这个痛点彻底解决让脚本文件像 Windows 下的.bat一样直接拖拽到某个入口就自动在 WSL 里跑起来。先说明一下适用人群你不需要是 Linux 老手只要 WSL 环境能正常使用wsl --install -d Ubuntu-24.04装好的那种就行能看懂几条批处理命令就能照着抄。整套方案不依赖额外软件纯 Windows 自带功能实现改几个字符就能适配你自己的目录和脚本。1. 为什么.sh在Windows上双击没有反应执行链路先搞清楚想实现“拖拽运行”首先要理解 Windows 和 WSL 之间到底是怎么协作的。很多人在这里卡住不是因为缺少工具而是没弄明白 .sh 文件在 Windows 眼里到底是什么东西。1.1 Windows 不认 shebang只认文件关联Linux 下执行脚本靠的是文件开头的#!/bin/bash这类 shebang 声明系统看到这行就知道该用哪个解释器。但 Windows 没有这个概念它决定“双击某个文件用什么程序打开”靠的是注册表里的文件关联。.sh后缀在默认 Windows 环境里没有绑定到任何执行器大部分情况下会落到“选择默认应用”或者干脆用记事本打开。就算你把 .sh 关联到bash.exeWindows 传递的还是C:\Users\xxx\test.sh这种带盘符反斜杠的路径WSL 里的 bash 根本不认识。这就是单纯改关联也跑不起来的深层原因。所以“拖拽运行”这件事的本质不是让 Windows 直接执行 .sh而是做一个中转把 Windows 路径转换成 WSL 路径再交给 WSL 里的 bash 解释执行。1.2 WSL 执行脚本的几种姿势和差异WSL 安装好之后Windows 侧会多出一个wsl.exe命令行工具。在 CMD 或 PowerShell 里执行 wsl 相关命令实际上就是把请求转发给默认的 Linux 发行版。常见的调用方式有这么几种命令写法效果与注意点wsl进入默认发行版的交互式 shell相当于打开一个 Linux 终端wsl bash -c 命令在默认发行版里执行单条命令命令放在双引号里wsl -d Ubuntu-24.04 bash -c 命令指定发行版执行适合机器上装了多个发行版的场景wsl bash 脚本路径让 WSL 里的 bash 直接运行脚本但路径必须是 Linux 风格第三个和第四个写法的核心区别很多人容易忽略如果你写wsl bash C:\Users\xxx\test.shWSL 侧接到的参数是带反斜杠的 Windows 路径bash 会把它当成一个名字里带反斜杠的文件结果通常是No such file or directory。所以路径转换不是可选项是必选项。1.3 隐藏的 wslpath 工具是路径转换的正解路径转换这事最不推荐的做法是在批处理里用字符串替换硬转比如把C:替换成/mnt/c。遇到C:\Users、D:\data这种简单的倒还好一旦路径里有空格、中文、括号字符串替换的方案就会崩给你看。正解是 WSL 自带的wslpath命令。你可以在 Windows 的 CMD 里直接这样调wsl wslpath -a C:\Users\admin\my script\test.sh输出结果会是/mnt/c/Users/admin/my script/test.sh-a参数表示“给出绝对路径的转换结果”这个命令会自行处理空格和中文编码基本不会出错。我后来做的方案里路径转换这一步全部交给 wslpath 处理再也没有自己拼过路径。2. 拖拽的想法怎么落地批处理就是最顺手的“接收器”原理清楚了接下来要解决的是“接收拖拽”这件事。Windows 里能接收文件拖拽的载体有不少但论省事、零依赖批处理文件.bat是首选。2.1 为什么批处理能接收拖拽而其他方案不行Windows 的资源管理器有一个非常老的特性当你把文件拖到某个可执行程序或批处理文件上时系统会用该文件路径作为参数去启动目标程序。对批处理来说这意味着被拖拽的文件路径会出现在%1变量里。举个例子你写一个run.bat内容只有一行echo %1把任意文件拖到它的图标上松开CMD 窗口里就会打印出这个文件的完整路径包括双引号。这个机制从 Win95 时代就有了稳定得离谱也正因为它是系统层面的行为所以不需要安装任何第三方工具或编写额外服务。相比之下PowerShell 脚本虽然也能干这事但默认执行策略限制可能导致脚本双击后一闪而过用右键“发送到”菜单可以但每次都多迈一步不如拖拽直接。所以我的选择是做批处理把拖拽进来的路径统一转发给wsl.exe。2.2 接收拖拽时的参数坑空格路径和多个文件拖拽看起来简单但如果你认真写批处理会发现两个坑。一个坑是路径带空格。资源管理器拖拽时会给带空格或有特殊字符的路径自动加上双引号所以%1的值可能是C:\Users\admin\my script\test.sh。在批处理里引用变量时建议用%~1而不是%1前者会自动剥掉首尾的双引号拿到干干净净的路径。另一个坑是多文件拖拽。Windows 允许多选然后一起拖到批处理图标上这时候%1只包含第一个文件而%*包含全部参数。想要一个个处理就得用shift配合循环。我后面的完整代码里会写清楚单文件拖拽其实已经满足绝大多数需求了但多文件的处理逻辑值得顺手加上。2.3 第一步做一个只处理单文件的最简版本先别急着写完整版手把手做个能跑的最简版本。在任意目录新建run-in-wsl.bat内容如下echo off if %~1 ( echo 请把一个 .sh 文件拖拽到本脚本上。 pause exit /b ) set WIN_PATH%~1 for /f delims %%i in (wsl wslpath -a %WIN_PATH%) do set LINUX_PATH%%i echo Linux 路径: %LINUX_PATH% wsl bash %LINUX_PATH% pause这里解释三处关键设计if %~1用来判断是否真的拖拽了文件没有参数就直接退出避免双击后窗口一闪而过。for /f负责执行wsl wslpath命令并捕获输出结果这是把 Windows 路径转换成 Linux 路径的关键一步。delims表示不要按空白拆分整行内容保证带空格路径的完整性。wsl bash %LINUX_PATH%把转换好的路径交给 WSL 里的 bash 执行。注意这里用的是bash 脚本路径而不是./脚本路径原因后面会单独讲。如果文件是 Windows 换行符这个版本可能直接报错但先不用管把流程跑通再说。3. 完整实现单文件版、多文件版、固定脚本专用版最简版本能跑通之后接下来要处理的是实际使用中更“像样”的版本。我给三个不同场景分别做过配置你可以直接抄。3.1 通用版支持拖多个文件、自动切到脚本所在目录最常用的版本长这样echo off chcp 65001 nul title WSL Script Runner if %~1 ( echo 请将一个或多个脚本文件拖拽到本窗口。 pause exit /b ) :loop if %~1 goto :done set WIN_PATH%~1 for /f delims %%i in (wsl wslpath -a %WIN_PATH%) do set LINUX_PATH%%i for /f delims %%d in (wsl dirname %LINUX_PATH%) do set SCRIPT_DIR%%d echo. echo 正在 WSL 中执行: %LINUX_PATH% wsl bash -c cd %SCRIPT_DIR% bash %LINUX_PATH% if errorlevel 1 ( echo 脚本执行出错退出码: %errorlevel% ) shift goto loop :done echo. echo 全部执行完毕。 pause几个设计细节chcp 65001 nul把控制台代码页切到 UTF-8避免 WSL 输出的中文乱码。但这里有个前提bat 文件本身最好保存为 ANSI 或带 BOM 的 UTF-8否则 echo 里的中文会变成乱码无所谓的话全部用英文提示最省心。shift每处理完一个文件就移动参数列表配合goto loop实现循环处理多个拖拽文件。cd %SCRIPT_DIR%是关键一步。脚本里如果有相对路径引用同目录下的其他文件不在脚本所在目录启动就会找不到文件。加了这行之后脚本无论在哪里被拖进来工作目录都是它自己的目录。3.2 固定脚本专用版鼠标一拖就执行 up.sh 这类固定任务如果你的场景是“每天都跑同一个脚本”比如之前热词里出现过的up.sh、刷机脚本、备份脚本那更推荐做成固定脚本专用版。把脚本路径和默认参数写死在 bat 里拖不拖文件都无所谓了echo off chcp 65001 nul set DISTROUbuntu-24.04 set SCRIPT_NAME/mnt/c/Users/admin/scripts/up.sh echo 正在执行 %SCRIPT_NAME% wsl -d %DISTRO% bash -c cd $(dirname %SCRIPT_NAME%) bash %SCRIPT_NAME% pause你可能会问既然路径都写死了直接双击运行不就行了为什么还要留拖拽随机文件的能力我的做法是在这个固定脚本基础上再加一条逻辑如果拖拽了文件就用拖进来的文件没拖就用默认脚本。但现实是大多数用户根本不会拖错固定版反而更简洁。3.3 同时装多个 WSL 发行版时怎么指定比 WSL 1 升 WSL 2 更常见的场景是机器上同时装着 Ubuntu 20.04 和 Ubuntu 24.04这时候直接敲wsl bash进的是默认发行版不一定是你想要的那个。解决办法是用wsl -d参数指定发行版名称。可以在 bat 顶部加一个变量set WSL_DISTROUbuntu-24.04然后所有调用都写成wsl -d %WSL_DISTRO%。发行版名称可以用wsl --list --verbose查到复制粘贴进去就行。我还见过一个进阶做法用wsl --set-default把常用发行版设为默认然后在 bat 里仍然用wsl不带参数。好处是省一次字符输入坏处是如果别人用了你的脚本跑的可能不是预期环境。4. 工作目录、路径传参和WSL发行版从“能跑”到“好用”拖拽方案最基本的版本上面已经能跑了。但用到第三天你就会遇到几个真问题比如脚本执行后输出找不到文件、明明运行在高权限场景却不生效、拖拽进去的脚本想带参数怎么办。这节把这些问题一次说清。4.1 为什么脚本里用了相对路径就找不到文件WSL 的行为和 Linux 类似bash 执行脚本时当前工作目录不会自动切换到脚本所在目录。你在 Windows 的 D 盘拖了一个D:\project\deploy\build.sh脚本内部写的是cp ./config/app.conf ./dist/此时./指向的是批处理进程启动时的目录通常是 bat 文件所在目录跟脚本目录可能差了十万八千里。解决方式有两条路在 bat 里用dirname算好脚本目录再cd进去执行就是我上面的做法。在脚本内部自己写cd $(dirname $0)让脚本自校正。我推荐第一条因为不是所有脚本都愿意被你改。用dirname的命令在批处理里这样写for /f delims %%d in (wsl dirname %LINUX_PATH%) do set SCRIPT_DIR%%d然后执行时wsl bash -c cd %SCRIPT_DIR% bash %LINUX_PATH%。需要注意%LINUX_PATH%已经带上了单引号所以哪怕路径里有空格也能安全传递。4.2 拖拽进来的脚本想带参数怎么办Windows 拖拽行为本身不会给目标程序附加额外参数你拖的只是一个文件路径参数得自己想办法。分两种情况如果参数是固定的直接写死在 bat 里wsl bash -c cd %SCRIPT_DIR% bash %LINUX_PATH% --force如果参数是每次执行时临时想的可以在 bat 里加一行交互输入set /p SCRIPT_ARGS请输入脚本参数: wsl bash -c cd %SCRIPT_DIR% bash %LINUX_PATH% %SCRIPT_ARGS%这里有个使用细节set /p获取到的是 Windows 侧的变量传到bash -c的字符串里时如果参数本身带空格或者这类特殊字符容易出问题。我的建议是交互参数先假设为简单无空格的场景复杂参数直接进 Linux 终端手敲不要硬塞进拖拽流程里。4.3 脚本在 Windows 盘还是 Linux 盘执行差异非常大WSL 挂载的 Windows 盘符路径默认在/mnt/c、/mnt/d下。从这里执行脚本会经过 9P 文件系统协议性能和纯 Linux 文件系统比如~/scripts有明显差距。如果你的脚本涉及大量文件读写从/mnt/c跑可能比在 Linux 原生目录里跑慢 3 到 5 倍。这点被很多人忽略。我实际测试过一个部署脚本里面要压缩一千多个小文件放在/mnt/c下跑耗时 40 秒复制到~/scripts下跑只要 12 秒。所以一个值得养成的习惯是频繁执行、重读写的脚本建议直接放在 WSL 的 Linux 侧文件系统里然后 bat 里写死 Linux 路径set LINUX_PATH/home/admin/scripts/deploy.sh wsl bash -c cd /home/admin/scripts bash deploy.sh这种写法甚至不需要 wslpath 转换因为路径本来就是 Linux 格式。拖拽方案也兼容你依然可以把文件拖到 bat 上如果检测到路径以\\wsl$开头用wslpath转出来也是/home/...逻辑不用改。4.4 把 bat 固化成快捷方式或发送到菜单bat 文件本身用久了会觉得有点“丑”因为每次都要先定位到那个文件再拖拽。我做了两处体验升级第一把常用的固定脚本 bat 发送到“发送到”菜单。按Win R输入shell:sendto打开目录后把 bat 的快捷方式放进去。以后在任何文件资源管理器里右键 .sh 文件选“发送到 → WSL 运行”效果和拖拽一样。第二如果想拖拽到任务栏可以给 bat 建一个快捷方式固定到任务栏。然后从文件夹里拖 .sh 文件到任务栏图标上等提示出现再松手。不过这个操作在部分 Windows 版本上可能不响应我更推荐桌面快捷方式或者直接拖到 bat 文件的图标上稳定性最高。5. 实测容易翻车的几个点CRLF、权限、编码、安全提醒写了好几个版本都离不开这个忠告不要以为拖拽过去能弹出窗口就算结束。真正让人崩溃的往往是一些看着很基础的问题我一个个说。5.1 Windows 换行符导致的报错bad interpreter 与 command not found在 Windows 上编辑过 .sh 文件的人大概率会遇到这个经典报错-bash: ./test.sh: /bin/bash^M: bad interpreter: No such file or directory或者执行时明明脚本里有这个命令却提示command not found。原因很好理解Windows 的文本文件换行符是\r\nLinux 只认\n。VSCode、Notepad 这类工具如果没设置默认行尾保存出来的 sh 文件就带上了\r。bash 解释器读到第一行的/bin/bash\r会认为解释器路径是 “/bin/bash 加回车符”自然找不到。解决方式不唯一在编辑器里改默认行尾为 LF这是治本。在拖拽运行前自动清理 CRLF。可以给通用版 bat 加一条wsl bash -c sed -i s/\r$// %LINUX_PATH%我实测过在拖拽执行前先跑这条 sed能顺手把 Windows 编辑导致的 CRLF 转干净。但注意如果脚本文件本身是二进制或者有特殊编码sed 有可能误伤所以建议只对文本类 .sh 脚本启用。5.2 权限问题为什么直接 ./test.sh 执行权限不足用 bash test.sh 就行拖拽方案里我统一用的是bash 脚本路径不是./脚本路径。这两者的区别经常让新手困惑。./test.sh依赖文件的“可执行权限位”Windows 文件拖到 WSL 后默认没有 x 权限直接执行会报Permission denied。bash test.sh是把脚本当作 bash 的输入参数只要文件可读就行不要求可执行权限。所以用bash 脚本路径这个方式最省心。但有个隐含代价如果脚本内部再执行./xxx这样的命令仍会受权限限制。这时候就要在拖拽后先chmod xwsl bash -c chmod x %LINUX_PATH% bash %LINUX_PATH%5.3 CMD 窗口里的中文乱码根源代码页与文件编码不一致WSL 输出 UTF-8 编码的中文CMD 默认代码页可能是 GBK936或 UTF-865001不一致就会乱码。我在 bat 开头加的chcp 65001 nul就是在切换代码页。这个操作本身很简单但和 bat 文件的编码搭配讲究bat 文件保存为 ANSIGBK加chcp 65001后echo 中文反而会乱。bat 文件保存为 UTF-8 with BOM加chcp 65001后echo 中文正常。bat 文件保存为 UTF-8 without BOM最危险中文 echo 大概率乱。实际操作中我的建议是如果只是自己用bat 里不写中文字符全部用英文 echo。因为 chcp 只在当前窗口生效你永远说不准下一个打开这个 bat 的人是不是用着英文系统。不是面子问题是少踩一个坑。5.4 安全提醒拖拽执行等于把文件交给 WSL 当命令跑每次运行带“拖拽”“执行”性质的方案都得提一句安全意识。拖拽执行本质上等价于在 WSL 里执行任意脚本。如果脚本来源不明或者是从网上下载后直接拖进去的建议先通读一遍脚本内容再执行。尤其不要为了方便在网上复制一个“万能执行脚本”绑到拖拽入口上。另一个容易被忽略的隐患是WSL 和 Windows 的文件权限互通意味着在 WSL 里可以访问你 Windows 用户目录下的几乎所有文件。如果你只在某个发行版里执行脚本这个发行版的 root 权限就是该机器上的最高权限。拖拽入口只是提升了便利性并没有改变 WSL 本身的权限模型。5.5 批量执行多个脚本时窗口消失或提前退出多文件版设计了pause和循环但如果你把多个脚本拖进来其中一个脚本内部执行了exit它会把 WSL 会话退出后续脚本就不会跑了。这种情况不多但确实会遇到。规避办法是每个脚本单独启动一个 WSL 会话也就是我代码里的wsl bash -c ... bash ...已经做到了这点。每个文件都是独立的 WSL 调用前一个退出不影响后一个。如果你想要更细的控制可以在循环里加一个确认步骤echo 准备执行下一个文件按任意键继续... pause nul这样在跑一串脚本时还能挨个盯输出不容易看漏错误信息。我在实际使用这套拖拽方案长达半年后最大的体会是它解决的并不是一个高深的技术难题而是把“Windows 用户 WSL 环境”这段链路中最后那一步摩擦降到了最低。以前是复制路径、开终端、敲命令三个动作现在变成一拖一放一个回车整个流程的肌肉记忆完全变了。如果你平时也在 WSL 里跑构建、部署或者各种运维脚本这套入口值得用十分钟搭起来之后每天能省下的时间绝对不止十分钟。最后再分享一个我后来往里加的小细节在 bat 里顺手调用了wsl df -h /mnt/c打印磁盘剩余空间执行完脚本就能顺带看到 Windows 盘还有多少余量这个信息在部署脚本调磁盘占用时特别直观。