
简介围绕批处理命令与FTP自动传输整理的Word文档面向Windows及UNIX环境下的系统运维人员和网络管理员用于解决每天重复登录、手工收发文件既繁琐又容易出错的问题。文档以1.ftp与1.bat两个文本的编写为主线说明如何用 ftp -n -s 参数静默调用脚本如何取舍二进制与ASCII两种传输模式如何用Mget与Mput完成收发切换并把批处理加入“任务计划”按周期自动执行同时给出UNIX下shell脚本的写法与可执行权限设置便于跨平台对照。包内共1个doc文件约44KB正文含脚本片段、步骤说明与应用案例可直接当模板改造使用。已有2121人学习适合需要搭建定时传输任务、减少人工干预的初、中级运维读者参考。1. 两个文本文件替代一个定时任务这套 FTP 自动传输方案的定位半夜两点半一台常开的 Windows 机器要从对端拉走当天的数据文件第二天早上八点前必须到位。这个需求用「批处理命令实现 FTP 文件的自动传输」是最省事的解法一个.ftp文件写会话命令一个.bat文件切目录并把脚本传给ftp.exe最后交给系统自带的任务计划按周期触发。全程不装任何软件运行时就是C:\Windows\System32\ftp.exe。适合的场景很明确Windows 常开、对端是 UNIX 或另一台 Windows、文件量几十到几百个、总量几百 MB 以内、对端只提供标准 FTP。不适合的同样明确要断点续传、要加密链路、要做复杂重试和告警。下面按「脚本怎么写 → 批处理怎么包装 → 计划任务怎么挂 → 哪里会翻车」拆开说。2. 拆 1.ftp从 open 到 bye 的九行命令每行都在决定成败2.1 脚本文件的本质是把键盘输入重定向成文件ftp.exe本身是个交互式程序你在提示符下敲open、user、cd它读一行执行一行。-s:文件名做的事只有一件把标准输入从这个文件读而不是从键盘读。所以脚本里能写什么完全取决于客户端在交互模式下认什么命令没有额外语法没有变量替换没有 if 判断也没有循环。这一点想明白后面很多「为什么我写了变量它不认」的疑问就自然消失了。把原料里的脚本摊开看实际长这样open 10.30.15.3 user anonymous 123456 cd data bin prompt mget *.* bye quit exitopen建立控制连接默认端口 21如果对端改了端口就写成open 10.30.15.3 2121。user后面跟用户名和密码这一行实际对应 FTP 协议里的 USER 和 PASS 两条命令密码不写在user同一行也可以下一行单独写客户端会把它当 PASS 发出去。但要注意如果user后面只写了用户名、下一行又忘了写密码客户端会当场停在Password:提示上等人输入无人值守场景下就是一直挂着直到超时。bye、quit、exit三个是同一个意思都是发 QUIT 并断开。写一个就够多写不报错但日志里会多出一段无意义的响应。原料里三行都写了这属于「写了也没坏处但没必要」。真正需要留意的是cd data这一行cd切的是远端目录本地目录要靠lcd或者批处理里的cd来处理这两个概念在初学时最容易混。2.2 bin 与 ascii模式选错文件看着传完了其实是坏的bin是binary的缩写对应 FTP 协议的TYPE I不写这一行Windows 客户端默认走TYPE A也就是 ASCII 模式。两者的差别不在速度在于是否对字节做转换。ASCII 模式会按行处理把本地的行尾转成网络标准 CRLF对端再转成自己的行尾二进制模式逐字节搬运一个字节都不动。原料里有一句「如果是 Windows 之间传去掉 bin 一行就可以了」这话得辩证看。两台 Windows 之间传纯文本ASCII 模式确实能正常工作因为两边的行尾都是 CRLF来回转换等于没转。但一旦传的是压缩包、可执行文件、数据库备份去掉bin就是灾难——ASCII 模式会把文件里恰好出现的0x0D、0x0A字节当成行尾改写传完大小对不上解压报 CRC 错误备份文件恢复不了。更麻烦的是这种损坏不会当场报错日志里照样是226 Transfer complete等你去恢复数据的时候才发现。按文件类型选模式的对照关系我一般按这张表来定文件类型传输模式选错的后果.txt/.csv/.log/.sqlascii用 bin 传UNIX 端每行末尾多个^M解析脚本读出错位.zip/.rar/.7z/.gzbin用 ascii 传压缩包损坏解压直接报错.exe/.dll/.sobin用 ascii 传可执行文件加载失败.dmp/.bak数据库备份bin用 ascii 传备份集无法恢复且不易察觉.jpg/.png/.pdfbin用 ascii 传图片花屏、PDF 打不开实操上的做法是按方向拆脚本而不是在一个脚本里来回切模式。收文本一个.ftp收二进制另一个.ftp各自在自己那个脚本开头写死模式。混在一个脚本里也不是不行ascii和bin可以在会话中反复切换但脚本一长就容易出现「切过去忘了切回来」的问题排查起来很费劲。2.3 prompt 与 mget/mput通配符的两次展开和确认开关prompt在 Windows 客户端里是个切换开关敲一次关掉交互确认再敲一次打开。关掉之后mget一次匹配到十个文件也不会逐个问mget xxx?。不写这一行的后果在无人值守场景下很直观任务计划状态一直显示「正在运行」日志停在一长串mget a.txt?上等到超时才退出一个文件都没传下来。原料里把prompt放在mget前面顺序是对的这个顺序不能反。mget *.*里的通配符不是客户端在本地展开的而是原样发给服务器由服务器的NLST或LIST来匹配。这带来两个边界。第一*.*在多数 UNIX 服务端只能匹配「文件名里带点」的文件像README、Makefile这种不带点的文件匹配不到要改成mget *。第二mget不递归远端data目录下的子目录里的文件不会被带下来真要按层级收常见做法是一个子目录写一个cdmget段落或者干脆在对端先打包成一个文件再传。发送方向同理把mget换成mputcd换成远端的上传目标目录本地目录靠lcd指定。一个改好之后可直接抄的版本open 10.30.15.3 user anonymous 123456 binary prompt lcd C:\datatran cd data mget *.txt mget *.csv byelcd这一行很多示例里不写靠批处理里的cd来兜底。两种做法都行但显式写lcd更稳因为批处理的当前目录会被任务计划的「起始于」字段影响而lcd是写死在脚本里的绝对路径不受外部干扰。密码和用户名并到user一行也建议这么改少一行就少一个错位的机会。3. 1.bat 的封装层ftp -n -s 几个参数各自在挡什么3.1 -n 不是「不登录」是「不要自动匿名登录」批处理这一层看着只有三行但ftp后面那几个参数每一个都在解决一个具体问题。先看原料给的写法C: cd datatran ftp -n -s:c:\1.ftp-n是最容易被误解的一个。它的准确含义是「启动时抑制自动登录」也就是不主动发USER anonymous去尝试匿名登录。如果不加-n客户端启动后会先做一次匿名登录尝试再执行脚本里的open和user结果是日志里凭空多出一段 331/230 响应某些对失败次数敏感的服务端还会因此触发限制。脚本里既然已经写死了用户名密码-n就应该加上。-s:后面紧跟脚本路径冒号和文件名之间不能有空格这一点和多数命令行工具的-s 文件名写法不一样。路径里带空格的时候用引号包住整个参数写成-s:C:\my scripts\1.ftp。原料用的是c:\1.ftp脚本放在盘符根目录这个习惯在单人环境里没问题但放到多台机器统一部署时最好归到一个固定目录比如C:\scripts\否则重装系统或者换盘符就得挨个改。其余几个参数按需加我整理成一张表备查参数作用什么时候必须加-n禁止启动时自动匿名登录脚本里已写死账号密码时-s:file指定命令脚本文件无人值守场景总是-v抑制远端响应输出日志要干净、只留关键行时-i多文件传输时关闭交互确认脚本里漏写prompt时的兜底-d打开调试输出每条 FTP 命令排查「为什么这条没执行」时临时加-A强制匿名登录对端只开放匿名访问时-w:size设置接收缓冲区大小大文件吞吐调优一般不用动-i和脚本里的prompt是同一个效果的两条路径建议两个都写脚本里写prompt是为了脚本本身独立可读命令行加-i是为了别人临时改脚本时忘写也不至于卡死。这种「双保险」在无人值守场景里不算冗余算必要成本。3.2 C: 和 cd批处理的当前目录才是文件落地的地方C:切盘符cd datatran切目录这两行的作用是给后面启动的ftp.exe定一个当前工作目录。mget下载下来的文件默认落在当前目录也就是这两行切过去的C:\datatran。如果这两行漏了ftp.exe继承的是父进程的当前目录手工双击 bat 的时候父进程是资源管理器目录可能恰好是你想要的一旦挂进任务计划父进程是taskeng.exe或svchost.exe当前目录变成C:\Windows\System32文件全落到系统目录里还因为权限问题写不进去。更稳的写法是一行到位cd /d C:\datatran/d参数让cd同时处理盘符切换和目录切换不用先写C:再写cd。原料里分两行的写法在C:盘内没问题但如果哪天目录换到D:盘只写cd D:\datatran是不生效的——它只改 D 盘的当前目录不切当前盘文件还是落在 C 盘。这是个很典型的老坑/d能一次性绕开。目录切完ftp这一行建议加上输出重定向把会话日志留到文件里ftp -n -v -s:C:\scripts\1.ftp C:\logs\ftp_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log 21 echo [%TIME%] exit%ERRORLEVEL% C:\logs\ftp_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log是追加避免同一天多次运行互相覆盖21把错误流也并进同一个文件否则连接失败的提示只会闪一下窗口就没了。最后那行echo exit%ERRORLEVEL%很关键——%ERRORLEVEL%是紧跟上一条命令的退出码中间隔了别的命令就被覆盖了所以必须紧贴着写。3.3 先手工跑通再挂计划验证的三个观察点挂任务计划之前一定先在命令行里手工跑一遍而且要模拟任务计划的运行环境不是在资源管理器里双击。打开cmdcd到一个不相干的目录再执行C:\scripts\1.bat这样才能暴露相对路径的问题。跑完之后按三个点检查日志。第一看有没有230 Login successful没有的话是账号、密码或者对端权限的问题跟脚本后面的命令无关。第二数226 Transfer complete出现了几次和远端目录里的文件数对一下少几个就是通配符没匹配上或者某个文件被跳过。第三比对一个具体文件的大小本地和对端一致才算真的传完如果用的是 ASCII 模式传二进制文件大小可能一致但内容已经坏了这种情况只能靠校验和发现。%DATE%这个变量有个隐性依赖它的格式跟着系统区域设置走。中文系统默认是2024/05/12 周日这种形态%DATE:~0,4%取到年份没问题换成英文系统就是Sun 05/12/2024同样一段截取取到的是星期几的缩写日志文件名直接乱掉。要跨机器部署的话把时间戳换成wmic os get localdatetime或者用 PowerShell 的Get-Date -Format yyyyMMdd更保险别在这个细节上栽跟头。4. 挂进任务计划无人值守必须拧对的三个开关4.1 图形向导和 schtasks 命令行选哪个老系统上的路径是「开始 → 程序 → 附件 → 系统工具 → 任务计划 → 添加任务计划」一路下一步填周期和密码就完事。新系统Win7 之后改叫「任务计划程序」在运行框里敲taskschd.msc打开左侧「创建基本任务」适合手工配一次「创建任务」能配的选项更全。图形界面的好处是能看见每个开关的中文说明坏处是配十台机器要点十遍。批量部署我一般直接用schtasksschtasks /create /tn FTP_Auto_Transfer /tr cmd /c C:\scripts\1.bat /sc daily /st 02:30 /ru SYSTEM /rl HIGHEST /f参数逐个对着看参数含义需要注意的地方/tn任务名同名会冲突配/f直接覆盖/tr要执行的命令路径带空格必须用引号建议外套一层cmd /c/sc调度周期daily / weekly / minute / onlogon / onstart/st开始时间24 小时制 HH:mm不带秒/mo间隔配/sc minute /mo 15表示每 15 分钟/ru运行账户填 SYSTEM 时不需要密码/rp账户密码/ru指定普通账户时不填会退化成「仅登录时运行」/rl权限级别HIGHEST 需要当前是管理员/f同名覆盖不加会弹交互确认脚本里就卡住了/tr外面套cmd /c是刻意为之。任务计划对「程序路径」和「参数」是分开解析的直接写C:\scripts\1.bat有时会被当成程序名去找路径里一旦有空格就断在中间。套一层cmd /c之后整条命令交给命令解释器处理路径、重定向、连接符都按批处理的规则走行为更可控。4.2 「只在用户登录时运行」和「不管用户是否登录都运行」这一组选项是无人值守能不能成立的分水岭。图形界面里在「常规」选项卡顶部两个单选按钮上面那个是「只在用户登录时运行」下面那个是「不管用户是否登录都运行」。向导默认选的是上面那个因为它不需要存密码。选上面的后果是人一下班注销任务就停。有些环境里用户不注销、只是锁屏任务还能跑于是测试的时候一切正常正式跑了一周突然开始抽风——因为中间有人重启了机器登录会话没了。这种「间歇性失败」最难查因为失败的时候没人在现场看。选下面的选项要输入账户密码密码存在系统的凭据存储里。这里有个后续麻烦域环境或者本地安全策略要求定期改密码密码一改任务计划里存的旧密码就失效任务开始报0x8007052E登录失败。要么在改密码的流程里把任务计划一起更新要么干脆把运行账户换成SYSTEM。SYSTEM的好处是不需要密码、不受登录会话影响、权限足够高。代价是它看到的网络环境是机器本身的不是某个登录用户的用户手工映射的网络驱动器盘符它一律看不见必须用 UNC 路径。纯 FTP 传输不涉及映射盘所以SYSTEM是个很省心的选择但如果批处理里还要往网络共享目录里搬文件就得换回普通账户加存密码的方式。4.3 起始于、失败重试和「上次运行结果」图形界面里「操作」那一页有个「起始于可选」字段很多人跳过不填。这个字段设的是任务启动时进程的当前目录不填的话就是C:\Windows\System32。批处理里如果已经写了cd /d这个字段填不填无所谓如果批处理依赖相对路径这里必须填否则就是前面说的那种「手工跑正常、计划跑找不到文件」。schtasks命令行没有直接对应「起始于」的参数要设的话得导出 XML 改WorkingDirectory节点或者把/tr写成cmd /c cd /d C:\datatran 1.bat。后者更省事也不依赖 XML 的格式版本。「如果任务失败按以下频率重新启动」这个设置在 FTP 场景里很有用。对端偶尔网络抖动、服务端连接数满了、DNS 解析慢都会导致一次连接超时重试两三次基本能覆盖掉。频率设 5 分钟、重试 3 次是个比较稳的组合间隔太短会在对端还没释放连接的时候又顶上去。跑完之后看「上次运行结果」那一列几个常见码记一下0x0是成功0x1是一般性错误通常是脚本里的命令返回了非零0x2是找不到文件路径写错了0x41301是任务正在运行不是错误是还没跑完0x8007052E是登录失败密码失效。看到0x0也别急着放心前面说过FTP 连不上但脚本正常退出退出码也可能是 0真正的判断要看日志内容。5. 五次真实环境故障从卡在 y/n 到「只在登录时运行」下面这几条都是在这类脚本上真实遇到过的按「现象 → 原因 → 解决」写遇到类似症状可以直接对照。5.1 现象任务状态一直「正在运行」日志停在一串mget xxx?原因脚本里漏了promptmget匹配到多个文件时逐个等确认无人值守环境下没人敲 y进程一直挂着直到超时退出。原料里的脚本是有prompt的但很多人抄的时候只抄了open、user、cd、mget、bye几行把prompt和bin当成可选行略掉了。解决脚本里mget之前必须有一行prompt命令行再加-i做兜底。已经卡住的任务要在任务计划里手动「结束」掉否则下一次触发会因为「任务已在运行」被跳过看起来像是彻底不跑了。5.2 现象手工双击 bat 一切正常挂进计划就报「找不到文件」原因两种可能。一是批处理里没有cd /d任务计划的当前目录是System32相对路径全部失效。二是任务计划的「起始于」为空而 bat 里引用脚本时用了相对路径比如-s:1.ftp而不是-s:C:\scripts\1.ftp。解决bat 第一行写cd /d C:\datatran脚本路径一律用绝对路径/tr外面套cmd /c。三件事一起做这类问题基本不再出现。5.3 现象压缩包传过去解压报 CRC 错误但文件大小差不多原因用了 ASCII 模式传二进制文件。原料里那句「Windows 之间传就去掉 bin」被照搬到所有场景结果传的是 zip、dmp、exe。ASCII 模式把文件里恰好出现的0x0D0A当成行尾改写大小差别不大内容已经错了。解决二进制文件一律bin文本文件才用ascii按文件类型拆成不同脚本。已经传坏的文件没法修只能重传如果对端有历史备份比对一下大小和校验和就能确认是哪一批坏的。5.4 现象白天测试都正常下班之后任务就再也不跑了原因任务计划用了默认的「只在用户登录时运行」。测试的时候人坐在机器前会话一直在下班注销或者机器重启之后没人登录任务就没有可用的登录令牌触发时直接被跳过。图形界面里这个选项藏在「常规」页顶部向导里根本不会提示。解决改成「不管用户是否登录都运行」并输入密码或者把运行账户换成SYSTEM。改完之后做一次验证注销当前用户等一个触发周期再登录进来看「上次运行结果」和日志文件有没有新内容。不注销就验证的等于没验证。5.5 现象账号密码都对就是登录失败日志里user那一行被截断了原因FTP 脚本按空格分词密码里带空格、#、;这类字符时user 用户名 密码这一行会被拆成多段客户端把第二段当成密码后面的当成未知命令。原料示例里密码是123456看着没问题换成真实环境的复杂密码就中招。解决最直接的办法是把密码改成不含空格和特殊符号的字符串。改不了的话就别把密码写死在脚本里改成每次运行前用echo生成一个临时脚本跑完删掉——虽然多了几步但至少不会被分词坑到。另外把明文密码放在.ftp文件里本身就有风险文件权限要收紧到只有运行账户可读。6. 从「能跑」到「跑坏了知道」退出码判断、日志计数与过期清理脚本能定时跑起来只是及格线真正省心的是它出问题的时候你能第一时间知道。ftp.exe的退出码信息量很有限连接失败返回 2命令执行中出错返回 1 或 0光看退出码判断不了「文件到底传没传全」。可行的办法是回到日志里数226 Transfer complete出现的次数和预期文件数比。下面这段是可以直接用的收尾逻辑echo off setlocal set LOGDIRC:\logs if not exist %LOGDIR% md %LOGDIR% set LOG%LOGDIR%\ftp_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log cd /d C:\datatran ftp -n -v -s:C:\scripts\1.ftp %LOG% 21 rem 统计成功传输次数和预期文件数比对 set EXPECT12 for /f %%c in (findstr /c:226 Transfer complete %LOG% ^| find /c /v ) do set ACTUAL%%c if not %ACTUAL%%EXPECT% ( echo [%DATE% %TIME%] 传输数不符 预期%EXPECT% 实际%ACTUAL% %LOGDIR%\alert.log exit /b 1 ) rem 清理 7 天前的日志避免目录无限膨胀 forfiles /p %LOGDIR% /m *.log /d -7 /c cmd /c del path nul 21 exit /b 0关键在for /f那一行findstr筛出所有含226 Transfer complete的行管道交给find /c /v 计数。find /c单独用会要求输入文件名配上/v 表示「统计所有不含空串的行」也就是统计全部行这是批处理里数行数的老技巧。EXPECT要按实际情况改文件数每天变的话可以改成和远端目录列表比对或者干脆只判断「实际数是否大于 0」。forfiles /d -7按修改时间筛出 7 天前的文件/c cmd /c del path逐个删掉日志目录就不会越堆越大。还有一个容易被忽略的点%ERRORLEVEL%在setlocal作用域里是即时值但在if块里引用时如果是延迟展开环境要用!ERRORLEVEL!。上面的写法把ftp和判断拆在两条独立语句里避开了这个坑代价是丢掉了ftp的原始退出码。要两个都保留的话在ftp那行之后立刻set RC%ERRORLEVEL%再用%RC%判断。从那以后我每次挂无人值守任务都强制走一遍「手工跑 → 看日志 → 注销会话再跑一次」这三步一步都不省。省下的那十分钟往往要用一个通宵来找回来。希望帮到你。本文还有配套的精品资源点击获取