跳转至

Shell 基础、管道与引用

标准输入、输出与错误

每个命令通常有三个标准流:

0 stdin   标准输入
1 stdout  标准输出
2 stderr  标准错误
command > output.txt       # 覆盖 stdout
command >> output.txt      # 追加 stdout
command 2> error.log       # 覆盖 stderr
command > all.log 2>&1     # stdout、stderr 写入同一文件
command < input.txt        # 从文件读取 stdin

重定向从左向右处理:

command >all.log 2>&1      # stderr 跟随已经指向 all.log 的 stdout
command 2>&1 >all.log      # stderr 仍指终端,只有 stdout 进文件

管道

管道把左侧 stdout 传给右侧 stdin:

journalctl -u nginx --since today \
  | grep ' 5[0-9][0-9] ' \
  | awk '{print $1, $2, $3}' \
  | sort \
  | uniq -c \
  | sort -nr

stderr 默认不会进入管道,需要明确合并:

command 2>&1 | grep -i error

不要写无意义的 cat file | grep pattern,直接 grep pattern file 更清楚;但 cat 合并多个文件或展示上下文时可以使用。

退出状态与条件执行

command
echo "$?"

command1 && command2   # 前一个成功才执行
command1 || command2   # 前一个失败才执行

Shell 约定 0 表示成功,非 0 表示失败。grep1 表示没有匹配,不一定是程序故障;2 才通常表示参数或读取错误。

脚本建议:

#!/usr/bin/env bash
set -Eeuo pipefail
  • -e:未处理的失败尽早退出,但在条件表达式中有例外。
  • -u:引用未定义变量时报错。
  • pipefail:流水线中任一命令失败会影响整体状态。
  • -E:ERR trap 在函数等上下文中继承。

这些选项不是自动安全保证,脚本仍要正确处理允许失败的命令和清理动作。

引号

name='app server'
printf '%s\n' "$name"    # 一个参数:app server
printf '%s\n' $name      # 两个参数,且会发生通配符展开
写法 行为
'text $var' 完全按字面量,不展开变量
"text $var" 展开变量和命令替换,但保留整体参数
text\ space 反斜杠转义下一个字符
无引号 变量展开后还会字段分割和通配符展开,风险最高

传递文件名和用户输入时始终加双引号:

cp -- "$source" "$destination"
rm -- "$target"

-- 表示后续内容不再按选项解析,防止文件名以 - 开头时被当成参数。

命令替换与算术

today=$(date +%F)
count=$(grep -c ERROR app.log)
next=$((count + 1))

使用 $(...),不要使用难以嵌套的反引号。命令替换会移除结尾换行,不适合无损保存任意二进制或多行数据。

常用辅助命令

head -n 20 file
tail -n 100 file
tail -F /var/log/app.log
wc -l file
cut -d: -f1 /etc/passwd
tr '[:lower:]' '[:upper:]'
paste file1 file2
tee output.log

tail -F 会在日志轮转后继续跟踪同名文件;tee 一边显示一边写文件。对来自 find 的文件名使用 NUL 分隔,避免空格和换行破坏:

find /var/log -type f -name '*.log' -print0 \
  | xargs -0 grep -l 'ERROR'