MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Bash中的特殊变量与参数扩展

2021-07-284.3k 阅读

一、Bash特殊变量概述

在Bash编程中,特殊变量是一类具有特定含义和用途的变量。它们不像普通变量那样由用户随意定义,而是由Bash shell预定义,为脚本编写者提供了与脚本执行环境、参数传递等紧密相关的重要信息。这些特殊变量极大地方便了脚本的编写,使脚本能够更灵活、高效地处理各种情况。

二、常见的Bash特殊变量

(一)位置参数变量

  1. $1, $2, …, $9
    • 含义:这些变量分别对应脚本执行时传递的第一个、第二个……直到第九个参数。例如,在脚本test.sh中,可以通过$1获取执行脚本时传入的第一个参数值。
    • 示例:创建一个名为print_args.sh的脚本,内容如下:
#!/bin/bash
echo "第一个参数: $1"
echo "第二个参数: $2"

然后在终端执行./print_args.sh apple banana,输出结果为:

第一个参数: apple
第二个参数: banana
  1. ${10}, ${11}, …
    • 含义:当参数个数超过9个时,需要使用这种大括号的形式来获取第10个及之后的参数。因为直接写$10会被Bash解析为$1后面跟着字符0,而不是第10个参数。
    • 示例:修改上述脚本为print_more_args.sh
#!/bin/bash
echo "第十个参数: ${10}"

执行./print_more_args.sh a b c d e f g h i j,输出为:

第十个参数: j
  1. $@ 和 $*
    • 含义$@$*都表示脚本的所有参数。但它们在使用上有细微差别,这主要体现在双引号内的展开方式上。$*在双引号内展开时,所有参数会被视为一个整体,以$IFS(内部字段分隔符,默认为空格、制表符和换行符)作为分隔连接起来;而$@在双引号内展开时,每个参数会被视为独立的单词。
    • 示例:创建test_at_star.sh脚本:
#!/bin/bash
echo "使用 \$* 在双引号内展开: \"$*\""
echo "使用 \$@ 在双引号内展开: \"$@\""

执行./test_at_star.sh a b c,输出结果为:

使用 $* 在双引号内展开: "a b c"
使用 $@ 在双引号内展开: "a" "b" "c"

在处理参数列表时,这种差异非常关键。例如,当需要将所有参数作为一个整体传递给某个命令时,$*更合适;而当需要对每个参数进行独立处理时,$@更方便。

(二)与脚本执行相关的特殊变量

  1. $0
    • 含义$0表示脚本的名称。在脚本内部,通过$0可以获取当前正在执行的脚本文件名,这在需要在脚本中提及自身名称的场景中很有用,比如日志记录、错误提示等。
    • 示例:创建print_script_name.sh脚本:
#!/bin/bash
echo "当前脚本名称: $0"

执行./print_script_name.sh,输出:

当前脚本名称:./print_script_name.sh
  1. $#
    • 含义$#表示传递给脚本的参数个数。通过这个变量,脚本可以动态地根据参数数量执行不同的逻辑。
    • 示例:创建check_arg_count.sh脚本:
#!/bin/bash
if [ $# -eq 0 ]; then
    echo "没有传递参数"
elif [ $# -eq 1 ]; then
    echo "传递了1个参数"
else
    echo "传递了多个参数"
fi

分别执行./check_arg_count.sh./check_arg_count.sh single./check_arg_count.sh one two,会得到相应的输出。 3. $$ - 含义$$表示当前Bash脚本或进程的进程ID(PID)。在需要为临时文件生成唯一名称、或者在脚本中需要区分不同实例时,$$非常有用。 - 示例:创建create_temp_file.sh脚本:

#!/bin/bash
temp_file="temp_$$.txt"
echo "创建临时文件: $temp_file"
touch $temp_file

每次执行该脚本,都会创建一个以当前进程ID命名的临时文件。 4. $? - 含义$?表示上一个命令的退出状态码。正常情况下,命令成功执行返回状态码0,失败则返回非零值。通过检查$?,脚本可以根据上一个命令的执行结果决定后续的操作。 - 示例:创建check_command_status.sh脚本:

#!/bin/bash
ls non_existent_file 2>/dev/null
status=$?
if [ $status -eq 0 ]; then
    echo "文件存在"
else
    echo "文件不存在"
fi

这里尝试列出一个不存在的文件,然后根据$?的值判断文件是否存在。

(三)其他特殊变量

  1. $-
    • 含义$-表示当前Shell的选项标志。它是一个字符串,包含了当前Shell以何种选项运行的信息。例如,h表示哈希查找功能开启,u表示当使用未定义变量时会报错等。
    • 示例:在脚本中可以通过打印$-来查看当前Shell的选项:
#!/bin/bash
echo "当前Shell选项: $-"
  1. $!
    • 含义$!表示最近在后台启动的进程的PID。当脚本中启动了后台任务,并且需要获取该任务的进程ID以便后续管理(如终止进程)时,$!就派上用场了。
    • 示例:创建start_background_task.sh脚本:
#!/bin/bash
sleep 10 &
pid=$!
echo "后台任务的PID: $pid"

执行该脚本时,会启动一个睡眠10秒的后台任务,并输出其PID。

三、Bash参数扩展

参数扩展是Bash中一种强大的机制,它允许在使用变量时对变量的值进行修改、替换或检查。这使得脚本在处理变量时更加灵活,能够满足各种复杂的需求。

(一)变量替换

  1. ${var:-word}
    • 含义:如果变量var已经设置(非空),则返回var的值;否则返回word。这个扩展常用于为变量提供默认值。
    • 示例:创建default_value.sh脚本:
#!/bin/bash
unset name
echo "名字: ${name:-Guest}"
name="John"
echo "名字: ${name:-Guest}"

输出结果为:

名字: Guest
名字: John
  1. ${var:=word}
    • 含义:如果变量var已经设置(非空),则返回var的值;否则将word赋值给var,并返回var的值。这不仅能提供默认值,还会实际修改变量的值。
    • 示例:修改上述脚本为assign_default_value.sh
#!/bin/bash
unset name
echo "名字: ${name:=Guest}"
echo "名字现在是: $name"
name="John"
echo "名字: ${name:=Guest}"
echo "名字现在是: $name"

输出为:

名字: Guest
名字现在是: Guest
名字: John
名字现在是: John
  1. ${var:?word}
    • 含义:如果变量var已经设置(非空),则返回var的值;否则将word输出到标准错误,并终止脚本执行。这常用于确保脚本所需的变量已被设置。
    • 示例:创建check_variable.sh脚本:
#!/bin/bash
unset name
echo ${name:?"名字变量未设置"}

执行该脚本时,会输出错误信息并终止脚本:

./check_variable.sh: line 3: name: 名字变量未设置
  1. ${var:+word}
    • 含义:如果变量var已经设置(非空),则返回word;否则返回空字符串。这可以根据变量是否设置来有条件地返回某些值。
    • 示例:创建conditional_return.sh脚本:
#!/bin/bash
unset name
echo "结果: ${name:+已设置}"
name="John"
echo "结果: ${name:+已设置}"

输出为:

结果: 
结果: 已设置

(二)字符串操作

  1. ${var:offset:length}
    • 含义:从变量var的值中提取子字符串。从偏移量offset开始,提取长度为length的子字符串。如果省略length,则提取从offset开始到字符串末尾的所有字符。偏移量从0开始计数。
    • 示例:创建substring_extraction.sh脚本:
#!/bin/bash
string="Hello, World!"
echo "从偏移量7开始,长度为5的子字符串: ${string:7:5}"
echo "从偏移量7开始到末尾的子字符串: ${string:7}"

输出为:

从偏移量7开始,长度为5的子字符串: World
从偏移量7开始到末尾的子字符串: World!
  1. ${var#pattern} 和 ${var##pattern}
    • 含义${var#pattern}从变量var的值开头删除最短匹配pattern的子字符串;${var##pattern}从变量var的值开头删除最长匹配pattern的子字符串。这里的pattern可以是通配符。
    • 示例:创建remove_prefix.sh脚本:
#!/bin/bash
url="https://example.com"
echo "最短匹配删除前缀: ${url#https://}"
echo "最长匹配删除前缀: ${url##https://}"

输出为:

最短匹配删除前缀: example.com
最长匹配删除前缀: example.com
  1. ${var%pattern} 和 ${var%%pattern}
    • 含义${var%pattern}从变量var的值末尾删除最短匹配pattern的子字符串;${var%%pattern}从变量var的值末尾删除最长匹配pattern的子字符串。
    • 示例:创建remove_suffix.sh脚本:
#!/bin/bash
file="document.txt"
echo "最短匹配删除后缀: ${file%.*}"
echo "最长匹配删除后缀: ${file%%.*}"

输出为:

最短匹配删除后缀: document
最长匹配删除后缀: document
  1. ${var/pattern/replacement} 和 ${var//pattern/replacement}
    • 含义${var/pattern/replacement}将变量var的值中第一个匹配pattern的子字符串替换为replacement${var//pattern/replacement}将变量var的值中所有匹配pattern的子字符串替换为replacement
    • 示例:创建replace_substring.sh脚本:
#!/bin/bash
text="Hello, world! Hello, bash!"
echo "替换第一个匹配: ${text/Hello/Hi}"
echo "替换所有匹配: ${text//Hello/Hi}"

输出为:

替换第一个匹配: Hi, world! Hello, bash!
替换所有匹配: Hi, world! Hi, bash!

(三)数组相关扩展

  1. ${array[@]}
    • 含义:展开数组array的所有元素,每个元素作为一个独立的单词。在将数组元素传递给命令时非常有用。
    • 示例:创建array_expansion.sh脚本:
#!/bin/bash
fruits=("apple" "banana" "cherry")
echo "数组所有元素: ${fruits[@]}"

输出为:

数组所有元素: apple banana cherry
  1. ${#array[@]}
    • 含义:返回数组array的元素个数。这在需要知道数组大小以便进行循环等操作时很有用。
    • 示例:修改上述脚本为array_size.sh
#!/bin/bash
fruits=("apple" "banana" "cherry")
echo "数组元素个数: ${#fruits[@]}"

输出为:

数组元素个数: 3
  1. ${array[*]}
    • 含义:与${array[@]}类似,但在双引号内展开时,所有元素会以$IFS作为分隔连接起来,形成一个字符串。
    • 示例:创建array_join.sh脚本:
#!/bin/bash
fruits=("apple" "banana" "cherry")
echo "数组元素连接: \"${fruits[*]}\""

输出为:

数组元素连接: "apple banana cherry"

四、特殊变量与参数扩展的综合应用

  1. 编写一个文件备份脚本 在编写文件备份脚本时,可以利用特殊变量和参数扩展来提高脚本的灵活性和健壮性。例如:
#!/bin/bash
source_file=$1
if [ -z "$source_file" ]; then
    echo "请指定源文件"
    exit 1
fi
backup_dir=${2:-backup}
if [! -d "$backup_dir" ]; then
    mkdir -p "$backup_dir"
fi
timestamp=$(date +%Y%m%d%H%M%S)
backup_file="$backup_dir/${source_file##*/}_$timestamp"
cp "$source_file" "$backup_file"
if [ $? -eq 0 ]; then
    echo "文件 $source_file 备份成功到 $backup_file"
else
    echo "文件备份失败"
fi

在这个脚本中,使用$1获取源文件参数,通过参数扩展确保源文件参数已设置。使用${2:-backup}为备份目录提供默认值,并根据需要创建备份目录。利用$$结合日期时间生成唯一的备份文件名,最后通过$?检查文件复制命令的执行状态。 2. 处理复杂参数列表的脚本 假设我们有一个脚本需要处理不同类型的参数,例如:

#!/bin/bash
while [ $# -gt 0 ]; do
    case "$1" in
        -f)
            file=$2
            shift
            ;;
        -d)
            dir=$2
            shift
            ;;
        *)
            echo "未知参数: $1"
            ;;
    esac
    shift
done
if [ -n "$file" ]; then
    echo "文件: $file"
fi
if [ -n "$dir" ]; then
    echo "目录: $dir"
fi

在这个脚本中,通过$#判断参数是否处理完毕,利用$1获取当前参数,根据不同的参数值(如-f-d)使用参数扩展获取相应的参数值,并对未知参数进行提示。

五、注意事项

  1. 特殊变量的作用域 特殊变量的作用域通常与脚本的执行环境相关。例如,$0$#等变量在整个脚本执行过程中都有效,但像$!只在启动后台进程后立即获取才有意义,后续如果有其他后台进程启动,$!的值会更新。
  2. 参数扩展的语法细节 在使用参数扩展时,语法必须准确。例如,${var:-word}中,{var之间不能有空格,否则会导致语法错误。同时,在处理复杂的模式匹配和替换时,要注意通配符的使用以及替换字符串中的特殊字符转义。
  3. 与其他编程语言的差异 Bash的特殊变量和参数扩展机制与其他编程语言有很大不同。例如,在Python中,命令行参数通过sys.argv获取,没有类似Bash中丰富的参数扩展语法。在从其他编程语言转向Bash编程时,需要特别注意这些差异,避免混淆。

通过深入理解和熟练运用Bash中的特殊变量与参数扩展,脚本编写者能够编写出更强大、灵活和健壮的Bash脚本,满足各种系统管理、自动化任务等需求。无论是简单的文件操作脚本,还是复杂的系统部署脚本,特殊变量和参数扩展都能发挥重要作用。在实际应用中,不断练习和总结经验,能更好地掌握这一强大的工具集。