Home | 简体中文 | 繁体中文 | 杂文 | Github | 知乎专栏 | Facebook | Linkedin | Youtube | 打赏(Donations) | About
知乎专栏

1.3. I/O 重定向

		
cat <<End-of-message
   8 -------------------------------------
   9 This is line 1 of the message.
  10 This is line 2 of the message.
  11 This is line 3 of the message.
  12 This is line 4 of the message.
  13 This is the last line of the message.
  14 -------------------------------------
End-of-message
		
		

		
MYSQL=mysql
MYSQLOPTS="-h $zs_host -u $zs_user -p$zs_pass $zs_db"

$MYSQL $MYSQLOPTS <<SQL
SELECT
        category.cat_id AS  cat_id ,
        category.cat_name AS  cat_name ,
        category.cat_desc AS  cat_desc ,
        category.parent_id AS  parent_id ,
        category.sort_order AS  sort_order ,
        category.measure_unit AS  measure_unit ,
        category.style AS  style ,
        category.is_show AS is_show ,
        category.grade AS  grade
FROM  category
SQL
		
		

<<-LimitString可以抑制输出时前边的tab(不是空格). 这可以增加一个脚本的可读性.

		
cat <<-ENDOFMESSAGE
	This is line 1 of the message.
	This is line 2 of the message.
	This is line 3 of the message.
	This is line 4 of the message.
	This is the last line of the message.
ENDOFMESSAGE

		
		

关闭参数替换

		
NAME="John Doe"
RESPONDENT="the author of this fine script"

cat <<'Endofmessage'

Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.

Endofmessage
		
		
		
NAME="John Doe"
RESPONDENT="the author of this fine script"

cat <<\Endofmessage

Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.

Endofmessage
		
		

1.3.1. stdout

$ ln -s /dev/stdout test
$ cat file > test
			

1.3.2. error 重定向

			
your_shell 2>&1
			
			

错误输出演示

			
[root@localhost ~]# id ethereum
id: ethereum: no such user

# 这里可以看到错误输出 id: ethereum: no such user

[root@localhost ~]# id ethereum > test
id: ethereum: no such user

我们尝试将他重定向到文件 test,但是结果仍是输出 id: ethereum: no such user

[root@localhost ~]# cat test
[root@localhost ~]#

查看 test 文件,内容空。
			
			

继续做实验

			
[root@localhost ~]# id ethereum > test 2>&1
[root@localhost ~]# cat test
id: ethereum: no such user
			
			

测试实验结果成功了,将错误输出转到标准输出,然后写入文件。

1.3.3. 使用块记录日志

			
{
	...
	...
} > $LOGFILE 2>&1
			
			

1.3.4. test - 条件判断命令

test 命令用于条件判断,返回 0(真)或非 0(假)。在 shell 脚本中常用于 if、while 等控制结构。方括号 [ ] 是 test 命令的另一种写法。

判断目录
				
	test -d /path/to/directory || mkdir -p /path/to/directory
				
			
检查环境变量
			
		test -x $HAPROXY || exit 0
		test -f "$CONFIG" || exit 0
			
			
基本语法
test EXPRESSION
[ EXPRESSION ]

# 两种写法等价:
test -f /etc/passwd
[ -f /etc/passwd ]
			

注意:[EXPRESSION 以及 ] 之间必须有空格。

文件测试操作符

测试文件属性的常用操作符:

  • -e file - 文件存在

  • -f file - 文件存在且是普通文件

  • -d file - 文件存在且是目录

  • -r file - 文件存在且可读

  • -w file - 文件存在且可写

  • -x file - 文件存在且可执行

  • -s file - 文件存在且大小大于 0

  • -L file - 文件存在且是符号链接

  • -O file - 文件存在且属于当前用户

  • -G file - 文件存在且属于当前组

  • -N file - 文件存在且上次读取后被修改过

  • -c file - 文件存在且是字符特殊文件

  • -b file - 文件存在且是块特殊文件

  • -S file - 文件存在且是套接字

  • -p file - 文件存在且是命名管道

文件比较:

  • file1 -nt file2 - file1 比 file2 新(mtime)

  • file1 -ot file2 - file1 比 file2 旧

  • file1 -ef file2 - file1 和 file2 有相同的 device 和 inode

示例:

# 检查文件是否存在
test -f /etc/passwd && echo "文件存在"

# 检查目录是否存在
[ -d /var/log ] && echo "目录存在"

# 检查文件是否可执行
[ -x /usr/bin/munin-cron ] && /usr/bin/munin-cron

# 检查文件是否为空
if [ ! -s /tmp/log.txt ]; then
    echo "文件为空或不存在"
fi

# 比较两个文件的新旧
if [ file1.txt -nt file2.txt ]; then
    echo "file1.txt 比 file2.txt 新"
fi
			
字符串测试操作符

字符串比较操作符:

  • -z string - 字符串长度为 0(空字符串)

  • -n string - 字符串长度不为 0(非空字符串)

  • string1 = string2 - 字符串相等

  • string1 != string2 - 字符串不等

示例:

# 检查变量是否为空
name=""
if [ -z "$name" ]; then
    echo "变量为空"
fi

# 检查变量是否非空
name="neo"
[ -n "$name" ] && echo "变量非空: $name"

# 字符串相等比较
user="root"
if [ "$user" = "root" ]; then
    echo "是 root 用户"
fi

# 字符串不等比较
if [ "$user" != "guest" ]; then
    echo "不是 guest 用户"
fi

# 注意:变量应该用引号包裹,防止 word splitting
[ "$var" = "value" ]  # 正确
[ $var = "value" ]    # 危险:如果 $var 为空会报错
			
整数比较操作符

整数比较操作符:

  • n1 -eq n2 - n1 等于 n2

  • n1 -ne n2 - n1 不等于 n2

  • n1 -lt n2 - n1 小于 n2

  • n1 -le n2 - n1 小于等于 n2

  • n1 -gt n2 - n1 大于 n2

  • n1 -ge n2 - n1 大于等于 n2

示例:

# 检查数字是否相等
count=5
if [ "$count" -eq 5 ]; then
    echo "计数为 5"
fi

# 检查数字范围
age=25
if [ "$age" -ge 18 ] && [ "$age" -le 65 ]; then
    echo "年龄在 18-65 之间"
fi

# 检查进程数
procs=$(ps aux | wc -l)
if [ "$procs" -gt 100 ]; then
    echo "进程数过多: $procs"
fi

# 检查返回值
command
if [ $? -ne 0 ]; then
    echo "命令执行失败"
fi
			

记忆技巧:

  • eq = equal

  • ne = not equal

  • lt = less than

  • le = less than or equal

  • gt = greater than

  • ge = greater than or equal

逻辑操作符

组合多个条件:

  • expr1 -a expr2 - 逻辑与(expr1 和 expr2 都为真)

  • expr1 -o expr2 - 逻辑或(expr1 或 expr2 为真)

  • ! expr - 逻辑非

使用 shell 操作符(推荐):

  • expr1 && expr2 - 逻辑与

  • expr1 || expr2 - 逻辑或

  • ! expr - 逻辑非

示例:

# 逻辑与:文件存在且可执行
[ -f /usr/bin/munin-cron ] && [ -x /usr/bin/munin-cron ] && /usr/bin/munin-cron

# 使用 test 的 -a 操作符
test -f /etc/passwd -a -r /etc/passwd && echo "文件存在且可读"

# 逻辑或:目录不存在则创建
[ -d /tmp/test ] || mkdir /tmp/test

# 逻辑非:文件不存在
[ ! -f /tmp/lock ] && echo "锁文件不存在"

# 复杂条件组合
if [ -f "$file" ] && [ -r "$file" ] && [ -s "$file" ]; then
    echo "文件存在、可读且非空"
fi

# 嵌套条件
if [ "$user" = "root" ] || ([ "$user" = "admin" ] && [ -w "$file" ]); then
    echo "有权限"
fi
			
在 if 语句中使用
# 基本 if 结构
if [ -f /etc/passwd ]; then
    echo "文件存在"
fi

# if-else 结构
if [ -d /var/log ]; then
    echo "目录存在"
else
    echo "目录不存在"
fi

# if-elif-else 结构
if [ -f "$file" ]; then
    echo "是普通文件"
elif [ -d "$file" ]; then
    echo "是目录"
elif [ -L "$file" ]; then
    echo "是符号链接"
else
    echo "未知类型"
fi

# 多条件判断
if [ -f "$config" ] && [ -r "$config" ]; then
    source "$config"
elif [ -f "$default_config" ]; then
    source "$default_config"
else
    echo "没有配置文件"
    exit 1
fi
			
在 while/until 循环中使用
# while 循环:条件为真时执行
count=0
while [ $count -lt 5 ]; do
    echo "计数: $count"
    count=$((count + 1))
done

# until 循环:条件为假时执行
until [ -f /tmp/ready ]; do
    echo "等待文件创建..."
    sleep 2
done

# 读取用户输入
while [ "$answer" != "yes" ] && [ "$answer" != "no" ]; do
    read -p "请输入 yes 或 no: " answer
done
			
实际应用示例
# 检查命令是否存在
if [ -x /usr/bin/git ]; then
    echo "git 已安装"
else
    echo "git 未安装"
    exit 1
fi

# 检查用户权限
if [ "$(id -u)" -ne 0 ]; then
    echo "需要 root 权限运行"
    exit 1
fi

# 文件备份脚本
src="/data/important.db"
backup="/backup/$(date +%Y%m%d)_important.db"
if [ -f "$src" ] && [ -s "$src" ]; then
    cp "$src" "$backup"
    echo "备份成功: $backup"
else
    echo "源文件不存在或为空"
    exit 1
fi

# 检查磁盘空间
available=$(df / | tail -1 | awk '{print $4}')
threshold=1048576  # 1GB in KB
if [ "$available" -lt "$threshold" ]; then
    echo "警告:磁盘空间不足 1GB"
fi

# 等待服务启动
timeout=30
count=0
while [ $count -lt $timeout ]; do
    if [ -S /var/run/mysql.sock ]; then
        echo "MySQL 已启动"
        break
    fi
    sleep 1
    count=$((count + 1))
done

if [ $count -eq $timeout ]; then
    echo "MySQL 启动超时"
    exit 1
fi
			
[[ ]] 扩展测试(Bash 特有)

Bash 提供 [[ ]] 作为 [ ] 的增强版本:

  • 支持正则表达式匹配:=~

  • 支持 &&|| 在测试内部使用

  • 不需要对变量加引号(自动处理)

  • 支持模式匹配:== 配合通配符

# 正则表达式匹配
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "有效的邮箱地址"
fi

# 模式匹配
if [[ "$filename" == *.jpg ]]; then
    echo "是 JPEG 图片"
fi

# 内部使用逻辑操作符
if [[ -f "$file" && -r "$file" ]]; then
    echo "文件存在且可读"
fi

# 字符串比较(不需要引号)
if [[ $user == root ]]; then
    echo "是 root 用户"
fi

# 字典序比较
if [[ "apple" < "banana" ]]; then
    echo "apple 在 banana 之前"
fi
			

注意:[[ ]] 是 Bash 扩展,不兼容 POSIX sh。如果需要可移植性,使用 [ ]

常见陷阱
# 错误:方括号内没有空格
if [-f /etc/passwd]; then  # 错误!
if [ -f /etc/passwd ]; then  # 正确

# 错误:整数比较使用字符串操作符
if [ "$count" = "5" ]; then  # 字符串比较
if [ "$count" -eq 5 ]; then  # 整数比较(正确)

# 危险:未加引号的变量
if [ $file = "test.txt" ]; then  # 如果 $file 为空会报错
if [ "$file" = "test.txt" ]; then  # 安全

# 错误:使用 = 比较数字
if [ $age = 18 ]; then  # 字符串比较
if [ $age -eq 18 ]; then  # 整数比较(正确)

# 错误:在 [ ] 中使用 && 和 ||
if [ -f file1 && -f file2 ]; then  # 错误!
if [ -f file1 ] && [ -f file2 ]; then  # 正确
			
返回值检查

test 命令的返回值($?):

# 0 表示真,非 0 表示假
test -f /etc/passwd
echo $?  # 输出: 0(文件存在)

test -f /nonexistent
echo $?  # 输出: 1(文件不存在)

# 在脚本中检查返回值
command
if [ $? -eq 0 ]; then
    echo "命令成功"
else
    echo "命令失败,退出码: $?"
fi

# 简写形式
command && echo "成功" || echo "失败"
			

1.3.5. tee - read from standard input and write to standard output and files

echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward;
			
重定向到文件
				
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://du8c1in9.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
				
				
nettee - a network "tee" program

				

1.3.6. 创建文件

			
cat << EOF > foo.sh
   printf "%s was here" "$name"
EOF

cat >> foo.sh <<EOF
   printf "%s was here" "$name"
EOF
			
			

1.3.7. 快速清空一个文件的内容

$ > /www/access.log