Windows下运行.sh脚本:从Git Bash到虚拟环境配置全攻略

📅 发布时间:2026/9/17 12:23:20
Windows下运行.sh脚本:从Git Bash到虚拟环境配置全攻略
之前在Windows上做自动化最烦的一件事就是拿到一个.sh脚本不知道怎么跑。扔到Linux上没问题但本机是Windows环境又不想为了一个脚本去装虚拟机、开WSL成本太高了。后来我把Git Bash、cmd终端、PyCharm终端这几条路都走了一遍整理出一套统一的跑法无论在哪个终端里.sh文件都能执行而且脚本内部可以自己指定要用的虚拟环境。这篇文章就把整个思路、完整操作和踩坑记录写出来给需要在Windows下和shell脚本打交道的朋友做个参考不管你是做后端、自动化测试、数据处理还是只想跑通开源项目里的某个脚本应该都能用上。1. 先把问题摆清楚Windows和.sh脚本的天然隔阂1.1 为什么Windows不能像Linux那样直接执行.sh.sh是shell脚本语法围绕POSIX环境设计它依赖的是一整套Unix下的命令和工具ls、grep、sed、awk都是基础操作更别说权限模型、文件路径、换行符这些底层约定。Windows原生环境里既没有标准的bash解释器也没有这些命令工具所以双击一个.sh文件系统只会问你要用什么程序打开而不会把它当成可执行程序来跑。很多人刚接触时容易误判觉得我装了Python里面不是有pip、有终端吗为什么不能跑sh这是两码事。.sh需要的是一个shell解释器去逐行解释执行而不是把文件读进来当普通程序。Windows的cmd和PowerShell自己有一套语法体系跟POSIX shell完全不兼容默认情况下它们也找不到bash这个命令。所以想在Windows下跑.sh第一件事就是给系统补一个类Unix环境。技术路线其实不少我最常推荐的还是装Git for Windows因为Git是开发机几乎必装的东西装完就自带Git Bash不需要额外折腾。1.2 三条路线怎么选Git Bash、WSL、Cygwin方案优点缺点适合场景Git Bash跟随Git安装轻量路径转换自动PyCharm、cmd都能直接调用不是完整Linux环境个别系统工具缺失日常跑脚本、做自动化、本地开发WSL最接近真实Linux能跑绝大多数Linux程序安装体积大首次配置耗时路径在Windows和Linux之间来回切换较绕需要Linux原生依赖的复杂项目Cygwin老牌POSIX模拟环境工具链全环境配置繁琐依赖包管理复杂对新手不友好少数必须在纯Windows图形环境下用Unix工具的场合我的建议很清楚如果只是跑.sh脚本、做自动化、配合Python虚拟环境老老实实用Git Bash就够了。脚本里只要不涉及特别冷门的Linux系统调用Git Bash基本都能处理。WSL是重武器等哪天你的脚本需要systemd、需要装Linux内核模块再说为跑一个bash脚本去开WSL完全是杀鸡用牛刀。2. 环境准备把Git Bash变成一台迷你Unix2.1 安装Git for Windows时的关键选项安装Git for Windows没什么难度官网下载安装包一路Next基本能用。但有两个选项会影响后面所有操作需要留个心眼。第一个是Adjusting your PATH environment这一步。新手经常忽略结果装完发现在cmd里敲bash完全不认识。这里建议选择第二项或第三项Git从命令行启动并可供第三方软件使用它的作用是把Git的cmd目录加入系统PATH后面我们才能在cmd和PyCharm终端里直接调bash命令。如果之前装的时候没注意装完也可以手动把C:\Program Files\Git\cmd和C:\Program Files\Git\bin加到环境变量Path里。第二个是行结束符转换设置。安装过程中会让你选Checkout Windows-style, commit Unix-style line endings还是Checkout as-is, commit Unix-style line endings。这个选项直接影响后面脚本会不会出现\r报错我在下一节单独说。装完以后桌面或开始菜单里就能看到Git Bash入口。点开它看到的是一个跟Linux终端长得差不多的窗口里面可以正常使用ls、pwd、cd这些命令。到这里你的Windows已经具备执行.sh的基础能力了。2.2 确认bash命令在cmd里能被调用这一步很关键先验证一下环境是否真的通到了系统全局。打开cmd直接敲bash --version如果有输出版本信息说明你的PATH配置成功了。如果提示bash不是内部或外部命令也没关系有两个解决办法一是手动检查环境变量里有没有C:\Program Files\Git\bin没有就加上然后重新打开cmd。二是不改环境变量改用完整路径调用C:\Program Files\Git\bin\bash.exe --version这个方法更省事后续在cmd和PyCharm里都可以用完整路径调用我建议新手先这样兜底。等确认没问题了再去纠结PATH优化也不迟。顺便说一句Git Bash里那个bash和Git安装目录下的bash.exe其实是一回事。Git Bash快捷方式在启动时会自动带上--login参数加载用户的.bashrc配置而我们在cmd里直接调bash.exe默认不加载.bashrc。这个细节后面写.sh脚本时要注意脚本不要依赖.bashrc里的环境变量。2.3 从源头避开换行符坑core.autocrlf和.gitattributes换行符问题是Windows下跑.sh最隐蔽的雷。Linux和macOS用LF\nWindows用CRLF\r\n。如果.sh文件带着CRLF进到bash里每一行命令后面都跟着一个肉眼看不见的\r终端一执行就会报$\r: command not found非常影响排查问题的节奏。我一开始遇到过这个坑从仓库clone下来的脚本在Linux上跑得好好的一到Windows Git Bash就挂后来用cat -A script.sh一看行尾全是^M$气得直拍桌子。解决方案有三个层面。第一个是全局设置Git不自动转换换行符在Git Bash里执行git config --global core.autocrlf false这样checkout代码时就不会把LF悄悄转成CRLF了。第二个更精细的做法是在仓库里放一个.gitattributes文件专门指定.sh文件使用LF*.sh text eollf这样不管团队成员用什么系统提交代码仓库里的.sh文件始终是LF。第三个是处理手头已经出现问题的文件用编辑器重新保存为LF或者在Git Bash里跑一下sed -i s/\r$// script.sh这三个配合起来能省掉后面一大半莫名其妙的坑。3. 在git bash中运行.sh文件最顺手但也要小心3个细节3.1 直接运行还是用bash命令显式执行在Git Bash中进入脚本所在目录就可以执行了。假设项目在D:\work\myprojectGit Bash里盘符的表现形式是/d/work/myproject所以cd /d/work/myproject ./script.sh注意./不能省。如果脚本没有可执行权限会报Permission denied这时候可以顺手加一个chmod x script.sh不过Git Bash的权限位是模拟的它的意义更多是让bash决定能不能直接执行。还有一种更省事的调用方式我一直很推荐bash script.sh这种调用方式不依赖文件的可执行权限也不需要脚本自带正确的shebang只要文件内容没有语法问题就一定能跑。尤其是从网上下载的脚本经常没有执行权限用bash script.sh就是最稳的起手式。sh script.sh也可以跑但在Git Bash里sh最终会调bash而且有些bash特性在sh模式下会被限制所以统一写bash更靠谱别给自己挖坑。3.2 编码、换行和shebang这三个隐性地雷Git Bash本身支持UTF-8所以脚本保存成UTF-8是最省心的。但要小心两个变种第一个是BOMByte Order Mark。UTF-8带BOM的文件开头会有三个不可见字节bash读第一行#!/bin/bash时会一起读进去得到的解释器路径变成了/bin/bashBOM直接报bad interpreter。Windows自带记事本保存UTF-8容易带BOM所以我写脚本一般用VSCode或Notepad并在编辑器右下角把编码切成UTF-8不带BOM行结束符切成LF。第二个就是上一节说的CRLF。编码正确、换行符正确脚本就成功了一大半。如果还是报错用cat -A script.sh看一眼行尾^M$就是CRLF$是LF。看到^M就赶紧转。shebang这行也值得说清楚。Git Bash里/bin/bash这个路径是存在的所以写#!/bin/bash没问题。但如果同一个脚本将来还要在Linux上跑可以写#!/usr/bin/env bash这样系统会从PATH里找bash兼容性更好。不过需要注意的是shebang只有在用./script.sh这种直接执行方式时才起作用用bash script.sh时bash已经指定了shebang行会被忽略。3.3 脚本里的路径写法容不得Windows习惯在Git Bash里Windows路径写法会带来不少麻烦。最典型的就是反斜杠。反斜杠在bash里是转义字符写cd C:\Users\me\projectbash会把\U、\m这些当成转义序列解析结果就是路径完全乱了套。正确做法是用Git Bash的统一路径表示比如/c/Users/me/project或者至少用正斜杠的Windows风格C:/Users/me/project。在写.sh脚本时如果脚本里要调用外部程序路径写法也要统一。比如python D:/work/scripts/tool.py这种写法在Git Bash里通常没问题Git Bash会自动把参数转换成Windows程序能识别的格式。还有一个进阶坑Git Bash会对命令行参数做自动路径转换。当你给某个Windows程序传以斜杠开头的参数时它可能会被误判成路径。比如curl -I https://example.com这个-I开头带着横杠一般没事但遇到类似/user:admin这种参数时Git Bash可能会把/user:admin转成C:/Program Files/Git/user:admin。遇到这种情况可以在调用命令前加上MSYS_NO_PATHCONV1环境变量来禁用转换MSYS_NO_PATHCONV1 some_win_program.exe /user:admin这种场景虽然不常见但遇到了能救你一命我先放这里备着。4. 在cmd和PyCharm终端中运行.sh文件4.1 cmd里调用bash一条命令搞定既然我们的目标是可以在cmd里运行.sh那核心思路就一句话在cmd里借用bash.exe来执行脚本。先按第2节的方法确认bash命令能被cmd识别。然后直接用bash D:\work\myproject\script.sh这里有个好玩的点cmd里写的D:\work\myproject\script.sh其实是Windows路径但bash.exe在解析参数时能把它自动转换成Git Bash认可的路径格式所以不需要你手动改成/d/work/...。实测下来这个自动转换非常稳。如果bash不在PATH里就用完整路径C:\Program Files\Git\bin\bash.exe D:\work\myproject\script.sh个人经验在cmd里跑脚本时先cd /d D:\work\myproject切到项目根目录再执行这样脚本内的相对路径不容易出问题。如果脚本内已经写了自动切换到脚本目录的逻辑那就无所谓了从哪启动都能跑。4.2 把PyCharm自带的Terminal从cmd/PowerShell换成Git BashPyCharm默认的Terminal窗口是调用系统shell的Windows下通常是PowerShell或cmd。你可以在PowerShell里敲bash script.sh一样能跑但总感觉有点别扭而且PowerShell的路径转换、别名机制偶尔会跟bash语法打架。更好的办法是把PyCharm的Terminal直接换成Git Bash这样IDE底部那个终端窗口就是一个完整的bash环境。操作很简单打开PyCharm进入Settings - Tools - Terminal在Shell path一栏填入C:\Program Files\Git\bin\bash.exe点击OK然后关掉当前Terminal重新开一个看到的终端提示符就变成bash风格了ls、pwd都能直接使跟Git Bash窗口里操作完全一样。这个细节对Windows开发体验提升很大。在PyCharm里编辑脚本直接在底部Terminal跑./script.sh改完就跑不用切换窗口效率高很多。4.3 用PyCharm的Run配置直接跑.sh分专业版和社区版如果你用的是PyCharm Professional它支持直接添加Shell Script运行配置。操作路径是右键.sh文件 -Run或者打开Run/Debug Configurations添加一个Shell Script类型配置把Script path指到.sh文件Interpreter选bash设置好Working directory就行。这样一来你可以像启动Python脚本一样在IDE里点绿色按钮直接跑.sh输出日志都在Run窗口里。如果是PyCharm Community版默认没有这个Shell Script配置类型我的建议是别折腾插件了直接把Terminal改成Git Bash上一节的方法在Terminal里跑。社区版的核心能力本来就在Python开发为跑个shell脚本去装几层插件不值当。无论哪个版本都建议在工作目录上多注意Run配置里的Working directory和脚本的实际目录最好一致或者在脚本内部统一做cd $(dirname $0)处理。这也是我在第5章模板里一定会写的那句它在cmd、PyCharm、Git Bash下都有效属于跨环境脚本的保命咒语。5. .sh文件中怎么指定虚拟环境5.1 Windows虚拟环境的目录差异Scripts还是binPython虚拟环境在Windows和Linux下的目录结构有一个关键差异Linux是bin/Windows是Scripts/。具体来说Linux/macOS的venv里激活脚本在.venv/bin/activateWindows的venv里激活脚本在.venv/Scripts/activate。很多人直接把Linux平台上写的脚本拿到Windows的Git Bash里跑卡在source .venv/bin/activate这一句报No such file or directory。原因就是这个目录结构差异不是虚拟环境创建得有问题。Git Bash虽然是一个bash环境但它里的venv仍然是由Windows版Python创建的所以目录结构跟Windows一致。正确写法是source .venv/Scripts/activate注意.venv/Scripts/下除了activate.bat和Activate.ps1还有一个不带扩展名的activate文件这个文件就是一个POSIX shell脚本Git Bash里能用source执行它cmd和PowerShell反而用不了。这个细节懂了虚拟环境激活就算通了。5.2 在.sh里自动创建并激活venv手动激活虚拟环境谁都会但脚本的自洽性更重要。一个合格的自动化脚本应该能判断虚拟环境是否存在不存在就创建存在就激活然后执行后续任务。下面这段是我反复用的基础逻辑#!/usr/bin/env bash set -e cd $(dirname $0) VENV_DIR.venv PYTHON_BIN${PYTHON_BIN:-python} if [ ! -d $VENV_DIR ]; then echo 创建虚拟环境$VENV_DIR $PYTHON_BIN -m venv $VENV_DIR fi if [ -f $VENV_DIR/Scripts/activate ]; then source $VENV_DIR/Scripts/activate elif [ -f $VENV_DIR/bin/activate ]; then source $VENV_DIR/bin/activate fi echo 当前Python$(python --version) python --version python -m pip install --upgrade pip python -m pip install -r requirements.txt echo 开始执行主任务 python main.py这段脚本有几个关键点第一set -e。它让脚本在任意一条命令失败时立即退出避免虚拟环境没激活成功下面还在用系统Python硬跑最后产生一堆看不懂的错。第二cd $(dirname $0)。不管脚本是从哪个目录被调用的都会先切到脚本所在的目录这样后面所有相对路径都稳了。这个写法在Git Bash、cmd、PyCharm里都生效。第三对Scripts和bin都做了判断左边适配Windows右边适配Linux/macOS。这样同一个脚本在团队内跨平台使用也不会挂。关于PYTHON_BIN${PYTHON_BIN:-python}这行它允许你在调用时临时覆盖Python解释器路径比如PYTHON_BINpy -3.11 bash setup.shWindows下如果系统里装了多个Python版本用py -3.11来指定版本还是很常见的。不过py本身是个launcher传进-m venv时也正常工作实测没问题。5.3 在.sh里激活conda环境如果你用的是Anaconda或Miniconda在.sh脚本里激活环境要稍微绕一下。因为非交互式bash脚本默认不会加载.bashrcconda的初始化代码不会自动生效直接写conda activate myenv大概率报conda: command not found。解决办法是在脚本开头手动加载conda的shell函数。思路是找到conda的安装目录然后source它自带的profile脚本# 假设conda命令本身已经在PATH里 CONDA_BASE$(conda info --base) # 在Windows Git Bash里conda info --base返回的是Windows路径 # 需要用cygpath转换成bash路径或者直接用反斜杠路径source source $(cygpath -u $CONDA_BASE)/etc/profile.d/conda.sh conda activate myenv如果cygpath不存在Git Bash里一般都有也可以手动写source /c/Users/你的用户名/miniconda3/etc/profile.d/conda.sh conda activate myenv这种显式source的方式比依赖.bashrc要可靠得多也不受是不是交互式shell影响。还有一种更省事的思路不在脚本里激活conda环境直接调用conda环境里的Python绝对路径。比如CONDA_PYTHON/c/Users/你的用户名/miniconda3/envs/myenv/python.exe $CONDA_PYTHON -m pip install -r requirements.txt $CONDA_PYTHON main.py不激活环境直接指定解释器实际上更稳。唯一的代价是写起来长一点但避免了conda shell初始化的所有坑。我经常在自动化流水线里用这个方式省心。5.4 一个完整同时支持venv和conda的通用模板实际项目中团队里可能有人用venv、有人用conda我只想写一个脚本让所有人都能用。下面这个模板我用了很久整体思路是优先venv没有再找conda最后回调系统Python#!/usr/bin/env bash set -e cd $(dirname $0) VENV_DIR.venv PYTHON_BIN${PYTHON_BIN:-python} # 1. 尝试创建并激活 venv if [ ! -d $VENV_DIR ]; then echo 创建venv $PYTHON_BIN -m venv $VENV_DIR fi if [ -f $VENV_DIR/Scripts/activate ]; then source $VENV_DIR/Scripts/activate elif [ -f $VENV_DIR/bin/activate ]; then source $VENV_DIR/bin/activate fi # 2. 如果venv里没有pip用conda兜底 if ! command -v python /dev/null 21; then if command -v conda /dev/null 21; then CONDA_BASE$(conda info --base) source $(cygpath -u $CONDA_BASE)/etc/profile.d/conda.sh conda activate project_env else echo 找不到任何可用的Python环境 2 exit 1 fi fi # 3. 统一从这里开始 echo Python: $(python --version) python -m pip install --upgrade pip python -m pip install -r requirements.txt python main.py这个模板的核心思想是环境激活细节全部封装在脚本内部使用者只需要执行./run.sh不管他是什么终端、什么系统都尽量往同一个结果走。这比让每个人自己手动激活环境要可靠得多。提取一个建议虚拟环境目录.venv一定用相对路径不要写死成C:/.../.venv。因为不同用户clone项目的位置可能不一样相对路径配合cd $(dirname $0)才能保证团队里所有人都能跑。5.5 和PyCharm解释器配置的关系这里有一个很多初学者混淆的点PyCharm右下角选择的Python解释器和.sh脚本里激活的虚拟环境是两个独立的东西。PyCharm解释器的作用是给IDE的代码补全、调试器和Run窗口用的。当你直接右键运行Python文件时PyCharm用的是自己配置的解释器它可能会自动激活某个venv也可能直接用系统Python。但.sh脚本执行时脚本内部的source .venv/Scripts/activate决定用哪个环境PyCharm那套配置根本管不到它。所以在团队协作中最好约定一切以脚本内的环境激活为准。PyCharm里配置的解释器只是为了开发体验真正跑自动化脚本时直接执行.sh由脚本统一托管环境。这样就不会出现我本地明明激活了xxx环境怎么脚本跑起来用的还是另一个Python的情况。如果一定要让两者一致也简单在PyCharm的Settings - Project - Python Interpreter里把解释器指向项目里的.venv/Scripts/python.exe这样IDE和脚本用的是同一个环境排查问题更省力。6. 常见问题与排查技巧实录先把我遇到过的、以及周围同事问过的高频问题整理成一张表然后挑几个典型场景详细说排查过程。现象常见原因解决办法$\r: command not found脚本行尾是CRLFsed -i s/\r$// script.sh或在编辑器里改为LFbad interpreter /bin/bash^M脚本带BOM编码另存为UTF-8无BOMPermission denied脚本没有可执行权限chmod x script.sh或直接bash script.shsource: No such file or directoryvenv激活路径写成了Linux的binWindows下用.venv/Scripts/activateconda: command not found非交互式bash没有初始化condasource $(cygpath -u $(conda info --base))/etc/profile.d/conda.sh在cmd里敲bash提示不是内部或外部命令Git的bin目录没加到系统PATH手动添加C:\Program Files\Git\bin或用完整路径调用中文输出乱码脚本UTF-8中文cmd默认GBK在cmd里先执行chcp 65001或直接用Git Bash终端set -e后脚本中途退出没看到业务日志某条命令非零退出在关键命令后加第一条CRLF的坑我前面已经重点讲过了这里再说一个排查技巧脚本报错时不要只看最后几行用cat -A script.sh看整个文件的行尾符号一秒钟就能确定是不是换行符惹的祸。第二条关于conda环境变量的问题我补充一个细节。在Git Bash里直接敲conda activate经常看到CommandNotFoundError这不一定是你conda没装好而是conda只在你登录shell时初始化了bash环境脚本在非交互模式下根本没加载这些初始化代码。所以脚本里source conda.sh那一步必不可少。还有一个容易被忽略的问题python命令指到了哪里。Windows上如果装了Microsoft Store的Python再点安装可能会把python映射到一个商店页面程序导致在Git Bash里执行python -m venv .venv毫无反应或直接打开商店。碰到这种情况建议在脚本里显式指定解释器路径或者统一用py -3.11这种形式。最后一个经验是运行过程中想调试脚本时怎么办。我会习惯在脚本开头临时加一行set -x它会把每条命令展开打印出来等于把脚本的执行过程从头到尾可视化这对于查看虚拟环境到底激活成没激活、路径到底传成了什么特别有效。排查完再删掉就行。我个人在实际操作中的体会是Windows下跑.sh最大的障碍不是语法而是环境差异。你只要把换行符、路径、虚拟环境目录结构这三件事理顺后面就顺了。这也正是我为什么在脚本模板里总是不厌其烦地写兼容判断和cd $(dirname $0)——因为跨环境跑脚本开发者真正需要的不是我这里能跑而是大家都能跑。