【第1章】内置命令
内置命令(builtin)由 shell 自身实现,不派生子进程。它们能直接改变当前 shell 的状态(变量、目录、任务),这也是
cd、source必须内置的原因。
command
command - 绕过 shell 函数和别名,直接执行同名的外部命令或内置命令
常用命令
command ls即使 ls 被 alias 成ls -G,也执行原始的 lscommand -v git查询命令的路径或定义(脚本里判断命令是否存在的标准写法)command -v node >/dev/null 2>&1 || echo "node 未安装"脚本中做依赖检查command -V cd显示命令类型的详细描述(builtin / alias / function / file)command -p getconf PATH用系统默认 PATH 查找命令,避免被用户 PATH 干扰
语法格式
command [-pVv] command [arg ...]
OPTIONS
-v打印命令的路径或定义(类似 which,但对 alias/function 也有效)-V打印更详细的类型说明-p使用系统默认的 PATH 搜索,而不是当前环境的 PATH
备注
- 与
which的区别:which是外部程序、只搜 PATH,看不到 alias/function/builtin;脚本里检测命令是否存在应优先用command -v - 在函数里调用同名外部命令时(如包装 cd 的函数内部),必须用
command cd防止无限递归
echo
echo - 将文本输出到标准输出
常用命令
echo "Hello World"输出一行文本并自动换行echo $PATH查看变量的值(最快的环境变量查看方式)echo -n "no newline"输出后不追加换行符,常用于拼接输出echo -e "line1\nline2\ttab"解释转义字符(\n换行、\t制表符)echo "content" > file.txt覆盖写入文件(快速创建小文件)echo "export PATH=\$PATH:/new/bin" >> ~/.zshrc追加一行内容到文件末尾echo $?输出上一条命令的退出码,脚本排错常用echo {1..5}配合花括号展开输出1 2 3 4 5echo *输出当前目录所有文件名(通配符由 shell 展开)echo -e "\033[31mred\033[0m"输出 ANSI 颜色文本
语法格式
echo [OPTIONS] [STRING...]
OPTIONS
-n不在末尾输出换行符-e启用反斜杠转义解释-E禁用转义解释(GNU echo 默认行为)
常用转义序列(配合 -e):
\n换行\t水平制表符\\反斜杠本身\033(或\e)ESC 字符,用于 ANSI 颜色控制\c到此为止不再输出(含换行),效果类似-n
备注
- echo 既是 shell 内置命令也有外部程序
/bin/echo,平时执行的是内置版本;各实现对-e/-n支持不一致:macOS 的/bin/echo(BSD)不支持-e,zsh 内置 echo 则默认就解释转义。可移植脚本建议改用printf,如printf '%s\n' "$str" - 输出变量务必加双引号:
echo "$var",否则值里的连续空格会被压缩、*等字符会被通配展开 - 输出以
-开头的任意内容时 echo 会把它当选项,此场景用printf '%s\n' "$data"
eval
eval - 将参数拼接成一条命令,由 shell 重新解析并执行
常用命令
eval "$(ssh-agent -s)"执行 ssh-agent 输出的环境变量设置语句(最常见用法)eval "$(brew shellenv)"加载 Homebrew 的环境变量(macOS 配置文件常见)eval "$(pyenv init -)"初始化 pyenv / rbenv / direnv 这类需要注入 shell 函数的工具cmd="ls -l /tmp"; eval "$cmd"执行存放在变量里的完整命令(含管道、重定向时必须 eval)eval echo \$$varname间接取变量值:varname 存的是另一个变量名(bash 更推荐${!varname})eval "$name=hello"动态给「名字存在变量里」的变量赋值(bash 也可用declare "$name=hello")for f in *.log; do eval "gzip '$f'"; done拼接后再解析一次,让变量中的引号、重定向生效
语法格式
eval [ARGUMENTS...]
OPTIONS
eval 是 shell 内置命令,没有选项:
ARGUMENTS所有参数以空格连接成一个字符串,再作为 shell 命令完整走一遍解析(变量展开、通配、引号、重定向、管道)并执行- 退出码为被执行命令的退出码;参数为空时返回 0
备注
- 安全风险:eval 会二次解析字符串,任何来自用户输入 / 外部数据的内容进入 eval 都可能造成命令注入,如
eval "ls $userinput"且输入为; rm -rf ~。原则:能不用就不用,必须用时严格控制输入来源 - 与直接执行
$cmd的区别:$cmd只做变量展开和分词,字符串里的|、>、&&、引号不会被解释;eval 会完整再解析一遍,因此「变量里存整条命令」的场景才需要 eval - 多数间接引用场景有更安全的替代:bash 的
${!var}、declare -n(nameref)、数组存命令"${cmd[@]}"
history
history - 查看和管理当前 shell 的命令历史
常用命令
history列出全部历史命令(带编号)history 20只看最近 20 条history | grep ssh搜索历史中包含 ssh 的命令!100重新执行编号为 100 的历史命令!!重新执行上一条命令,典型用法sudo !!!ssh重新执行最近一条以 ssh 开头的命令!$引用上一条命令的最后一个参数,如mkdir /data/logs后接cd !$Ctrl+R交互式反向搜索历史(输入关键词增量匹配,回车执行)history -c清空当前会话的内存历史history -d 100删除编号 100 的那条历史(如误输了密码)history -w立即把内存中的历史写入历史文件(bash 默认退出时才写)fc -l -10POSIX 方式列最近 10 条;fc不带参数则用编辑器打开上一条命令改完再执行
语法格式
history [N]
history [-c] [-d OFFSET]
history [-anrw] [FILENAME]
history [-ps] ARG...
OPTIONS
N只显示最近 N 条-c清空内存中的历史列表-d OFFSET删除指定编号的历史条目-a把本会话新增的历史追加到历史文件-r读取历史文件内容追加到当前内存列表-w把内存历史整体写入历史文件(覆盖)-n读取历史文件中尚未加载的新行(多终端同步)-p只做历史展开并打印结果,不执行、不入史-s把参数作为一条历史记录追加(不执行)
备注
- 相关环境变量:
HISTFILE(历史文件,bash 默认~/.bash_history,zsh 常配~/.zsh_history)、HISTSIZE(内存条数)、HISTFILESIZE/ zsh 的SAVEHIST(文件条数)、HISTCONTROL=ignoredups:ignorespace(bash:去重、以空格开头的命令不入史——临时敏感命令前加个空格即可) - bash 默认退出时才把历史写入文件,多窗口会互相覆盖;可用
PROMPT_COMMAND='history -a'实时追加。zsh 对应setopt SHARE_HISTORY/INC_APPEND_HISTORY - 以上选项为 bash 内置版本;zsh 的
history是fc -l的别名,-c/-d等选项不通用,zsh 清空历史需操作HISTFILE或用fc -p
kill
kill - 向进程发送信号(默认 TERM,常用于终止进程)
常用命令
kill 1234向 PID 1234 发送 SIGTERM,请求进程正常退出(可被捕获、做清理)kill -9 1234发送 SIGKILL 强制杀死,进程无法捕获或忽略(最后手段)kill -15 1234显式发送 SIGTERM,等价于不带参数kill -HUP 1234发送 SIGHUP,很多守护进程(nginx 等)约定收到后重新加载配置kill -STOP 1234/kill -CONT 1234暂停 / 恢复进程(不可捕获的挂起)kill -l列出所有信号名与编号kill -0 1234不发送任何信号,仅探测进程是否存在及是否有权限(脚本判活常用)kill -TERM -1234PID 为负数时向整个进程组 1234 发信号kill %1杀掉当前 shell 的 1 号后台作业(jobs 查看)pkill -f "python server.py"按命令行匹配杀进程;pgrep -f xxx先确认再杀更稳妥killall nginx按进程名杀掉所有同名进程
语法格式
kill [-SIGNAL | -s SIGNAL | -n SIGNUM] PID...
kill -l [SIGNUM]
OPTIONS
-SIGNAL信号名或编号,如-9、-KILL、-SIGKILL等价-s SIGNAL以选项形式指定信号名-n SIGNUM以选项形式指定信号编号-l列出信号;kill -l 9反查编号对应的信号名PID目标进程号;0当前进程组;-1所有有权限的进程(危险);-N进程组 N;%N作业号
常用信号速查:
1 SIGHUP挂断/重载配置2 SIGINT中断(等价 Ctrl+C)9 SIGKILL强制杀死,不可捕获15 SIGTERM默认信号,请求正常退出18 SIGCONT/19 SIGSTOP恢复 / 暂停(Linux 编号;macOS 为 19/17)
备注
- 优先
kill(TERM)给进程清理机会,几秒无效再kill -9;直接 -9 可能留下未落盘数据、僵尸子进程或残留锁文件 kill -9杀不掉的进程通常处于 D 状态(不可中断的内核 IO 等待)或为 zombie(僵尸进程要靠父进程回收,杀父进程才消失)- kill 既是 shell 内置命令又有外部程序
/bin/kill,内置版本才支持%N作业号;信号编号在 Linux 与 macOS/BSD 间有差异(如 SIGSTOP),跨平台脚本用信号名不用编号
set
set - 设置 shell 运行选项,或查看/重置位置参数与变量
常用命令
set -e任一命令失败(非零退出码)立即终止脚本,脚本头部标配set -x执行前打印每条命令(带展开后的实参),调试脚本利器set -u引用未定义变量时报错退出,避免rm -rf $DIR/之类事故set -o pipefail管道中任一环节失败即视为整条管道失败(bash/zsh)set -euxo pipefail严格模式组合,常作为 bash 脚本第一行配置set +x关闭命令回显(+表示关闭,与-相反)set -- a b c重置位置参数,之后$1=a$2=b$3=cset --清空全部位置参数set不带参数:列出当前 shell 的所有变量和函数set -o列出所有选项及当前开/关状态
语法格式
set [-abefhkmnptuvxBCEHPT] [-o OPTION] [ARGUMENTS...]
set [+abefhkmnptuvxBCEHPT] [+o OPTION] [ARGUMENTS...]
set -- [ARGUMENTS...]
OPTIONS
-e命令失败即退出(errexit);条件判断(if/&&/||)中的失败不触发-u使用未定义变量视为错误(nounset)-x回显执行的命令及展开结果(xtrace)-v原样回显读入的每一行(verbose,不做展开)-n只做语法检查不执行(noexec),bash -n script.sh同效-f禁用文件名通配展开(noglob)-C禁止>覆盖已存在文件(noclobber),需覆盖时用>|-o pipefail管道退出码取第一个失败命令的退出码-o OPTION长名形式:errexit/nounset/xtrace/noglob等与短选项一一对应--其后的参数全部作为新的位置参数,即使以-开头
备注
-开启、+关闭,方向与直觉相反,如set +e是取消「出错即退」set与env/export的区别:set 显示的是 shell 全部变量(含未 export 的)和选项开关;设置环境变量用export VAR=value,set 不用于赋值(set VAR=value在 bash 里只会改位置参数——这是从 csh 迁移过来的常见误用)set -e有不少坑:命令位于if、while、&&/||左侧、!之后时失败不会退出;子 shell 与命令替换中的行为随 bash 版本有差异,关键路径仍应显式判断退出码
source
source - 在当前 shell 中读取并执行脚本文件,不产生子进程
常用命令
source ~/.zshrc修改配置文件后立即生效,无需重开终端. ~/.bashrc.是 source 的 POSIX 等价写法,效果完全相同source /etc/profile重新加载系统级环境变量配置source venv/bin/activate激活 Python 虚拟环境(脚本要修改当前 shell 环境,必须 source)source .env将.env中的变量赋值导入当前 shell(变量需 export 才能传给子进程)set -a; source .env; set +a导入 .env 并自动 export 全部变量,供后续命令使用source script.sh arg1 arg2带位置参数执行脚本,脚本内可用$1$2取值(bash/zsh 支持)[ -f ~/.aliases ] && source ~/.aliases文件存在才加载,常写在配置文件里避免报错
语法格式
source FILENAME [ARGUMENTS...]
. FILENAME [ARGUMENTS...]
OPTIONS
source 是 shell 内置命令,本身没有选项开关,参数含义如下:
FILENAME要读取执行的脚本路径;文件名不含/时 bash 会先在$PATH中查找,用./file可强制当前目录ARGUMENTS传给脚本的位置参数;省略时脚本沿用当前 shell 的位置参数
相关的 shell 开关:
shopt -s sourcepath/shopt -u sourcepathbash 中控制是否在$PATH里查找被 source 的文件(默认开启)
备注
- 与
bash script.sh(子进程执行)的核心区别:source 在当前 shell 中执行,脚本里的变量赋值、cd、函数定义都会留在当前会话;子进程执行则全部随进程结束丢弃。这也是激活 venv 必须 source 的原因 - 被 source 的脚本里执行
exit会直接退出当前 shell(终端被关掉),脚本内应改用return .(点命令)是 POSIX 标准写法,source是 bash/zsh 扩展;写#!/bin/sh的可移植脚本时只能用.
test
test - 条件求值:判断文件属性、字符串与数值关系,以退出码表示真假
常用命令
test -f /etc/hosts && echo exists判断普通文件是否存在[ -d /data ] || mkdir /data[是 test 的等价写法(最后一个参数必须是]),目录不存在则创建[ -z "$var" ] && echo "var 为空"判断字符串为空[ -n "$var" ]判断字符串非空[ "$a" = "$b" ]字符串相等判断(注意是单个=,两边留空格)[ "$num" -gt 10 ]数值比较:大于(-eq -ne -lt -le -gt -ge)[ -x script.sh ] && ./script.sh有可执行权限才运行[ -s error.log ]判断文件存在且非空[ file1 -nt file2 ]判断 file1 比 file2 更新(newer than),增量构建常用[ -e "$path" -a -w "$path" ]多条件与(POSIX 已不推荐-a/-o,建议[ -e "$p" ] && [ -w "$p" ])[[ $str == foo* ]]bash/zsh 扩展[[ ]]:支持通配匹配、=~正则,且变量不加引号也不分词
语法格式
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]] # bash/zsh 扩展关键字
OPTIONS
文件判断:
-e FILE存在(任意类型)-f FILE是普通文件-d FILE是目录-L FILE是符号链接-s FILE存在且大小大于 0-r / -w / -x FILE可读 / 可写 / 可执行F1 -nt F2/F1 -ot F2F1 比 F2 新 / 旧
字符串与数值:
-z STR/-n STR为空 / 非空STR1 = STR2/STR1 != STR2字符串相等 / 不等N1 -eq N2数值相等,另有-ne -lt -le -gt -ge! EXPR逻辑取反
备注
- 变量务必加双引号:
[ -f "$file" ];不加引号时空值会让表达式塌缩成[ -f ]引发误判或语法错误。[[ ]]内无此问题 - 字符串比较用
=,==仅是 bash 扩展;数值比较必须用-eq系列,[ 10 > 9 ]里的>会被当成重定向([[ ]]内则是字符串字典序比较,[[ 10 > 9 ]]结果也不是数值比较) [要求最后一个参数是],且[、]与表达式之间必须有空格;[[ ]]是 shell 关键字非命令,功能更强(&&、||、=~正则、通配)但不是 POSIX,#!/bin/sh脚本只能用test/[