Shell 基础语法:变量、条件、循环与函数

Shell 脚本是 Linux 运维和日常开发中最常用的自动化工具。本文整理 Shell 最基础的语法知识:变量的定义与引用、字符串操作、条件判断、循环、函数,以及一个脚本究竟有哪几种执行方式、它们之间有什么区别。

变量的定义与引用

变量名由字母、数字和下划线组成,且不能以数字开头。定义时等号两边不能有空格,这是 Shell 新手最容易踩的坑。

name="sungod"
age=18
readonly PI=3.14        # 只读变量,赋值后不可修改
unset name              # 删除变量

引用变量时使用 $ 符号,变量名紧跟的字符有歧义时需要加花括号:

echo $name
echo "My name is ${name}_dev"   # 花括号明确变量边界,这里不能写成 $name_dev
echo "I am $age years old"      # 双引号内变量会被展开
echo 'I am $age years old'      # 单引号内不展开,原样输出 $age

除了自定义变量,还有一组常用的特殊变量:

变量 含义
$0 脚本名
$1 ~ $n 第 1 到第 n 个位置参数
$# 参数个数
$@ / $* 所有参数列表
$? 上一条命令的退出状态,0 表示成功
$$ 当前 Shell 的 PID

字符串操作

str="hello world"

# 求长度
echo ${#str}                # 输出 11

# 截取子串:从第 6 个字符开始截取 5 个
echo ${str:6:5}             # 输出 world

# 删除前缀 / 后缀
file="archive.tar.gz"
echo ${file#*.}             # 从头删除最短匹配,输出 tar.gz
echo ${file##*.}            # 从头删除最长匹配,输出 gz
echo ${file%.*}             # 从尾删除最短匹配,输出 archive.tar
echo ${file%%.*}            # 从尾删除最长匹配,输出 archive

# 替换
echo ${str/world/shell}     # 输出 hello shell

# 默认值:变量未定义或为空时使用默认值
echo ${UNSET_VAR:-default}  # 输出 default

字符串拼接直接写在一起即可:

greeting="Hello, "${name}"!"
greeting2="Hello, ${name}!"

条件判断

条件判断用 if 配合 test(即 [ ])完成,注意 [ 后面和 ] 前面必须有空格

if [ "$age" -gt 18 ]; then
    echo "adult"
elif [ "$age" -gt 12 ]; then
    echo "teenager"
else
    echo "child"
fi

常用判断条件:

# 数值比较
[ $a -eq $b ]   # 等于
[ $a -ne $b ]   # 不等于
[ $a -gt $b ]   # 大于
[ $a -ge $b ]   # 大于等于
[ $a -lt $b ]   # 小于
[ $a -le $b ]   # 小于等于

# 字符串比较
[ -z "$str" ]           # 字符串长度为 0
[ -n "$str" ]           # 字符串长度非 0
[ "$str1" = "$str2" ]   # 字符串相等

# 文件判断
[ -e /tmp/a.txt ]   # 文件存在
[ -f /tmp/a.txt ]   # 存在且是普通文件
[ -d /tmp/dir ]     # 存在且是目录
[ -x /tmp/a.sh ]    # 存在且可执行

# 逻辑组合
[ $a -gt 0 ] && [ $a -lt 10 ]   # 与
[ $a -eq 0 ] || [ $a -eq 1 ]    # 或

除了 [ ],还有两种更强大的写法:

  • (( )):用于算术表达式,支持 ><>= 等数学符号,不需要转义;
  • [[ ]]:bash 的增强版条件判断,支持 &&|| 直接写在括号内,还支持通配符和正则匹配。
if (( a > 10 && a < 20 )); then
    echo "a is between 10 and 20"
fi

if [[ $str == hello* ]]; then
    echo "str starts with hello"
fi

循环

for 循环

# 列表形式
for i in 1 2 3 4 5; do
    echo "number: $i"
done

# 遍历文件
for f in /var/log/*.log; do
    echo "$f"
done

# C 风格
for (( i = 0; i < 5; i++ )); do
    echo "i = $i"
done

while 循环

# 常见的逐行读文件
while read line; do
    echo "$line"
done < /etc/passwd

# 计数循环
count=0
while [ $count -lt 5 ]; do
    echo $count
    (( count++ ))
done

untilwhile 相反,条件为假时继续循环:

until [ $count -ge 5 ]; do
    (( count++ ))
done

函数

函数定义可以带 function 关键字,也可以不带。函数内的 $1$2 是函数自己的参数,与脚本的参数互不影响。

#!/bin/bash

greet() {
    local name=$1           # local 声明局部变量
    echo "Hello, $name"
    return 0                # return 只能返回 0~255 的整数,表示退出状态
}

greet "world"               # 输出 Hello, world
echo $?                     # 输出 0

# 如果需要"返回值",可以用命令替换捕获 echo 的输出
result=$(greet "shell")
echo "$result"              # 输出 Hello, shell

脚本的几种执行方式

同一个脚本,有三种常见执行方式,行为却完全不同:

bash test.sh    # 方式一
./test.sh       # 方式二(需要有可执行权限)
source test.sh  # 方式三,等价写法 . test.sh
  • bash test.sh:当前 Shell 新建一个子进程,在子进程中用 bash 解释执行脚本。子进程中的变量、cd 等操作不影响当前 Shell。
  • ./test.sh:脚本第一行的 #!/bin/bash(shebang)指明了用哪个解释器来执行,本质上也是新建一个子进程执行,因此环境同样不影响当前 Shell。这种方式要求脚本有可执行权限(chmod +x test.sh)。
  • source test.sh:在当前 Shell 进程中逐行执行脚本,不开子进程。脚本里定义的变量、函数、cd 操作都会保留在当前 Shell 中。所以脚本中写 exit 会直接退出当前 Shell,这一点要格外小心。

验证一下区别:

# test.sh 内容:
cd /tmp
new_var="hello"

bash test.sh
echo $new_var        # 输出为空,pwd 也不变

source test.sh
echo $new_var        # 输出 hello,当前目录变成了 /tmp

日常写脚本时,一般用 bash xxx.sh./xxx.sh 执行;而需要修改当前环境(比如加载配置、切换目录)时才用 source


   转载规则


《Shell 基础语法:变量、条件、循环与函数》 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Shell 脚本条件判断详解 Shell 脚本条件判断详解
条件判断是 Shell 脚本流程控制的基石。本文系统整理 if 语句的基本语法、[ ] 中的各类测试条件,以及 (( )) 和 [[ ]] 这两种高级写法,并附上常见用法示例。 基本语法if [ command ]; then 符合
2020-10-25
下一篇 
A Guided Tour A Guided Tour
A Guided TourIt’s time to take our tour. The table below lists some interesting places to explore. This is by no means a
2020-10-11
  目录