Windows 下装了 WSL + Ubuntu 之后,写好的.sh脚本一直有个尴尬:终端里跑没问题,但桌面场景下想“像 Windows 程序一样把它运行起来”,就得开终端、敲bash /mnt/c/.../xxx.sh一长串路径。拖动文件到终端窗口里倒是能把路径带进去,但也不够干脆,尤其面对那些固定步骤的部署脚本、备份脚本、批处理脚本,每次都要开终端找目录,属实浪费生命。
这篇文章就是把“Windows 拖拽运行 WSL SH 脚本”这件事彻底做明白。我会先讲清楚 WSL 脚本在 Windows 侧的调用逻辑,再给出一份可以直接抄的.bat拖拽运行器,最后补上文件夹拖入、发送到菜单、右键菜单等进阶玩法。整个方案不依赖额外安装任何东西,纯用 WSL 自带的wsl.exe/bash.exe/wslpath命令组合,一台装好 WSL2 + Ubuntu 的 Windows 10/11 机器就能直接用。适合两种人:一是刚把 WSL 配好、还在被命令行劝退的新手;二是平时要反复跑部署脚本、备份脚本、批量处理脚本的老手——把常用脚本拖上去就执行,能少敲很多命令。
1. 为什么放着好好的终端不用,偏要折腾“拖拽运行”
1.1 从一次“明明脚本没问题,却总不想打开终端”的体验说起
我有几个固定要跑的.sh脚本,比如同步服务器配置、压缩日志、批量重命名文件。它们都不复杂,语法也稳定,但问题出在“启动方式”上:每次都得打开 Windows Terminal,进入 WSL,Tab 补全路径,再bash xxx.sh。步骤不多,但架不住天天做,一旦那天手头正忙着别的,这点“额外动作”就会变成阻力,脚本就拖着不想跑了。
后来我试过把脚本扔到 WSL 的$PATH里,直接敲名字执行;也试过在 Windows 桌面建快捷方式,用wsl.exe指过去。快捷方式方案有个老毛病:工作目录不对,脚本里的相对路径经常失效。拖拽方案反而是最接近“Windows 原生操作”的那一个——把文件往一个入口上一扔,剩下的事自动完成。
1.2 拖拽运行器适合谁,不适合谁
先泼一盆冷水:拖拽运行不是万能药。
它最适合的场景是“无交互”脚本:不需要你中途输入参数、不需要长驻终端看实时日志、跑完看一眼退出码就行的那类。比如启动开发服务、执行数据库备份、触发一次构建、运行一个数据处理脚本。
它不适合的场景也很明确:需要交互式输入、需要持续输出日志到前台、依赖特定 TTY 行为的脚本。这类脚本你还是开一个正经终端窗口去跑,拖拽这种“一次性击发”的模式满足不了。
另外,拖拽运行本质上是双击运行,只不过换成了 Linux 环境。安全性上要保持基本意识:拖上去之前确认这个脚本是可信的,别随便拖来历不明的文件。
1.3 拖拽运行器能覆盖哪些实际场景
我整理了一下自己平时会用到的地方,供你参考:
- 部署 / 发布:一键执行
deploy.sh,把构建产物同步到服务器 - 数据处理:拖一个包含待处理文件的清单或脚本,让 WSL 里的 Python / ffmpeg / awk 处理
- 启动服务:WSL 里装了 Docker、Redis、Elasticsearch 等,用
.sh一键拉起相关容器或服务 - 定时/批量任务:日志清理、压缩归档、批量重命名,脚本本身固定,拖一下就跑
这篇文章后面所有方案,都是围绕这些“固定套路脚本”设计的。
2. WSL 脚本在 Windows 侧的几种调用姿势,以及它们各自的脾气
2.1wsl.exe和bash.exe到底有什么区别
在 Windows 里调 WSL 脚本,最常见的入口有两个:wsl.exe和bash.exe。很多人混着用,其实它们有细微差异。
wsl.exe是官方推荐的入口。它负责跟 WSL 服务层通信,会把参数传给默认发行版(或多个发行版里的指定发行版)去执行。bash.exe是早期 WSL 遗留下来的兼容入口,本质上也是调用默认发行版里的 bash,但它在 stdin/stdout 处理、参数传递的细节上跟wsl.exe有差别,新项目不建议依赖它。
| 调用方式 | 典型命令 | 特点 |
|---|---|---|
wsl.exe | wsl.exe -e bash -lc "bash /mnt/c/xxx/run.sh" | 官方入口,推荐;支持-d指定发行版 |
bash.exe | bash.exe -lc "bash /mnt/c/xxx/run.sh" | 早期兼容入口;行为相对固定,但扩展能力弱 |
wsl.exe -e直接执行 | wsl.exe -e wslpath -u "C:\xxx\run.sh" | 直接在 WSL 里执行单条命令,适合wslpath这类转换工具 |
还有一个容易混淆的点:wsl.exe在执行参数时,会对“长得像 Windows 路径”的参数做自动转换。例如wsl.exe cat C:\Users\admin\test.txt可能被自动转成cat /mnt/c/Users/admin/test.txt。这个特性在简单场景挺方便,但一旦参数被包在 bash 字符串里(比如bash -lc "cat C:\Users\admin\test.txt"),自动转换经常失效或者转换得不符合预期。拖拽场景里,脚本路径是动态的,我们宁可手动转换,也不赌自动转换。
2.2 那些年被\r\n和转义坑过的经历
在 Windows 侧调 WSL,最大的敌人不是 WSL 本身,而是“命令字符串在两层环境里的解析差异”。
第一层是 CMD:&&、|、>、%这些都是特殊字符。第二层是 bash:空格、单引号、双引号、$、反斜杠又是另一套规则。同一个字符串要经过 CMD 解析一次、WSL interop 传递一次、bash 再解析一次,任何一层考虑不周都会翻车。
举个真实的例子:如果你在.bat里写
wsl.exe -e bash -lc "echo $PWD"会发现输出结果不是$PWD的值,而是字符串$PWD或空的换行,因为 CMD 和 bash 对$PWD的解析方式完全不同。更常见的是脚本文件本身用记事本保存,换行符是 CRLF,丢到 WSL 里执行时报$'\r': command not found。这些坑不是理论问题,是每个 Windows + WSL 双修用户都会撞上的墙。
后面第 3 章我会用第一版拖拽脚本把这些坑一个个踩给你看。
2.3 认识wslpath:路径转换的官方钥匙
WSL 自带wslpath命令,专门负责 Windows 路径和 Linux 路径之间的转换。这是整个拖拽运行器最核心的工具。
wslpath最常见的用法:
# Windows 路径转 WSL 路径 wslpath -u 'C:\Users\admin\test.sh' # 输出:/mnt/c/Users/admin/test.sh # WSL 路径转 Windows 路径 wslpath -w '/mnt/c/Users/admin/test.sh' # 输出:C:\Users\admin\test.sh-u表示转换为 Unix/Linux 风格路径,-w表示转换为 Windows 风格路径。它不只是把盘符替换成/mnt/,还会正确处理反斜杠、大小写、相对路径等细节。从 Windows 侧调用的话,前面套一层wsl.exe -e即可:
wsl.exe -e wslpath -u "C:\Users\admin\test.sh"3. 初版拖拽方案:一个 .bat 就能跑,但三个坑藏得很深
3.1 第一版脚本长什么样
先给一个“能跑但很脆”的初版脚本,你感受一下问题:
@echo off set "FULL_PATH=%~1" wsl.exe -e bash -lc "bash '%FULL_PATH%'" pause把任意.sh文件拖到这个.bat上,如果路径里没有空格、脚本文件本身是 LF 换行、且不依赖相对路径,那它确实能跑。但只要是搬到一个真实的脚本环境里,各种问题就会冒出来。我先把三个最常踩的坑列一下。
3.2 坑一:%1和%~1的区别,以及路径带空格时的“看起来正常”陷阱
拖拽文件到.bat上时,Windows 会把这个文件的完整路径当作参数传给批处理。如果路径里有空格,Windows 传过来的参数会自动带上引号,比如"C:\Users\admin\my folder\test.sh"。
在批处理里:
%1拿到的是“带引号”的原始参数;%~1拿到的是“去掉引号”之后的参数。
初版脚本里我用了%~1,这一点是对的。但问题出在下一步:bash '%FULL_PATH%'。
这里FULL_PATH的值是C:\Users\admin\my folder\test.sh,注意它已经没有外层引号了。拼进wsl.exe -e bash -lc "..."之后,CMD 看到的字符串是:
bash 'C:\Users\admin\my folder\test.sh'CMD 不把单引号当作引号,它只看双引号。这一串外面没有任何双引号,于是按空格拆分,命令变成了三四个 token:bash、'C:\Users\admin\my、folder\test.sh'。bash 那边收到的参数完全是乱的,结果就是路径带空格必炸,不带空格时又恰好正常。这种“看运气”的行为是最坑的,它会让你误以为脚本写得没问题,直到某天路径里出现一个空格。
3.3 坑二:WSL 内工作目录不在脚本目录,相对路径全都失效
假设脚本本身是好的,里面有一些操作是cd ./config、source ./env.sh这样的相对路径。你用上面那个初版脚本去拖,大概率会报“找不到文件”。
原因是:wsl.exe启动时的工作目录,默认是你调用它的那个 Windows 当前目录。比如你把.bat放在桌面上,WSL 里的工作目录就是/mnt/c/Users/你的用户名/Desktop。你的脚本在D:\scripts\deploy.sh,脚本里写的./config会跑到桌面对应的目录里去找,自然找不到。
要让脚本正常运行,得先把工作目录切到脚本所在的目录,再执行脚本。这个坑在终端里不明显,因为大多数人会先cd /d到脚本目录再运行;但拖拽场景把这一步省略了,问题就暴露得很直接。
3.4 坑三:Windows 记事本保存的脚本,在 WSL 里跑出$'\r': command not found
这是所有 Windows 写脚本的人都会遇到的问题:换行符。
Windows 记事本保存文本文件时,默认使用 CRLF(\r\n)作为行尾。Linux 只按 LF(\n)理解,脚本里每一行末尾多出来的\r会被 bash 当成命令的一部分,于是出现:
$'\r': command not found或者更隐蔽的表现:脚本第一行 shebang 写的是#!/bin/bash,但实际解释器看到的是#!/bin/bash\r,直接报No such file or directory或/usr/bin/env: 'bash\r': No such file or directory。
修复方式是在 WSL 里把这个脚本的换行符转成 LF:
sed -i 's/\r$//' /mnt/c/路径/脚本.sh或者安装dos2unix工具处理:
sudo apt install dos2unix dos2unix /mnt/c/路径/脚本.sh如果脚本是你自己在 Windows 里新建的,建议编辑器里把换行符设置为 LF 再保存。这个问题跟拖拽运行器本身关系不大,但拖拽方式最容易把它暴露出来。
4. 拖拽路径进 WSL 的核心:搞懂 Windows 路径与 /mnt/c 的互相转换
4.1 为什么C:\Users\admin\test.sh到 WSL 里会变成/mnt/c/Users/admin/test.sh
WSL2 的文件系统是独立的 Linux 文件系统,Windows 的磁盘通过 9P 协议挂载到 WSL 里。默认情况下,C 盘挂载在/mnt/c,D 盘挂载在/mnt/d,以此类推。
所以:
C:\Users\admin\test.sh对应到 WSL 里就是:
/mnt/c/Users/admin/test.sh这个映射关系看起来简单,但不能简单地用字符串替换完成。比如路径里的反斜杠要换成正斜杠,盘符大小写、隐藏的 UNC 路径、特殊字符转义,这些细节交给wslpath处理最稳妥。可以把它理解成一个“地址翻译官”:Windows 地址你说了不算,Linux 地址它说了也不算,翻译结果以它为准。
4.2wslpath的实战用法
我从 Windows 侧调用wslpath,路径转换的几个典型操作:
REM 把 Windows 路径转成 WSL 路径 wsl.exe -e wslpath -u "C:\Users\admin\test.sh" REM 输出:/mnt/c/Users/admin/test.sh REM 把 WSL 路径转成 Windows 路径 wsl.exe -e wslpath -w "/mnt/c/Users/admin/test.sh" REM 输出:C:\Users\admin\test.sh REM 把相对路径转成绝对 WSL 路径 wsl.exe -e wslpath -a "./test.sh"如果你的系统装了多个发行版,可以加-d指定:
wsl.exe -d Ubuntu-22.04 -e wslpath -u "C:\Users\admin\test.sh"4.3 路径里有空格、中文、$符号时怎么办
前面说过,路径一有空格,CMD 解析就会出问题。正确的做法是:整个命令行外层用双引号包住,让 CMD 把它当成一个完整参数;传入 bash 后,再用单引号保护路径里的空格。
拿一个带空格的路径举例:
C:\Users\admin\my script\test.sh转换后的 WSL 路径是:
/mnt/c/Users/admin/my script/test.sh在.bat里这样传给 WSL 最可靠:
wsl.exe -e bash -lc "bash '/mnt/c/Users/admin/my script/test.sh'"CMD 看到的最外层双引号把bash '/mnt/c/.../my script/test.sh'整个包住,所以不会按空格拆分;bash 看到的是单引号包起来的完整路径,也不会被空格干扰。这就是第 5 章完整脚本采用的传参模式。
中文路径在 Windows 和 WSL 之间转换,wslpath一般都能处理。真正要注意的是/mnt/c/挂载点下的中文文件名,在 bash 命令行里打出来可能显示乱码,但作为参数传给脚本执行通常没问题。$符号比较麻烦,比如路径里如果有$,bash 会把它当成变量引用,必须用单引号包住路径来阻止变量展开。这也是我坚持在 bash 侧用单引号而不是双引号的原因。
4.4 传给 bash 的命令串要怎么拼才不容易被转义坑到
这里总结一个简单到不用思考的规则:
- 外层永远用双引号包住整个 bash 命令行,这是给 CMD 看的;
- 内层涉及路径和变量时,用单引号包住,这是给 bash 看的;
- 不要试图在 CMD 层用反斜杠转义双引号,batch 对
\"的处理和 Bash 完全不同,很容易越转越乱。
比如这句:
wsl.exe -e bash -lc "bash '%WSL_PATH%'"%WSL_PATH%在 CMD 阶段展开成 WSL 路径字符串,比如/mnt/c/Users/admin/xxx.sh。整个参数在 CMD 层是一对双引号包着,在 bash 层是被单引号包着,两层解析各司其职,不会打架。
如果命令里还需要用&&连接多个 bash 命令,注意&&在 CMD 层有特殊含义,建议在.bat里写成^&^&,确保它原样传到 bash 而不是被 CMD 截断执行。
wsl.exe -e bash -lc "cd '/mnt/c/Users/admin' ^&^& bash './test.sh'"4.5 从\\wsl$\Ubuntu\...拖文件进 WSL 的特殊情况
有一种场景会让上面所有路径转换都失效:你拖的不是 Windows 本地文件,而是 WSL 内部共享出来的文件。Windows 资源管理器里访问 WSL 文件时,路径长这样:
\\wsl.localhost\Ubuntu\home\user\test.sh或者旧一点的写法:
\\wsl$\Ubuntu\home\user\test.sh这类 UNC 路径在资源管理器里存在,但在拖拽传给.bat时,wslpath -u的行为在不同 WSL 版本下不一致,有的版本能正确转成/home/user/test.sh,有的版本会报错或者原样输出。如果你日常主要拖的是 Windows 本地文件,主脚本不受影响;如果你要把 WSL 内部的文件拖到运行器上,最稳的办法是先把它复制到 Windows 本地(比如C:\Users\admin\scripts)再拖,或者直接从 WSL 的终端里执行。我自己的使用习惯是:拖拽运行器只处理 Windows 本地路径,WSL 内部脚本直接在终端里跑,分工明确。
5. 一份开箱可用的 Drag2WSL 拖拽运行器(含完整脚本与逐段拆解)
5.1 完整脚本
把所有坑避开之后的脚本长这样。我把文件名命名为Drag2WSL.bat,保存时建议用 ANSI 或 UTF-8 with BOM,最省事的是保持代码里的提示文本为英文,这样任何编码都不会乱码。
@echo off setlocal REM ============================================ REM Drag2WSL.bat REM Drag a .sh file onto this file to run it REM inside the default WSL distro. REM ============================================ REM If you have multiple distros, change this to yours, e.g. Ubuntu-22.04 set "DISTRO=" if "%~1"=="" ( echo Drag and drop a .sh file onto this batch file. echo It will run inside your default WSL distro. pause exit /b 1 ) set "FULL_PATH=%~1" set "DRIVE_DIR=%~dp1" REM Build wsl.exe command with optional distro flag set "WSL_CMD=wsl.exe" if defined DISTRO set "WSL_CMD=wsl.exe -d %DISTRO%" REM Convert Windows path to WSL path using wslpath for /f "delims=" %%P in ('%WSL_CMD% -e wslpath -u "%FULL_PATH%" 2^>nul') do set "WSL_PATH=%%P" if not defined WSL_PATH ( echo [ERROR] Cannot convert path to WSL path. echo Make sure WSL is installed and your default distro is running. pause exit /b 1 ) echo [INFO] Dropped file: %FULL_PATH% echo [INFO] WSL path: %WSL_PATH% REM Change current directory to the dropped file's directory. REM WSL will map this directory to the corresponding /mnt/... path, REM so relative paths inside the script still work. pushd "%DRIVE_DIR%" >nul 2>&1 if errorlevel 1 ( echo [WARN] Cannot enter %DRIVE_DIR%, working dir will be WSL default. ) else ( echo [INFO] Working dir changed to %CD% ) REM Run the script inside WSL. REM Use "bash file" instead of "./file" to avoid executable permission issues. REM The command string is wrapped in double quotes for CMD, REM and the path is wrapped in single quotes for bash. %WSL_CMD% -e bash -lc "bash '%WSL_PATH%'" set "EXIT_CODE=%ERRORLEVEL%" popd >nul 2>&1 echo ======================================== echo [DONE] Exit code: %EXIT_CODE% pause exit /b %EXIT_CODE%5.2 关键行拆解:为什么这么写
先把脚本里最容易困惑的几行单独拿出来说。
for /f "delims=" %%P in ('%WSL_CMD% -e wslpath -u "%FULL_PATH%" 2^>nul') do set "WSL_PATH=%%P"这一行是核心。for /f负责执行括号里的命令,并把输出赋给变量。delims=表示不要按空格或 Tab 做列拆分,整行输出直接给%%P。2^>nul表示把 stderr 重定向到空,不要干扰正常输出。路径里有空格也不怕,因为wslpath输出的是一行完整的 WSL 路径,delims=已经避免拆行。
pushd "%DRIVE_DIR%" >nul 2>&1DRIVE_DIR是%~dp1的结果,也就是拖入文件所在目录。pushd会记住当前目录,然后切换过去。这样wsl.exe启动时,WSL 内部的工作目录会自动对应到脚本所在目录的挂载路径。脚本内部的相对路径就能正常工作。执行完再popd切回来。
%WSL_CMD% -e bash -lc "bash '%WSL_PATH%'"这一行的关键是“用bash去执行bash”。第一个bash是外部启动的登录 shell,第二个bash表示用bash解释执行指定脚本文件。为什么不直接./?因为./依赖脚本文件有执行权限,而这个权限在 Windows 侧创建的.sh文件上经常没设置。用bash '%WSL_PATH%'就绕开了权限问题,只要文件内容是对的就能跑。
-l表示 login shell,会加载用户 profile,这样脚本运行时能拿到你平时在终端里的 PATH、环境变量、别名等配置。很多工具链(如 nvm、pyenv、conda)都是通过 profile 配置的,少了-l可能就找不到命令。
最后一个%ERRORLEVEL%用于获取脚本退出码。WSL interop 能把 Linux 进程的退出码传回 Windows,所以拖拽运行器能知道脚本到底是成功还是失败,而不仅仅是“跑完没跑完”。
5.3 常见错误对照表
运行时如果遇到问题,先对号入座看看:
| 现象 | 原因 | 解决办法 |
|---|---|---|
No such file or directory | 脚本路径没转换成功,或脚本文件不存在 | 确认FULL_PATH是不是完整路径;在 WSL 里ls -l看看文件是否真的在 |
Permission denied | 脚本没有执行权限 | 用bash script.sh执行而不是./script.sh,拖拽运行器已处理;或者chmod +x |
$'\r': command not found | 脚本换行符是 CRLF | 用sed -i 's/\r$//' 脚本路径或dos2unix修正 |
wslpath: command not found | 发行版缺少 wslpath 工具 | 确认 WSL/发行版已更新;极简发行版可尝试安装wslu后再用 |
| 中文显示乱码 | CMD 代码页与文件编码不一致 | 保持提示信息为英文,或把.bat保存为 ANSI/GBK |
| 脚本目录里的相对路径找不到 | 工作目录没有切到脚本目录 | 检查pushd是否成功;脚本内最好用cd "$(dirname "$0")"兜底 |
5.4 如何改成你本机的默认发行版和默认用户
如果你的 WSL 里装了多个发行版,脚本默认会用“默认发行版”。想指定发行版,改脚本顶部这段:
set "DISTRO=Ubuntu-22.04"再把后面所有wsl.exe相关命令都带上-d %DISTRO%,也就是脚本里的WSL_CMD变量会自动处理。
默认用户取决于你安装发行版时创建的用户。如果你平时在终端里登录的是普通用户,拖拽运行器也会以该用户身份执行;如果发现执行身份不对,可以在 WSL 里使用wsl.conf的[user] default=配置,或者干脆在脚本里用sudo -u之类的命令控制,但这些属于 WSL 本身的配置问题,这里不展开。
6. 进阶玩法:拖文件夹、批量脚本、右键菜单与任意 WSL 程序
6.1 不只 sh:同一个 bat 怎么变成“任意 WSL 命令运行器”
拖拽运行器本质上做了一件事:把 Windows 拖入文件的路径转换成 WSL 路径,然后交给 WSL 执行。所以只要改掉最后一行命令,它就能变成“任意 WSL 程序的文件拖入入口”。
比如:
| 拖入的文件 | WSL 内命令 | 实际效果 |
|---|---|---|
test.py | python '%WSL_PATH%' | 用 WSL 里的 Python 直接跑脚本 |
docker-compose.yml | docker compose -f '%WSL_PATH%' config | 检查 Compose 配置 |
video.mp4 | ffmpeg -i '%WSL_PATH%' /mnt/d/output.mp4 | 转码视频 |
archive.tar.gz | tar -tf '%WSL_PATH%' | 查看压缩包内容 |
改法就是在第 5 章的完整脚本里,把最后执行行替换成你要的命令。我之前用它拖docker-compose.yml验证配置、拖video.mp4快速截帧、拖.sql文件给 MySQL 容器导入数据,本质都是这一套路径转换逻辑。
6.2 让脚本支持拖入文件夹:自动列出并选择要跑的 .sh
默认情况下,拖入一个文件夹时%~1是文件夹路径,%~x1不是.sh,脚本会走普通执行路径然后报错。想让它变成一个“文件夹脚本选择器”,可以在脚本开头加一段判断。
思路是:如果拖入的是文件夹,就用for循环列出该目录下所有.sh文件,让用户输入编号选择要执行哪个。因为是纯 batch 实现,交互不复杂但够用:
@echo off setlocal enabledelayedexpansion if "%~1"=="" goto usage if not exist "%~1\" goto run_file echo [INFO] Folder detected: %~1 set "IDX=0" for %%F in ("%~1\*.sh") do ( set /a IDX+=1 echo [!IDX!] %%~nxF set "ITEM!IDX!=%%~fF" ) if not defined ITEM1 ( echo No .sh files found in this folder. pause exit /b 1 ) set /p "CHOICE=Enter number to run (0 to cancel): " if "%CHOICE%"=="0" exit /b 0 set "SELECTED=!ITEM%CHOICE%!" echo Running: !SELECTED! call "%~f0" "!SELECTED!" exit /b %ERRORLEVEL% :run_file REM 这里放第 5 章完整脚本的主体逻辑 REM FULL_PATH=%~1, 然后转换、pushd、执行这个版本的交互是“数字选择”,如果你想要更复杂的子目录递归扫描,可以在for /r "%~1" %%F in (*.sh)里加参数。但对大多数场景,一层的.sh列表已经足够。
6.3 把拖拽运行器挂到右键菜单和“发送到”
拖拽是个好操作,但对某些人来说,右键更顺手。有两个低成本方案:
第一个是“发送到”菜单,很安全:按Win+R输入shell:sendto,打开“发送到”文件夹,把Drag2WSL.bat的快捷方式放进去,重命名为“在WSL中运行”。之后在任意.sh文件上右键 → “发送到” → “在WSL中运行”即可。
第二个是给.sh文件注册右键菜单项。这需要改注册表,有风险,操作前建议先导出备份或者用系统还原点。下面是一个 per-user 的注册方式,不需要管理员权限:
reg add "HKCU\Software\Classes\sh_auto_file\shell\run_with_wsl" /ve /d "在WSL中运行" /f reg add "HKCU\Software\Classes\sh_auto_file\shell\run_with_wsl\command" /ve /d "\"D:\tools\Drag2WSL.bat\" \"%1\"" /f reg add "HKCU\Software\Classes\.sh" /ve /d "sh_auto_file" /f注意把D:\tools\Drag2WSL.bat换成你本地实际路径。注册完之后,.sh文件的右键菜单里会多出一项“在WSL中运行”。Windows 11 里传统的完整右键菜单默认收在“显示更多选项”里,这个属于系统行为,不影响注册结果。
6.4 给脚本传附加参数、把输出重定向到日志文件
拖拽文件本身只提供了一个参数——文件路径。如果脚本需要额外的参数,可以在 bat 执行前用set /p让用户输入,然后拼到命令末尾:
set /p "EXTRA_ARGS=Enter extra args: " %WSL_CMD% -e bash -lc "bash '%WSL_PATH%' %EXTRA_ARGS%"这种情况下,EXTRA_ARGS里的内容会被 bash 按空格拆分,适合传普通参数。注意不要让用户输入引号或$()这类特殊字符,避免被 bash 解析成命令。
输出重定向也很简单。如果想保留执行日志,把执行行改成:
%WSL_CMD% -e bash -lc "bash '%WSL_PATH%'" > "%~dpn1.log" 2>&1这样脚本的标准输出和错误输出都会写入被拖入脚本同目录的.log文件。我习惯给定时任务脚本加这个,跑完不看窗口,直接看日志。
6.5 多文件拖拽:Windows 的真实行为与并发注意事项
最后提醒一个很容易忽略的细节:把多个.sh文件同时拖到.bat上时,Windows 不会在一个进程里给你一串路径参数,而是为每个文件启动一个独立的.bat进程,每个进程各拿到一个路径。
这个行为意味着:
- 多个脚本会并发执行,而不是按顺序执行;
- 每个脚本有独立的 CMD 窗口和退出状态;
- 如果你的脚本之间有依赖关系,拖拽运行无法保证执行顺序。
如果你的使用场景是“拖多个文件,让脚本串行处理”,需要对批处理做改造,比如用for %%F in (%*)循环处理,但拖拽触发的方式决定了这很难做到。我的建议是:拖拽运行器服务“单脚本执行”这个明确目标,多脚本串行还是交给 for 循环或者写一个入口脚本去编排。
7. 一点使用体会和拓展思路
这套 Drag2WSL 我用了一段时间,最实际的感受是:以前在 Windows 桌面想跑一个 Linux 小工具,至少要开终端、切目录、敲命令三步,现在变成“拖上去、看一眼输出、关掉窗口”,尤其是每天早上要执行的同步脚本、固定格式的日志清理,拖拽比翻历史命令快得多。
但我也要坦白说一句:拖拽运行器解决的是“重复执行固定脚本”的启动效率,它不适合所有场景。脚本一旦需要交互、需要长驻输出、需要传复杂参数,开一个正经终端会舒服很多。工具只是工具,别为了统一方案去硬套。
最后分享一个让我用起来最顺手的小技巧:把经常要跑的脚本统一放到一个文件夹,比如C:\Users\admin\scripts,然后在桌面给每个脚本建一个指向Drag2WSL.bat的快捷方式,把快捷方式名字改成脚本名。这样在桌面上就有了一排“按钮”,双击就是在 WSL 里跑对应脚本,体验接近双击原生程序。脚本更新了没关系,快捷方式指向的是 bat 入口,拖拽时用的是实际文件路径,整个工作流非常灵活。如果你也有 Windows 和 WSL 混着用的日常操作,这个方案值得直接复制下来试两天。