Bash 学习摘录


文章目录


1、变量和参数的介绍

(1)变量替换

$(...)

使用 $(...) 机制来进行变量赋值(这是一种比后置引用(反引号`)更新的一种方法)。 事实上这两种方法都是命令替换的一种形式。

bash 复制代码
arch=$(uname -m)

(2)特殊的变量类型

export

一个脚本只能够 export 变量到这个脚本所产生的子进程, 也就是说只能够对这个脚本所产生的命令和进程起作用。 如果脚本是从命令行中调用的, 那么这个脚本所 export 的变量是不能影响命令行环境的。 也就是说, 子进程是不能够 export 变量来影响产生自己的父进程的环境的。

位置参数

从命令行传递到脚本的参数: 0, 1, 2, 3 . . .

0 就是脚本文件自身的名字, 1 是第一个参数, 2 是第二个参数, 3 是第三个参数, 然后是第四个。 9 之后的位置参数就必须用大括号括起来了, 比如, **{10}**, {11}, {12} 。

两个比较特殊的变量 ** \* 和 **@ 表示所有的位置参数。

shift

shift 命令会重新分配位置参数, 其实就是把所有的位置参数都向左移动一个位置。

1 \<--- 2, 2 \<--- 3, 3 \<--- 4, 等等。

原来的 1** 就消失了, 但是 **0 (脚本名) 是不会改变的。 如果传递了大量的位置参数到脚本中, 那么 shift 命令允许你访问的位置参数的数量超过 10 个, 当然 {} 标记法也提供了这样的功能。

2、引用

(1)引用变量

双引号中通过直接使用变量名的方法来引用变量。单引号 (' ') 操作与双引号基本一样, 但是不允许引用变量, 因为 $ 的特殊意义被关闭了。

(2)转义

  • \0xx
    转换为八进制的 ASCII 码, 等价于 0xx

3、条件判断

(1)条件测试结构

  • test/usr/bin/test[ ] , 和 /usr/bin/[ 都是等价命令

  • [[ ]] 结构比 [ ] 结构更加通用。 这是一个扩展的 test 命令, 是从 ksh88 中引进的。

  • (( )) 结构扩展并计算一个算术表达式的值

(2)文件测试操作符

  • -e
    文件存在
  • -a
    文件存在。这个选项的效果与 -e 相同。 但是它已经被 "弃用" 了, 并且不鼓励使用。
  • -f
    表示这个文件是一个一般文件 (并不是目录或者设备文件)
  • -s
    文件大小不为零
  • -d
    表示这是一个目录
  • -b
    表示这是一个块设备 (软盘, 光驱, 等等。)
  • -c
    表示这是一个字符设备 (键盘, modem, 声卡, 等等。)
  • -p
    这个文件是一个管道
  • -h
    这是一个符号链接
  • -L
    这是一个符号链接
  • -S
    表示这是一个 socket
  • -t
    文件(描述符)被关联到一个终端设备上。这个测试选项一般被用来检测脚本中的 stdin([ -t 0 ]) 或者 stdout([ -t 1 ]) 是否来自于一个终端。
  • -r
    文件是否具有可读权限(指的是正在运行这个测试命令的用户是否具有读权限)
  • -w
    文件是否具有可写权限(指的是正在运行这个测试命令的用户是否具有写权限)
  • -x
    文件是否具有可执行权限(指的是正在运行这个测试命令的用户是否具有可执行权限)
  • -g
    set-group-id(sgid) 标记被设置到文件或目录上。
    如果目录具有 sgid 标记的话, 那么在这个目录下所创建的文件将属于拥有这个目录的用户组, 而不必是创建这个文件的用户组。 这个特性对于在一个工作组中共享目录非常有用。
  • -u
    set-user-id (suid) 标记被设置到文件上。
    如果一个 root 用户所拥有的二进制可执行文件设置了 set-user-id 标记位的话, 那么普通用户也会以 root 权限来运行这个文件。
  • -k
    设置粘贴位
    对于 "粘贴位" 的一般了解, save-text-mode 标志是一个文件权限的特殊类型。 如果文件设置了这个标志, 那么这个文件将会被保存到缓存中, 这样可以提高访问速度。
  • -O
    判断你是否是文件的拥有者
  • -G
    文件的 group-id 是否与你的相同
  • -N
    从文件上一次被读取到现在为止, 文件是否被修改过
  • f1 -nt f2
    文件 f1 比文件 f2 新
  • f1 -ot f2
    文件 f1 比文件 f2 旧
  • f1 -ef f2
    文件 f1 和文件 f2 是相同文件的硬链接
  • !
    "非" ------ 反转上边所有测试的结果(如果没给出条件, 那么返回真)。

(3)其他比较操作符

二元比较操作符用来比较两个变量或数字。 注意整数比较与字符串比较的区别。

整数比较

  • -eq

    等于

    if [ "a" -eq "b" ]

  • -ne

    不等于

    if [ "a" -ne "b" ]

  • -gt

    大于

    if [ "a" -gt "b" ]

  • -ge

    大于等于

    if [ "a" -ge "b" ]

  • -lt

    小于

    if [ "a" -lt "b" ]

  • -le

    小于等于

    if [ "a" -le "b" ]

  • <

    小于(在双括号中使用)

    (("a" \< "b"))

  • <=

    小于等于(在双括号中使用)

    ((" a " < = " a" <= " a"<="b"))

  • >

    大于(在双括号中使用)

    (("a" \> "b"))

  • >=

    大于等于(在双括号中使用)

    (("a" \>= "b"))

字符串比较

  • =

    等于

    if [ "a" = "b" ]

  • ==

    等于

    if [ "a" == "b" ]

    = 等价。

  • !=

    不等号

    if [ "a" != "b" ]

    这个操作符将在 [[ ... ]] 结构中使用模式匹配。

  • <

    小于, 按照 ASCII 字符进行排序

    if [[ "a" \< "b" ]]

    if [ "a" **\\\<** "b" ]

    注意 "< " 使用在 [ ] 结构中的时候需要被转义。

  • >

    大于, 按照 ASCII 字符进行排序

    if [[ "a" \> "b" ]]

    if [ "a" \\\> "b" ]

    注意 "> " 使用在 [ ] 结构中的时候需要被转义。

  • -z

    字符串为 "null" , 意思就是字符串长度为零

  • -n

    字符串不为 "null" 。

compound comparison

  • -a

    逻辑与
    exp1 -a exp2 如果表达式 exp1 和 exp2 都为真的话, 那么结果为真。

  • -o

    逻辑或
    exp1 -o exp2 如果表达式 exp1 和 exp2 中至少有一个为真的话, 那么结果为真。

4、数字常量

shell 脚本在默认情况下都是把数字作为 10 进制数来处理, 除非这个数字采用了特殊的标记或者前缀。

如果数字以 0 开头的话那么就是 8 进制数。 如果数字以 0x 开头的话那么就是 16 进制数。 如果数字中间嵌入了 # 的话, 那么就被认为是 BASE#NUMBER 形式的标记法(有范围和符号限制)。

bash 复制代码
# 其他进制: BASE#NUMBER
# BASE 的范围在 2 到 64 之间. 
# NUMBER 的值必须使用 BASE 范围内的符号来表示, 具体看下边的示例.

let "bin = 2#111100111001101"
echo "binary number = $bin" # 31181

let "b32 = 32#77"
echo "base-32 number = $b32" # 231

5、变量重游

(1) 内部变量

内建变量

这些变量将会影响 bash 脚本的行为。

  • $BASH
    Bash 的二进制程序文件的路径
bash 复制代码
$ echo $BASH
/bin/bash
  • $BASH_ENV

    这个环境变量会指向一个 Bash 的启动文件, 当一个脚本被调用的时候, 这个启动文件将会被读取。

  • $BASH_SUBSHELL

    这个变量用来提示子 shell 的层次。 这是一个 Bash 的新特性, 直到版本 3 的 Bash 才被引入近来。

  • $BASH_VERSINFO[n]

    这是一个含有 6 个元素的数组, 它包含了所安装的 Bash 的版本信息。 这与下边的 $BASH_VERSION 很相像, 但是这个更加详细一些。

bash 复制代码
# Bash version info:
for n in 0 1 2 3 4 5; do
    echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done
# BASH_VERSINFO[0] = 3 # 主版本号
# BASH_VERSINFO[1] = 00 # 次版本号。 
# BASH_VERSINFO[2] = 14 # 补丁次数。 
# BASH_VERSINFO[3] = 1 # 编译版本。 
# BASH_VERSINFO[4] = release # 发行状态。 
# BASH_VERSINFO[5] = i386-redhat-linux-gnu  # 结构体系
                                            # (与变量$MACHTYPE相同)。
  • $BASH_VERSION
    安装在系统上的 Bash 版本号
bash 复制代码
bash$ echo $BASH_VERSION
3.00.14(1)-release
 
tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.
  • $DIRSTACK

    在目录栈中最顶端的值。 (将会受到 pushd 和 popd 的影响)

    这个内建变量与 dirs 命令相符, 但是 dirs 命令会显示目录栈的整个内容。

  • $EDITOR

    脚本所调用的默认编辑器, 通常情况下是 vi 或者是 emacs 。

  • $EUID

    "有效" 用户 ID 。

    不管当前用户被假定成什么用户, 这个数都用来表示当前用户的标识号, 也可能使用 su 命令来达到假定的目的。
    EUID** 并不一定与 **UID 相同。

  • $FUNCNAME

    当前函数的名字。(数组形式 $FUNCNAME[0] )

  • $GLOBIGNORE

    一个文件名的模式匹配列表,如果在通配(globbing)中匹配到的文件包含有这个列表中的某个文件, 那么这个文件将被从匹配到的结果中去掉。

  • $GROUPS

    目前用户所属的组。

    这是一个当前用户的组 id 列表(数组), 与记录在 /etc/passwd 文件中的内容一样。

bash 复制代码
root# echo $GROUPS
0
root# echo ${GROUPS[1]}
1
root# echo ${GROUPS[5]}
6
  • $HOME

    用户的 home 目录, 一般是 /home/username

  • **HOSTNAME** **hostname** 放在一个初始化脚本中, 在系统启动的时候分配一个系统名字。 然而, gethostname() 函数可以用来设置这个 Bash 内部变量 HOSTNAME 。

  • $HOSTTYPE

    主机类型

    就像 $MACHTYPE , 用来识别系统硬件。

bash 复制代码
bash$ echo $HOSTTYPE
i686
  • $IFS

    内部域分隔符

    这个变量用来决定 Bash 在解释字符串时如何识别域, 或者单词边界。
    IFS** 默认为 **空白** (空格, 制表符,和换行符), 但这是可以修改的, 比如, 在分析逗号分隔的数据文件时, 就可以设置为逗号。 注意 ** * 使用的是保存在 $IFS 中的第一个字符。

  • $IGNOREEOF

    忽略 EOF : 告诉 shell 在 log out 之前要忽略多少文件结束符(control-D)。

  • $LC_COLLATE

    常在 .bashrc/etc/profile 中设置, 这个变量用来控制文件名扩展和模式匹配的展开顺序。 如果 $LC_COLLATE 设置得不正确的话, LC_COLLATE 会在文件名匹配(filename globbing)中产生不可预料的结果。

  • $LC_CTYPE

    这个内部变量用来控制通配(globbing)和模式匹配中的字符串解释。

  • $LINENO

    这个变量用来记录自身在脚本中所在的行号。 这个变量只有在脚本使用这个变量的时候才有意义,并且这个变量一般用于调试目的。

  • $MACHTYPE

    机器类型。标识系统的硬件。

bash 复制代码
bash$ echo $MACHTYPE
i686
  • $OLDPWD

    之前的工作目录("OLD-print-working-directory", 就是之前你所在的目录)

  • $OSTYPE

    操作系统类型

  • $PATH

    可执行文件的搜索路径, 一般为 /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, 等等。

  • $PIPESTATUS

    这个数组变量将保存最后一个运行的前台管道的退出状态码。 相当有趣的是, 这个退出状态码和最后一个命令运行的退出状态码并不一定相同。

  • $PPID

    进程的 $PPID 就是这个进程的父进程的进程 ID(pid)。

  • $PROMPT_COMMAND

    这个变量保存了在主提示符 $PS1 显示之前需要执行的命令。

  • $PS1

    这是主提示符, 可以在命令行中见到它。

  • $PS2

    第二提示符, 当你需要额外输入的时候, 你就会看到它。 默认显示 ">" 。

  • $PS3

    第三提示符, 它在一个 select 循环中显示 。

  • $PS4

    第四提示符, 当你使用 -x 选项来调用脚本时, 这个提示符会出现在每行输出的开头。 默认显示 "+" 。

  • $PWD

    工作目录(你当前所在的目录)

  • $REPLY

    当没有参数变量提供给 read 命令的时候, 这个变量会作为默认变量提供给 read 命令。 也可以用于 select 菜单, 但是只提供所选择变量的编号, 而不是变量本身的值。

  • $SECONDS

    这个脚本已经运行的时间(以秒为单位)。

  • $SHELLOPTS

    shell 中已经激活的选项的列表, 这是一个只读变量。

bash 复制代码
bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactivecomments:emacs
  • $SHLVL

    Shell 级别, 就是 Bash 被嵌套的深度。 如果是在命令行中, 那么 SHLVL 为 1, 如果在脚本中那么 SHLVL 为 2 。

  • $TMOUT

    如果 $TMOUT 环境变量被设置为非零值 time 的话, 那么经过 time 秒后, shell 提示符将会超时。 这将会导致登出(logout)。

  • $UID

    用户 ID 号。当前用户的用户标识号, 记录在 /etc/passwd 文件中

位置参数

  • 0, 1, $2 , 等等。

    位置参数, 从命令行传递到脚本, 或者传递给函数, 或者 set 给变量

  • $#

    命令行参数或者位置参数的个数

  • $ *

    所有的位置参数都被看作为一个单词。

    "$*" 必须被引用起来。

  • $@

    与 $* 相同, 但是每个参数都是一个独立的引用字符串, 这就意味着, 参数是被完整传递的, 并没有被解释或扩展。 这也意味着, 参数列表中每个参数都被看作为单独的单词。

    "$@" 应该被引用起来。

其他的特殊参数

  • $-

    传递给脚本的标记(使用 set 命令)。

  • $!

    运行在后台的最后一个作业的 PID (进程 ID

  • $_

    这个变量保存之前执行的命令的最后一个参数的值

  • $?

    命令, 函数, 或者是脚本本身的退出状态码

  • $$

    脚本自身的进程 ID$$ 变量在脚本中经常用来构造 "唯一的" 临时文件名

(2) 操作字符串

Bash 所支持的字符串操作的数量多的令人惊讶。 但是不幸的是, 这些工具缺乏统一的标准。 一些是参数替换的子集, 而另外一些则受到 UNIX expr 命令的影响。 这就导致了命令语法的不一致, 还会引起冗余的功能, 但是这些并没有引起混乱。

bash 复制代码
expr --help
用法:expr 表达式
 或:expr 选项

      --help        显示此帮助信息并退出
      --version     显示版本信息并退出

将 <表达式> 的值打印到标准输出。以下运算符按优先级从低到高排列,不同
优先级之间以空行隔开。<表达式> 可以是:

  参数1 | 参数2     若 <参数1> 的值不为 0 或 null,则返回 <参数1>,
                      否则返回 <参数2>

  参数1 & 参数2     若两边的值都不为 0 或 null,则返回 <参数1>,否则返回 0

  参数1 < 参数2     <参数1> 小于 <参数2>
  参数1 <= 参数2    <参数1> 小于或等于 <参数2>
  参数1 = 参数2     <参数1> 等于 <参数2>
  参数1 != 参数2    <参数1> 不等于 <参数2>
  参数1 >= 参数2    <参数1> 大于或等于 <参数2>
  参数1 > 参数2     <参数1> 大于 <参数2>

  参数1 + 参数2     计算 <参数1> 加 <参数2> 的和
  参数1 - 参数2     计算 <参数1> 减 <参数2> 的差

  参数1 * 参数2     计算 <参数1> 乘以 <参数2> 的积
  参数1 / 参数2     计算 <参数1> 除以 <参数2> 的商
  参数1 % 参数2     计算 <参数1> 除以 <参数2> 的余数

  字符串 : 正则     在 <字符串> 起始处进行 <正则> 的模式匹配

  match 字符串 正则          等于 "字符串 : 正则"
  substr 字符串 位置 长度    求 <字符串> 的子串,<位置> 从 1 开始数
  index 字符串 字符          在 <字符串> 中搜索 <字符> 中任何一个,返回其索引,
                               如未找到则返回 0
  length 字符串              <字符串> 的长度
  + 记号                     将 <记号> 解释为字符串,即使它是一个关键字,
                               例如 "match",或者运算符,例如 "/"

  ( 表达式 )                 <表达式> 的值

请注意,由于 shell 这一层的存在,可能有许多运算符需要转义或者加引号。
如果两个 <参数> 都是数字,比较运算符就是算术比较,否则就是字典序比较。
模式匹配会返回 \( 和 \) 之间的字符串匹配的字符串,若没有匹配则返回 null;
如果未使用 \( 和 \),则会返回匹配的字符的个数,若没有匹配则返回 0。

若 <表达式> 的值既不是 null 也不是 0,则退出状态为 0;若 <表达式> 的值为 null
或者 0,则退出状态为 1;如果 <表达式> 的句法无效,则退出状态为 2;如果有错误
发生,则退出状态为 3。

GNU coreutils 在线帮助:<https://www.gnu.org/software/coreutils/>
请向 <http://translationproject.org/team/zh_CN.html> 报告任何翻译错误
完整文档 <https://www.gnu.org/software/coreutils/expr>
或者在本地使用:info '(coreutils) expr invocation'

字符串长度

语法:

bash 复制代码
${#string}
expr length $string
expr "$string" : '.*'

示例:

bash 复制代码
stringZ=abcABC123ABCabc

echo ${#stringZ}               # 15
echo $(expr length $stringZ)   # 15
echo $(expr "$stringZ" : '.*') # 15

${#var}

字符串长度 (变量var得字符个数) 。 对于 array 来说, {#array} 表示的是数组中第一个元素的长度。

例外情况:

  • {#\*} 和 {#@} 表示位置参数的个数。
  • 对于数组来说, {#array\[\*\]} 和 {#array[@]} 表示数组中元素的个数。

匹配字符串开头的子串长度

语法:

bash 复制代码
expr match "$string" '$substring'
# $substring 是一个正则表达式.
expr "$string" : '$substring'
# $substring是一个正则表达式.

示例:

bash 复制代码
stringZ=abcABC123ABCabc
#       |------|

echo $(expr match "$stringZ" 'abc[A-Z]*.2') # 8
echo $(expr "$stringZ" : 'abc[A-Z]*.2')     # 8

索引

语法:

bash 复制代码
expr index $string $substring
# 在字符串 $string 中所匹配到的 $substring 第一次所出现的位置

示例:

bash 复制代码
stringZ=abcABC123ABCabc

echo $(expr index "$stringZ" C12)   # 6 
                                    # C 字符的位置
echo $(expr index "$stringZ" 1c)    # 3
# 'c' (in #3 position) matches before '1'.

提取子串

语法:

bash 复制代码
${string:position}
# 在 $string 中从位置 $position 开始提取子串.
# 如果 $string 是 "*" 或者 "@" , 那么将会提取从位置 $position 开始的位置参数.

${string:position:length}
# 在 $string 中从位置 $position 开始提取 $length 长度的子串.

示例:

bash 复制代码
stringZ=abcABC123ABCabc
#       0123456789.....
#       0-based indexing.

echo ${stringZ:0}   # abcABC123ABCabc
echo ${stringZ:1}   # bcABC123ABCabc
echo ${stringZ:7}   # 23ABCabc
echo ${stringZ:7:3} # 23A
                    # 提取子串长度为3.

# 能不能从字符串的右边(也就是结尾)部分开始提取子串?
echo ${stringZ:-4} # abcABC123ABCabc
# 默认是提取整个字符串, 就象${parameter:-default}一样. 
# 然而 . . . 

echo ${stringZ:(-4)} # Cabc
echo ${stringZ: -4}  # Cabc
# 这样, 它就可以工作了. 
# 使用圆括号或者添加一个空格可以"转义"这个位置参数.

如果 string** 参数是 `"*"` 或 `"@"` , 那么将会从 **position 位置开始提取 length** 个位置参数, 但是由于可能没有 **length 个位置参数了,那么就有几个位置参数就提取几个位置参数。

bash 复制代码
echo ${*:2} # 打印出第2个和后边所有的位置参数.
echo ${@:2} # 同上.

echo ${*:2:3} # 从第2个开始, 连续打印3个位置参数.

#./t.sh 1 2 3 4 5 6
#2 3 4 5 6
#2 3 4 5 6
#2 3 4

语法:

bash 复制代码
expr substr $string $position $length
# 在 $string 中从 $position 开始提取 $length 长度的子串.

expr match "$string" '\($substring\)'
# 从 $string 的开始位置提取 $substring, $substring 是正则表达式.


expr "$string" : '\($substring\)'
# 从 $string 的开始位置提取 $substring , $substring 是正则表达式.

示例:

bash 复制代码
stringZ=abcABC123ABCabc
# =======
echo $(expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)') # abcABC1
echo $(expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)')     # abcABC1
echo $(expr "$stringZ" : '\(.......\)')                 # abcABC1
# 上边的每个echo都打印出相同的结果.

语法:

bash 复制代码
expr match "$string" '.*\($substring\)'
# 从 $string 的结尾提取$substring, $substring 是正则表达式.
expr "$string" : '.*\($substring\)'
# 从 $string 的结尾提取 $substring, $substring 是正则表达式.

示例:

bash 复制代码
stringZ=abcABC123ABCabc
# ======

echo $(expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)') # ABCabc
echo $(expr "$stringZ" : '.*\(......\)')                    # ABCabc

子串削除

语法:

bash 复制代码
${string#substring}
# 从 $string 的开头位置截掉最短匹配的 $substring .
${string##substring}
# 从 $string 的开头位置截掉最长匹配的 $substring .

示例:

bash 复制代码
stringZ=abcABC123ABCabc 
#       |----|
#       |----------|

echo ${stringZ#a*C} # 123ABCabc
# 截掉'a'到'C'之间最短的匹配字符串.

echo ${stringZ##a*C} # abc
# 截掉'a'到'C'之间最长的匹配字符串.

语法:

bash 复制代码
${string%substring}
# 从 $string 的结尾位置截掉最短匹配的 $substring .
${string%%substring}
# 从 $string 的结尾位置截掉最长匹配的 $substring.

示例:

bash 复制代码
stringZ=abcABC123ABCabc
#                     ||
#         |------------|

echo ${stringZ%b*c} # abcABC123ABCa
# 从$stringZ的结尾位置截掉'b'到'c'之间最短的匹配. 

echo ${stringZ%%b*c} # a
# 从$stringZ的结尾位置截掉'b'到'c'之间最长的匹配.

子串替换

语法:

bash 复制代码
${string/substring/replacement}
# 使用 $replacement 来替换第一个匹配的 $substring .

${string//substring/replacement}
# 使用 $replacement 来替换所有匹配的 $substring .

示例:

bash 复制代码
stringZ=abcABC123ABCabc
echo ${stringZ/abc/xyz}     # xyzABC123ABCabc
                            # 使用'xyz'来替换第一个匹配的'abc'.

echo ${stringZ//abc/xyz}    # xyzABC123ABCxyz
                            # 用'xyz'来替换所有匹配的'abc'.

语法:

bash 复制代码
${string/#substring/replacement}
# 如果 $substring 匹配 $string 的开头部分, 那么就用 $replacement 来替换 $substring .

${string/%substring/replacement}
# 如果 $substring 匹配 $string 的结尾部分, 那么就用 $replacement 来替换 $substring.

示例:

bash 复制代码
stringZ=abcABC123ABCabc

echo ${stringZ/#abc/XYZ}    # XYZABC123ABCabc
                            # 用'XYZ'替换开头的'abc'.

echo ${stringZ/%abc/XYZ}    # abcABC123ABCXYZ
                            # 用'XYZ'替换结尾的'abc'.

(3) 参数替换

处理和(或)扩展变量

  • ${parameter}

    与 $parameter 相同, 也就是变量 parameter 的值。

  • {parameter-default}, {parameter:-default}

    ${parameter-default} -- 如果变量 parameter 没被声明, 那么就使用默认值。

    ${parameter:-default} -- 如果变量 parameter 没被设置, 那么就使用默认值。

  • {parameter=default}, {parameter:=default}

    ${parameter=default} -- 如果变量 parameter 没声明, 那么就把它的值设为 default。

    ${parameter:=default} -- 如果变量 parameter 没设置, 那么就把它的值设为 default。

    这两种形式基本上是一样的。 只有在变量 $parameter 被声明并且被设置为 null 值的时候, 才会引起这两种形式的不同。

  • {parameter+alt_value}, {parameter:+alt_value}

    ${parameter+alt_value} -- 如果变量 parameter 被声明了, 那么就使用 alt_value , 否则就使用 null 字符串。

    ${parameter:+alt_value} -- 如果变量 parameter 被设置了, 那么就使用 alt_value , 否则就使用 null 字符串。

    这两种形式绝大多数情况下都一样。 只有在 parameter 被声明并且设置为 null 值的时候, 多出来的这个: 才会引起这两种形式的不同。

  • {parameter?err_msg}, {parameter:?err_msg}

    ${parameter?err_msg} -- 如果 parameter 已经被声明, 那么就使用设置的值, 否则打印 err_msg 错误消息。

    ${parameter:?err_msg} -- 如果 parameter 已经被设置, 那么就使用设置的值, 否则打印 err_msg 错误消息。

    这两种形式绝大多数情况都是一样的。 和上边所讲的情况一样, 只有在 parameter 被声明并设置为 null 值的时候, 多出来的:才会引起这两种形式的不同。

(4)指定变量的类型: 使用 declare 或者 typeset

declare 或者 typeset 内建命令(这两个命令是完全一样的)允许指定变量的具体类型。 在某些编程语言中, 这是指定变量类型的一种很弱的形式。 declare 命令是从 Bash 2.0 之后才被引入的命令。 typeset 也可以用在 ksh 的脚本中。

declare/typeset 选项

  • -r 只读
bash 复制代码
declare -r var1

(declare -r var1 与 readonly var1 是完全一样的)

这和 C 语言中的 const 关键字一样, 都用来指定变量为只读。 如果你尝试修改一个只读变量的值,那么会产生错误信息。

  • -i 整型
bash 复制代码
declare -i number
# 脚本将会把变量"number"按照整型进行处理。

number=3
echo "Number = $number" # Number = 3

number=three
echo "Number = $number" # Number = 0
# 脚本尝试把字符串"three"作为整数来求值(译者注: 当然会失败, 所以出现值为0)。

如果把一个变量指定为整型的话, 那么即使没有 expr 或者 let 命令, 也允许使用特定的算术运算。

bash 复制代码
n=6/3
echo "n = $n" # n = 6/3

declare -i n
n=6/3
echo "n = $n" # n = 2
  • -a 数组
bash 复制代码
declare -a indices

变量 indices 将被视为数组。

  • -f 函数
bash 复制代码
declare -f

如果在脚本中使用declare -f, 而不加任何参数的话, 那么将会列出这个脚本之前定义的所有函数。

bash 复制代码
declare -f function_name

如果在脚本中使用declare -f function_name这种形式的话, 将只会列出这个函数的名字。

  • -x export
bash 复制代码
declare -x var3

这句将会声明一个变量, 并作为这个脚本的环境变量被导出。

  • -x var=$value
bash 复制代码
declare -x var3=373

declare 命令允许在声明变量类型的同时给变量赋值。

示例:

bash 复制代码
#!/bin/bash

func1() {
    echo This is a function.
}

declare -f # 列出前面定义的所有函数。
echo

declare -i var1 # var1是个整型变量。
var1=2367
echo "var1 declared as $var1"
var1=var1+1 # 整型变量的声明并不需要使用'let'命令。
echo "var1 incremented by 1 is $var1."
# 尝试修改一个已经声明为整型变量的值。
echo "Attempting to change var1 to floating point value, 2367.1."
var1=2367.1 # 产生错误信息, 并且变量并没有被修改。
echo "var1 is still $var1"

echo

declare -r var2=13.36   # 'declare'允许设置变量的属性,
                        #+ 同时给变量赋值。
echo "var2 declared as $var2" # 试图修改只读变量的值。
var2=13.37                    # 产生错误消息, 并且从脚本退出。

echo "var2 is still $var2" # 将不会执行到这行。

exit 0 # 脚本也不会从此处退出。

(5)变量的间接引用

假设一个变量的值是第二个变量的名字。 那么我们如何从第一个变量中取得第二个变量的值呢? 比如,如果 a=letter_of_alphabet 并且 letter_of_alphabet=z , 那么我们能够通过引用变量 a 来获得 z 么? 这确实是可以做到的, 它被称为间接引用。 它使用 eval var1=\$$a 这种不平常的形式。

bash 复制代码
#!/bin/bash

# ind-ref.sh: 间接变量引用. 
# 访问一个以另一个变量内容作为名字的变量的值.(译者注: 怎么译都不顺)

a=letter_of_alphabet # 变量"a"的值是另一个变量的名字.
letter_of_alphabet=z

echo

# 直接引用.
echo "a = $a" # a = letter_of_alphabet

# 间接引用.
eval a=\$$a
echo "Now a = $a" # 现在 a = z

echo

# 现在, 让我们试试修改第二个引用的值.

t=table_cell_3
table_cell_3=24
echo "\"table_cell_3\" = $table_cell_3" # "table_cell_3" = 24
echo -n "dereferenced \"t\" = "
eval echo \$$t # 解引用 "t" = 24
# 在这个简单的例子中, 下面的表达式也能正常工作么(为什么?).
# eval t=\$$t; echo "\"t\" = $t"

echo

t=table_cell_3
NEW_VAL=387
table_cell_3=$NEW_VAL
echo "Changing value of \"table_cell_3\" to $NEW_VAL."
echo "\"table_cell_3\" now $table_cell_3"
echo -n "dereferenced \"t\" now "
eval echo \$$t
# "eval" 带有两个参数 "echo" 和 "\$$t" (与$table_cell_3等价)

echo

# (感谢, Stephane Chazelas, 澄清了上边语句的行为.)

# 另一个方法是使用${!t}符号, 见"Bash, 版本2"小节的讨论. 45 # 也请参考 ex78.sh.

exit 0

变量的间接引用到底有什么应用价值? 它给 Bash 添加了一种类似于 C 语言指针的功能, 比如, 在表格查找中的用法。

(6)$RANDOM: 产生随机整数

$RANDOM 是 Bash 的内部函数 (并不是常量), 这个函数将返回一个伪随机整数, 范围在 0 - 32767 之间。 它不应该被用来产生密匙。

bash 复制代码
#!/bin/bash
# 每次调用$RANDOM都会返回不同的随机整数. 
# 一般范围为: 0 - 32767 (有符号的 16-bit整数).

MAXCOUNT=10
count=1

echo
echo "$MAXCOUNT random numbers:"
echo "-----------------"

while [ "$count" -le $MAXCOUNT ] # 产生10 ($MAXCOUNT)个随机整数. 
do
    number=$RANDOM
    echo $number
    let "count += 1" # 增加计数. 
done
echo "-----------------"

# 如果你需要在特定范围内产生随机整数, 那么使用'modulo'(模)操作.(译者注: 事实上, 这不是一个非常 好的办法. 理由见man 3 rand)
# 取模操作会返回除法的余数. 
RANGE=500

echo

number=$RANDOM
let "number %= $RANGE"
#           ^^
echo "Random number less than $RANGE --- $number"

echo


# 如果你需要产生一个大于某个下限的随机整数. 
#+ 那么建立一个test循环来丢弃所有小于此下限值的整数. 

FLOOR=200

number=0 #初始化
while [ "$number" -le $FLOOR ]
do
    number=$RANDOM
done
echo "Random number greater than $FLOOR --- $number"
echo

# 让我们对上边的循环尝试一个小改动, 如下: 
# let "number = $RANDOM + $FLOOR"
# 这将不再需要那个while循环, 并且能够运行的更快. 
# 但是, 这可能会产生一个问题, 思考一下是什么问题?
# 结合上边两个例子, 来在指定的上下限之间来产生随机数. 
number=0 #initialize
while [ "$number" -le $FLOOR ]
do
    number=$RANDOM
    let "number %= $RANGE" # 让$number依比例落在$RANGE的范围内. 
done
echo "Random number between $FLOOR and $RANGE --- $number"
echo

# 产生二元值, 就是, "true" 或 "false" 两个值. 
BINARY=2
T=1
number=$RANDOM
let "number %= $BINARY"
# 注意 let "number >>= 14" 将会给出一个更好的随机分配. #(译者注: 正如man页中提到的, 更 高位的随机分布更加平均)
#+ (右移14位将把所有的位全部清空, 除了第15位, 因为有符号, 第16位是符号位). #取模操作使用低位来 产生随机数会相对不平均)
if [ "$number" -eq $T ]
then
    echo "TRUE"
else
    echo "FALSE"
fi 

echo

# 抛骰子. 
SPOTS=6 # 模 6 给出的范围是 0 - 5.
# 加 1 会得到期望的范围
# 感谢, Paulo Marcel Coelho Aragao, 对此进行的简化. 
die1=0
die2=0
# 是否让SPOTS=7会比加1更好呢? 解释行或者不行的原因?
# 每次抛骰子, 都会给出均等的机会. 
let "die1 = $RANDOM % $SPOTS +1" # 抛第一次. 
let "die2 = $RANDOM % $SPOTS +1" # 抛第二次. 
# 上边的算术操作中, 哪个具有更高的优先级呢 -- 
#+ 模(%) 还是加法操作(+)?
let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echo
exit

(7)双圆括号结构

let 命令很相似, ((...)) 结构允许算术扩展和赋值。 举个简单的例子, a=$(( 5 + 3 )), 将把变量 "a" 设为 "5 + 3" , 或者 8 。 然而, 双圆括号结构也被认为是在 Bash 中使用 C 语言风格变量操作的一种处理机制。

bash 复制代码
#!/bin/bash
# 使用((...))结构操作一个变量, C语言风格的变量操作.

echo

((a = 23)) # C语言风格的变量赋值, "="两边允许有空格.
echo "a (initial value) = $a"

((a++)) # C语言风格的后置自加.
echo "a (after a++) = $a"

((a--)) # C语言风格的后置自减.
echo "a (after a--) = $a"

((++a)) # C语言风格的前置自加.
echo "a (after ++a) = $a"

((--a)) # C语言风格的前置自减.
echo "a (after --a) = $a"

echo

########################################################
# 注意: 就像在C语言中一样, 前置或后置自减操作
#+ 会产生一些不同的副作用.

n=1
let --n && echo "True" || echo "False" # False
n=1
let n-- && echo "True" || echo "False" # True

# 感谢, Jeroen Domburg.
########################################################

echo

((t = a < 45 ? 7 : 11)) # C语言风格的三元操作.
echo "If a < 45, then t = 7, else t = 11."
echo "t = $t " # Yes!

echo

# ------------
# 复活节彩蛋!
# ------------
# Chet Ramey显然偷偷摸摸的将一些未公开的C语言风格的结构
#+ 引入到了Bash中 (事实上是从ksh中引入的, 这更接近些).
# 在Bash的文档中, Ramey将((...))称为shell算术运算,
#+ 但是它所能做的远远不止于此.
# 不好意思, Chet, 现在秘密被公开了.

# 你也可以参考一些 "for" 和 "while" 循环中使用((...))结构的例子.

# 这些只能够在Bash 2.04或更高版本的Bash上才能运行.

exit 0

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习