把文件下载下来、拷到机器人上、每天执行一次脚本,看起来都是一句命令。
真正麻烦的是:下载了一半却被下游使用,SSH 连到了错误的机器,或者定时任务只在手工启动时正常。
这篇讨论 wget、scp、crontab,重点不是开关数量,而是给操作定义可靠的完成条件。
完整工具导航见 Linux 工具学习地图。
拿到程序后如果运行不了,接着读 ELF 与动态链接篇。
复现与工程示例分开
在完整网站源码仓库根目录执行:
bash docker/linux-tools-lab/run.sh build |
下载 复现说明、验证状态、原始会话 可以核对实际执行范围。
动画使用真实输出重新排版,不是屏幕录像。
静态解释给出同样的操作逻辑,系统选择减少动态效果时使用 PNG。
实验的 wget 使用容器内回环 HTTP 服务,scp 使用隔离的临时 SSH 服务。
它们不上传到真实机器人,也不修改用户的 SSH 配置。
crontab 动画展示精简环境下执行任务脚本,不代表已经等待真实 cron 守护进程按时触发。
真实调度验收需要额外检查服务、时间、身份和日志;不能把 env -i 的成功当成完整调度通过。
下文 robot-lab、/opt/robot、/var/lib/robot-maint 等名称是工程模板。
使用前替换成自己有权限的目标,创建并检查对应目录,不要原样向未知机器发送文件。
wget:下载成功不等于文件可用了
把一个下载拆成三个结果
第一层是连接成功:DNS、TCP、TLS 等是否完成。
第二层是传输成功:服务器返回了什么状态,响应内容是否完整写入。
第三层是制品正确:内容是否确实是期望的版本、格式和校验值。
HTTP 200 也可能返回登录页或错误提示;扩展名是 .tar.gz 不会强迫响应变成压缩包。
一个可靠的脚本必须把“获得了字节”与“获得了正确制品”区分开。
以有权访问的发行包为例,先在独立目录中试下载,不直接覆盖正在使用的文件:
download_dir="$(mktemp -d)" |
这里的 example.org 地址是占位,不是可运行的下载源。
--output-document/-O 选择下载内容文件,--output-file/-o 选择日志文件,大小写不能混用。
-O 有截断输出文件的语义,不应把它当成无风险的“改个名字”。Ubuntu 22.04 的 GNU Wget 手册
进度条以外还应该看什么
阅读响应信息时,先看最终状态码、重定向位置、长度与内容类型,再看本地落盘文件。
一个跳转到登录页的请求可以有完整进度条,但仍没有拿到模型。
长度相同也不是内容一致的证据。
file "$download_dir/model.tar.gz.part" |
校验值要与可信发布说明、签名验证后的清单或已经固定的制品记录比较。
如果校验值和制品都来自同一个被替换的不可信页面,“两者匹配”并不能确认发布者身份。
先建立信任来源,再用 hash 检测传输或存储中的变化。
最小练习:在实验副本里将文本文件改名为 model.tar.gz。
比较 file、解包命令和文件扩展名,解释为什么脚本不能仅根据名称判断下载成功。
续传要求本地文件是同一实体的前缀
wget --continue \ |
续传适合已经存在一部分内容且远端实体未变化的文件。
-c 不会替你证明本地前半段属于当前远端文件;同一个 URL 被换成更大版本时,拼接可能产生损坏结果。
服务器是否支持 Range、是否经代理、响应实体有没有变化,都需要考虑。
因此,优先选不可变版本地址,不要长期对 latest/model.tar.gz 盲目续传。
完成后仍做完整校验,失败则保留证据并重新获取,不把“这次没报网络错”当作正确性保证。
服务器不支持续传时,具体回退行为要看工具输出,别假设原文件一定不被覆盖。
限速与超时不是同一个维度
wget --limit-rate=2m \ |
限速控制传输节奏,适合与其他业务共享网络;重试处理某些失败,不保证所有 HTTP 错误都会重新尝试。
read timeout 关注读操作的停滞,不是给整个下载设置一个绝对总时长。
需要任务总预算时,另由任务调度器或外层超时控制,并保留部分文件的状态。
如果下载在后台自动运行,优先保留适量错误日志,不要习惯性使用 -q 把所有线索消掉。
日志可能包含临时 URL、认证信息或内部地址,交给别人分析前需要脱敏。
TLS 错误应修原因
证书验证失败时,先检查系统时间、主机名、证书链与信任根。
企业内部 CA 应通过受控方式安装,或指定经核验的 CA 文件,而不是永久关闭证书校验。
wget --ca-certificate=/opt/company/certs/root-ca.pem \ |
这是使用已审核内部 CA 的模板,不是在教如何接受任意证书。
不要把 --no-check-certificate 写进通用下载函数,更不要紧接着执行刚下载的未知脚本。
实验 GIF 使用回环 HTTP 只是隔离教学选择,不证明公网 TLS 路径已经测试。
下载、校验、发布应分阶段
应用应该只读取已经验证的正式文件。
可以先写入同一目标文件系统中的独立临时文件,校验通过后再切换到新版本;失败时保留旧版本可用。
若采用 rename 切换,要认识到“名称切换原子”不等于“重启、数据刷盘、应用重新加载等全部都事务化”。
更不能用一个成功的 mv 证明正在运行的控制器已经使用新模型。
发布动作需要独立的版本确认与回退入口。
scp:身份、内容、目的地都要正确
先确认客户端版本
ssh -V |
scp 是命令名,不保证底层永远使用老的 SCP 协议。
OpenSSH 从 9.0 开始默认改用 SFTP;Ubuntu 22.04 常见的 OpenSSH 8.9 客户端仍属于旧默认行为,需要按实际版本判断。
不能把新电脑上的成功命令无条件套回较老容器。OpenSSH 9.0 发布说明、Ubuntu 22.04 scp 手册
在 Ubuntu 22.04 的 8.9 实现里,可按其手册使用 -s 选择 SFTP;新版本已经默认 SFTP,不应把这个旧版本开关到处追加。
新客户端的 -O 用于明确请求老协议,是兼容选择,不是遇到错误时必加的修复开关。
远端路径通配符、空格与用户主目录扩展在不同协议下也存在差异。
最常用的操作骨架
# 上传一个文件 |
大写 -P 指端口,小写 -p 是保留时间与模式位等属性;不要混用。
递归传输可能跟随源树里的符号链接,传输前要确认实际范围。
-p 也不意味着跨系统完整复制所有所有权、ACL 与扩展属性。OpenSSH scp 手册
known_hosts 不是烦人的弹窗记录
用户密钥认证回答“你是谁”,主机密钥校验回答“对面是不是你以为的机器”。
两个问题不能互相替代。
脚本不应该靠 StrictHostKeyChecking=no 或空的 known_hosts 文件绕过身份确认。
正式环境可以使用经过带外核验的专用 known_hosts 文件:
scp -B \ |
这个文件必须提前用可信方式准备;不存在或键不匹配就失败,才是无人值守场景应该有的行为。
-B 避免任务卡在密码询问,但不会自动创建凭据,也不会替你验证目标身份。SSH 主机密钥策略
ssh-keyscan 只能采集网络对端给出的 key,不能独立证明身份。
生产环境应通过管理员、受控控制台或其他可信渠道核对指纹,再纳入记录。
本实验使用自己刚启动的隔离回环服务收集临时 key,并与本轮生成的服务端公钥逐字段和指纹比对,通过后才写入临时 known_hosts。不能把它扩展成“对任何公网主机扫描后直接信任”。OpenSSH ssh-keyscan 安全说明
进阶:跳板机与带宽预算
scp -J gateway-lab \ |
跳板路径需要每一段身份和权限都已正确配置,不要为了方便默认转发整个 SSH agent。
-l 的带宽单位是 Kbit/s,与 wget 速率选项的字节单位不同;8192 Kbit/s 大约对应 1 MiB/s 的原始量级,协议开销会让实际有效速率有所不同。
压缩并不总会更快:JPEG、压缩包和很多模型归档已经压缩,追加 SSH 压缩可能只增加 CPU 成本。
目标小板 CPU 很忙时,限速、打包批量文件与避开高峰通常比盲目开启压缩更值得测试。
不把一次复制当成完整同步系统
scp 不提供通用的断点续传与双向同步语义。
文件比较大、网络容易中断、只想传增量时,应评估具备相关机制的工具,并单独学习它们的覆盖、删除和校验规则。
不要因为源文件夹少了一个文件,就假设 scp 会自动删除目标残留。
传完至少比较明确的对象:
sha256sum ./payload.txt |
这两条比较的是文件内容,不检查应用是否已经读取新文件。
配置与可执行文件的更新应先写入 staging 目录,核验后再按应用要求切换。
避免直接覆盖正在用于复现问题的唯一二进制或对应调试符号。
最小练习:只向实验目标复制一个文件,再改动本地副本。
重新计算两端 hash,区分“曾经复制成功”和“现在两端仍相同”;再模拟错误目标路径,确认脚本能将失败上报。
crontab:时间表达式只是任务的一小部分
用户表与系统表不是一种格式
日常查看和编辑自己账户的任务表:
crontab -l |
没有任务表时,crontab -l 可能返回非零并提示没有 crontab,不等于整个 cron 服务坏了。
crontab -e 会改变真实调度,正式环境应先导出、审查差异并保留可恢复版本。
不要为了“清一下再写”先运行删除全部用户表的命令。
用户 crontab 的任务行为是五个时间字段后接命令:
# 分 时 日 月 星期 命令 |
/etc/crontab 和 /etc/cron.d/ 使用的系统表,在时间与命令之间还需要执行用户:
15 2 * * * robot /opt/robot-maint/collect.sh |
把系统表这一行复制到用户表,会把 robot 当成命令开头,结果当然与预期不同。Ubuntu crontab 格式
日与星期为什么会出乎意料
0 9 1 * 1 /opt/robot-maint/report.sh |
对这里讨论的 Ubuntu/Vixie cron,当“日”和“星期”两栏都受限制时,匹配关系是 OR。
因此这是每月 1 日或每周一的 9 点,不是“恰逢周一的每月 1 日”。
同一时刻同时匹配也不是为同一条记录运行两遍。
当一栏使用 * 时,不能机械套用“另一栏无所谓,反正星号永远真”。
要按具体实现对受限日字段的规则理解;不同 cron 家族的扩展也未必兼容。
如果要求复杂交集,宁可在明确的每日入口里写可测试的日期条件。
*/15 表示该字段中的步进匹配,不是“上次任务结束后再过 15 分钟”。
cron 也不是毫秒级控制循环调度器,不能拿它替代机器人实时定时机制。
本轮容器里 crontab -l 实际返回了权限限制:隔离配置中的 no-new-privileges 阻止了该程序通过 setgid 读取任务表。动画保留了这一结果,没有放宽权限或安装任务表;后续通过的是 env -i 下的任务脚本,不是 cron 定时触发。
该演示只回答“脚本脱离交互终端后是否还能执行”。
没有启动守护进程并观察触发,就不能由这张 GIF 得出“每天 2:15 已经验证会运行”。
最常见故障是环境差异
cron 任务不应依赖你刚刚在终端里手工 source 的 ROS 工作空间、Python 虚拟环境或 shell 函数。
应在自己的脚本里明确解释器、工作目录、必要环境和输出路径。
例如,一个项目脚本可以明确这样组织:
|
这是 ROS 工程模板,不属于只有基础工具的实验镜像。
实际解释器、包名与参数需要替换;并且 source 顺序本身也是复现条件,不应偷偷依赖登录脚本再覆盖一遍。
如果目标输出必须不能被读到一半,应用应写入临时文件后完整提交,而不是让消费者读取正在写的 JSON。
用户表可以显式限定命令查找环境:
SHELL=/bin/sh |
cron 环境赋值不是普通 shell 脚本展开,不要写 PATH=$HOME/bin:$PATH 并期待它自动补全。
工作目录也要在脚本中处理;“终端里我刚好 cd 到那里”不是任务契约。
精简环境测试有用,但真实 cron 仍可能额外受到 PAM、服务配置、用户与权限影响。
百分号会先被 cron 处理
直接在用户表里写日期格式时:
0 8 * * * /usr/bin/date +\%F >> /var/lib/robot-maint/dates.log 2>&1 |
未转义的 % 在该实现中有特殊含义,后续内容会进入标准输入处理。
它不是先交给 shell 再按单引号决定含义,所以只给日期格式加引号并不能解决所有问题。
更简单的工程做法是把日期逻辑写进脚本,crontab 只负责调用脚本。
前一次没结束,不要悄悄启动十个副本
如果一个采集任务有时需要 8 分钟,却每 5 分钟启动一次,就可能重叠运行。
对同一台机器上遵守同一个锁约定的任务,可以使用 flock:
*/5 * * * * /usr/bin/flock -n -E 75 /var/lib/robot-maint/collect.lock /opt/robot-maint/collect.sh >> /var/lib/robot-maint/collect.log 2>&1 |
锁目录需由管理员预先正确配置权限,不能假设任意用户可写 /var/lib。
-n 表示拿不到锁立即返回;这里显式把竞争失败码设为 75,以便与业务错误区分。
实际监控还要把跳过次数收集出来,避免任务一直拿不到锁却被当成正常完成。util-linux flock 手册
这不是分布式锁,也不会约束没有遵守同一锁文件的其他程序。
文件系统与挂载方式可能影响锁语义;多机任务不能仅靠每台机器自己的本地锁避免重复执行。
应用还应考虑幂等:同一份输入重复处理,是否会重复扣费、覆盖报告或上传两次。
日志、时区与守护进程都要验收
正式验收至少包括:任务属于哪个账户、系统当前时间和时区、cron 是否在运行、上一次什么时候开始和结束、退出码与产物是否正确。
日志不是越多越好,需要限制敏感内容并配置容量或轮转。
也不要默认系统已配置邮件投递,因而“没有收到 cron 邮件”不能证明任务没失败。
Ubuntu 的 cron 对时间变化有自己的处理规则;业务要求涉及夏令时或跨时区时,必须阅读本机实现说明并测试相关边界。
在 shell 里设置一个 TZ 影响脚本输出,不自动等于更改该 cron 守护进程的所有调度时区。Ubuntu cron 守护进程说明
Docker 镜像里有 crontab,不代表有定时服务
命令安装成功只是有了客户端。
一个以实验脚本为入口、运行后退出的容器,不会凭空在明天准时醒来。
同样,容器里存在 systemctl 文件也不能证明 systemd 是 PID 1 并在管理服务。
可以由宿主机调度一次性容器,也可以在明确设计的服务里运行调度器。
选择哪一种,要同时决定日志留存、凭据注入、失败重试、并发策略和容器退出行为。
不要为运行一个日常脚本,直接把生产容器改成无约束的长期特权环境。
若系统本来就使用 systemd,可以评估 timer 与 service 的组合。
OnCalendar 表达日历触发,Persistent=true 可处理部分错过触发的补跑需求;它不是所有历史触发逐条重放,也不使任务自动具备业务幂等性。systemd.timer 说明
最小练习:先手工执行自己的脚本,再用精简环境执行,比较缺少了哪些假设。
随后在明确授权的测试环境安装一条短周期任务,等待真实触发,并核对开始、结束、退出码及输出文件。
完成后撤掉这条测试记录;不能把临时高频任务遗留在正式账户里。
联合流程:每天把机器人诊断包带回来
分清生成、传输与消费
一个可维护的流程可以分成三段:
机器人生成独立事件目录;传输脚本只复制已完成的包;分析端核验后才将它加入待分析队列。
三者最好通过一个明确的完成标记或不可变版本名衔接,而不是共同读写 latest.tar.gz。
诊断包里应包含程序版本、配置、日志时间范围与必要的运行环境,不应默认打包整个用户目录。
core、网络抓包、参数文件都可能有敏感数据,发送前应检查范围。
密钥与 token 不应该随着“复现材料”进入公开笔记仓库。
给每种失败留不同的证据
下载 TLS 失败,检查信任与时间;scp 主机键变化,先核验远端身份;cron 没有触发,检查调度服务与时间。
如果任务确实启动了却报动态库缺失,应转向环境与 ELF 动态链接,而不是重复改 cron 时间表达式。
同样地,复制成功但分析机打不开库,可能是架构、符号或运行库不一致,不属于网络传输失败。
先对比 hash,再分析文件身份,可以把两层问题快速分开。
一份简洁的验收记录
记录本次任务 ID、输入版本、目标主机身份、开始结束时间、退出码、最终文件校验值与输出位置。
发生跳过、重试和超时也要记录,不仅记录成功路径。
这样下次问“昨晚到底有没有跑”,可以读事实,不需要靠终端历史猜测。
本系列选题参考用户提供的 Linux Tools Quick Tutorial 工具参考入口,示例按复现与运维问题独立编写。
继续观察运行中的进程可看 进程诊断篇;容量与性能问题可看 资源监控篇。