1. Shell脚本基础命令符概览
作为Linux系统管理员和开发人员,我们每天都要与Shell脚本打交道。掌握基础命令符就像木匠熟悉自己的工具一样重要。下面这张表格列出了最常用的20个Shell命令符及其典型应用场景:
| 命令符 | 功能描述 | 使用示例 | 适用场景 |
|---|---|---|---|
# |
注释符号 | # 这是一行注释 |
脚本说明、代码解释 |
$ |
变量引用 | echo $PATH |
获取变量值 |
() |
命令分组 | (cd /tmp && ls) |
子shell中执行命令组 |
{} |
代码块界定 | { cmd1; cmd2; } |
在当前shell执行命令组 |
[] |
条件测试 | [ -f file.txt ] |
文件测试、条件判断 |
$() |
命令替换 | files=$(ls) |
捕获命令输出到变量 |
\ |
转义字符 | echo "Price: \$5" |
特殊字符转义 |
&& |
逻辑与 | cmd1 && cmd2 |
前命令成功才执行后命令 |
|| |
逻辑或 | cmd1 || cmd2 |
前命令失败才执行后命令 |
; |
命令分隔 | cmd1; cmd2 |
顺序执行多个命令 |
<< |
Here文档 | cat <<EOF |
多行文本输入 |
> |
输出重定向 | ls > file.txt |
覆盖写入文件 |
>> |
追加重定向 | echo "new" >> file.txt |
追加写入文件 |
< |
输入重定向 | wc -l < file.txt |
从文件读取输入 |
| |
管道 | ps aux | grep python |
命令输出作为下个命令输入 |
& |
后台运行 | sleep 10 & |
后台执行命令 |
* |
通配符 | rm *.tmp |
匹配任意字符 |
? |
单字符通配 | ls file?.txt |
匹配单个字符 |
! |
历史命令/逻辑非 | !-2 或 [ ! -z "$var" ] |
调用历史命令或逻辑取反 |
~ |
家目录 | cd ~/projects |
快速切换到用户目录 |
注意:在Shell脚本中,空格的使用非常关键。例如在条件测试
[ ]中,括号内必须留有空格,写成[ -f file ]是正确的,而[-f file]会导致语法错误。
2. 变量与字符串操作符详解
2.1 变量定义与引用
Shell中的变量就像贴了标签的储物箱,你可以随时存放和取出数据。但有些细节新手容易忽略:
bash复制# 正确赋值(等号两边不能有空格)
username="linuxuser"
# 错误示例(等号两边有空格)
username = "linuxuser" # 这会被解析为命令'username'带参数'='和'"linuxuser"'
# 引用变量时最好加双引号
echo "Hello, $username" # 推荐
echo Hello, $username # 当变量值含空格时会出问题
2.2 字符串操作符实战
字符串处理是Shell脚本的常见任务,下表列出了最实用的字符串操作符:
| 操作符 | 功能 | 示例 | 输出结果 |
|---|---|---|---|
${#var} |
字符串长度 | var="hello"; echo ${#var} |
5 |
${var:pos} |
子串提取 | var="abcdef"; echo ${var:2} |
cdef |
${var:pos:len} |
定长子串 | var="abcdef"; echo ${var:2:3} |
cde |
${var#pattern} |
删除最短前缀 | var="file.txt.bak"; echo ${var#*.} |
txt.bak |
${var##pattern} |
删除最长前缀 | var="file.txt.bak"; echo ${var##*.} |
bak |
${var%pattern} |
删除最短后缀 | var="file.txt.bak"; echo ${var%.*} |
file.txt |
${var%%pattern} |
删除最长后缀 | var="file.txt.bak"; echo ${var%%.*} |
file |
${var/old/new} |
替换首个匹配 | var="hello world"; echo ${var/ /_} |
hello_world |
${var//old/new} |
替换所有匹配 | var="a b c"; echo ${var// /-} |
a-b-c |
${var:-default} |
默认值 | unset var; echo ${var:-"empty"} |
empty |
经验分享:在脚本中处理文件路径时,
${var##*/}可以提取文件名(相当于basename),${var%/*}可以提取目录路径(相当于dirname),这比调用外部命令效率更高。
3. 条件测试与比较操作符
3.1 文件测试操作符
文件检查是Shell脚本中最常见的条件测试之一。以下操作符可以帮助你判断文件的各种属性:
| 操作符 | 测试条件 | 示例 | 说明 |
|---|---|---|---|
-e |
文件存在 | [ -e file ] |
文件或目录存在即为真 |
-f |
是普通文件 | [ -f file ] |
非目录、设备文件等 |
-d |
是目录 | [ -d dir ] |
测试是否为目录 |
-s |
文件非空 | [ -s file ] |
文件大小大于0 |
-r |
可读 | [ -r file ] |
当前用户有读权限 |
-w |
可写 | [ -w file ] |
当前用户有写权限 |
-x |
可执行 | [ -x file ] |
当前用户有执行权限 |
-L |
符号链接 | [ -L file ] |
测试是否为符号链接 |
-nt |
新于 | [ file1 -nt file2 ] |
file1比file2新 |
-ot |
旧于 | [ file1 -ot file2 ] |
file1比file2旧 |
-ef |
相同文件 | [ file1 -ef file2 ] |
硬链接到同一inode |
3.2 字符串比较操作符
字符串比较时,操作符两边必须有空格,否则会被解释为字符串的一部分:
bash复制# 正确比较方式
if [ "$str1" = "$str2" ]; then
echo "Strings are equal"
fi
# 错误示例(缺少空格)
if ["$str1"="$str2"]; then # 这会导致语法错误
echo "This won't work"
fi
常用字符串比较操作符:
| 操作符 | 含义 | 示例 |
|---|---|---|
= 或 == |
等于 | [ "$a" = "$b" ] |
!= |
不等于 | [ "$a" != "$b" ] |
< |
小于 | [[ "$a" < "$b" ]] |
> |
大于 | [[ "$a" > "$b" ]] |
-z |
空字符串 | [ -z "$var" ] |
-n |
非空字符串 | [ -n "$var" ] |
重要提示:在
[ ]测试中,<和>需要用\转义([\<]和[\>]),或者使用[[ ]]结构。这是因为在标准[ ]测试中,这些符号会被解释为重定向。
3.3 算术比较操作符
Shell脚本中进行数值比较时,推荐使用(( ))结构:
bash复制# 传统方式(不推荐)
if [ "$a" -gt "$b" ]; then
echo "a is greater than b"
fi
# 现代方式(推荐)
if (( a > b )); then
echo "a is greater than b"
fi
数值比较操作符对照表:
[ ] 风格 |
(( )) 风格 |
含义 |
|---|---|---|
-eq |
== |
等于 |
-ne |
!= |
不等于 |
-lt |
< |
小于 |
-le |
<= |
小于等于 |
-gt |
> |
大于 |
-ge |
>= |
大于等于 |
4. 高级命令组合与流程控制
4.1 命令组合操作符
Shell提供了多种方式来组合命令,每种方式都有其特定的用途和执行环境:
| 操作符 | 执行环境 | 示例 | 特点 |
|---|---|---|---|
; |
当前shell | cmd1; cmd2 |
顺序执行,不关心前命令结果 |
&& |
当前shell | cmd1 && cmd2 |
只有cmd1成功才执行cmd2 |
|| |
当前shell | cmd1 || cmd2 |
只有cmd1失败才执行cmd2 |
| |
当前shell | cmd1 | cmd2 |
cmd1输出作为cmd2输入 |
() |
子shell | (cmd1; cmd2) |
在子shell中执行,不影响当前环境 |
{} |
当前shell | { cmd1; cmd2; } |
在当前shell执行,注意空格和分号 |
4.2 流程控制结构
Shell脚本中的流程控制结构就像交通信号灯,指挥着程序的执行流向:
bash复制# if-then-else 结构
if [ condition ]; then
commands
elif [ another_condition ]; then
more_commands
else
other_commands
fi
# case 结构(非常适合多条件分支)
case "$variable" in
pattern1)
commands1
;;
pattern2)
commands2
;;
*)
default_commands
;;
esac
# for 循环(处理列表)
for item in list; do
commands
done
# while 循环(条件为真时执行)
while [ condition ]; do
commands
done
# until 循环(条件为假时执行)
until [ condition ]; do
commands
done
4.3 实用技巧与陷阱
- 引号使用:变量引用应该总是用双引号包裹,除非你有特殊理由不这样做。这可以防止空格导致的单词分割和路径名扩展。
bash复制# 危险示例
for file in $(ls *.txt); do # 如果文件名含空格会出问题
echo "$file"
done
# 安全做法
for file in *.txt; do # 让shell处理通配符
[ -e "$file" ] || continue # 跳过不匹配的情况
echo "$file"
done
- 错误处理:使用
set -e可以让脚本在命令失败时立即退出,使用set -o pipefail可以确保管道中任意命令失败都会导致整个管道失败。
bash复制#!/bin/bash
set -eo pipefail # 推荐在所有脚本开头添加
# 这样如果任何命令失败,脚本就会终止
command_that_might_fail
another_command
- 调试技巧:使用
set -x可以在执行时打印每条命令及其参数,非常适合调试复杂脚本。
bash复制#!/bin/bash
set -x # 开启调试模式
# 你的脚本命令
complex_command
another_complex_command
set +x # 关闭调试模式
- 参数处理:处理脚本参数时,
shift命令非常有用,它可以移动位置参数。
bash复制#!/bin/bash
while [ "$#" -gt 0 ]; do
case "$1" in
-v|--verbose)
verbose=1
shift
;;
-f|--file)
file="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
- 数组使用:Bash支持数组,这在处理多个项目时非常方便。
bash复制# 定义数组
files=(*.txt)
# 遍历数组
for file in "${files[@]}"; do
echo "Processing $file"
done
# 获取数组长度
echo "Total files: ${#files[@]}"
# 数组切片
echo "First three files: ${files[@]:0:3}"
掌握这些Shell脚本命令符和操作符,就像获得了一把打开Linux系统管理大门的钥匙。在实际工作中,我经常发现许多问题可以通过简单的Shell脚本优雅解决,而不需要编写复杂的程序。记住,好的Shell脚本应该像诗一样简洁明了,每个命令符都有其存在的理由。
