Skip to content

Linux Shell

Shell 基础

什么是 Shell

Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核。由于 Linux 是一个开源的操作系统,因此不同组织机构开发了不同的 shell,常见的 shell 有 sh、bash、csh、tcsh、ash 等。

  • sh 全称是 Bourne shell,是 UNIX 上的标准 shell,很多 UNIX 版本都配有 sh。
  • csh 的语法有点类似 C 语言,所以才得名为 C shell。
  • tcsh 是 csh 的增强版,加入了命令补全功能。
  • ash 全称是 Almquist shell,特点是快而轻巧,适合运行于低内存环境。
  • bash 全称是 Bourne Again Shell,是目前 Linux 的默认 shell,向后兼容 sh(即针对 sh 写的代码可以直接在 bash 中运行),后续章节基于 bash 编写

当前 Linux 系统可用的 shell 记录在 /etc/shells 文件中,可用 cat /etc/shells 命令查看。在现代 Linux 上,sh 已经被 bash 替代,/bin/sh 往往是指向 /bin/bash 的软链接。用 echo $SHELL 命令可查看当前 Linux 默认 shell。

$ echo $SHELL
/bin/bash

如上代码块中,第一行开头的 $命令提示符,并非命令的一部分,$后才是运行的命令,非 $ 开始的行为命令的输出

shell 自带的命令称为内建命令,由于 shell 启动时,内建命令的代码就加载到内存了,所以内建命令的执行速度很快,但内建命令不宜过多,过多会导致 shell 程序本身体积过大。而非内建命令称为外部命令。shell 在获取一个命令后,会先检测该命令是不是内建命令,如果是,就执行,如果不是,就会在 PATH 环境变量中的路径中寻找外部命令,如果在这些路径中找到了同名文件,就执行这个文件,若找不到,则报错。

Shell 脚本

什么是 Shell 脚本

我们可以在命令行中输入 shell 命令,输入一个执行一个,也可以将多个 shell 命令放到一个文件中,使用这个文件来一次性执行多个 shell 命令,这个文件便是 shell 脚本。shell 脚本的扩展名一般为 .sh(扩展名是什么并不影响脚本的执行,只是为了让用户一目了然这个文件的类型)。如下是一个简单的 shell 脚本示例。

打开文本编辑器,新建一个 hello_world.sh 文件,在内部输入

hello_world.sh
#!/bin/bash
echo "Hello World!"   ## 输出 Hello World!

这就是一个简单的 shell 脚本,其中:

  • #! 是一个约定标记,以告诉系统这个脚本需要用什么解释器来执行,若改写为 #!/bin/bash --login 则会告诉系统要用登录式 bash 来执行脚本
  • # 开头的是 shell 的注释(#! 开头的除外)

运行 Shell 脚本

运行 shell 脚本有多种方式:

  1. 将脚本作为程序运行

    这种方式执行脚本,第一行的解释器说明 #!/bin/bash 一定要写对。

    首先使用 chmod +x 命令给脚本文件加上可执行权限,然后运行 path_of_shell_scriptpath_of_shell_script 可以是相对路径也可以是绝对路径,若使用相对路径,可用 ./ 表示当前目录。如下示例执行上文新建的 hello_world.sh 脚本(当前路径已在该脚本所在目录下)。

    $ chmod +x hello_world.sh
    $ ./hello_world.sh
    Hello World!
    

    这种方法执行脚本,不能只给出脚本文件的文件名,否则系统就只会在 PATH 环境变量指定的路径中寻找,找不到便会执行失败。

  2. 将脚本作为参数传递给 shell 解释器

    这种方式,脚本传给哪个 shell 解释器,便用哪种 shell 解释器执行命令,脚本开始的 #!/bin/bash 指定是无用的。

    $ bash hello_world.sh
    Hello World!
    

    bash 是一个外部命令,由于 /bin 目录已在 PATH 变量中,所以可以直接执行 bash 来运行 /bin/bash

  3. 使用 source 命令运行

    source 是一个内建命令,该命令会读取脚本文件中的代码,并在当前的 shell 中依次执行所有语句,该命令可简写为 .

    $ source hello_world.sh
    Hello World!
    $ . hello_world.sh
    Hello World!
    

    这种方法与前两种方法不同的是,前两种方式会新打开一个新的 shell 进程来执行脚本,而使用 source. 命令会在当前 shell 进程中执行命令,可以在脚本中查看 PID 来验证(Linux 中每个进程都有唯一的进程标识号,称为 PID,读取 BASHPID 变量便可获取当前 bash 进程的 PID)。

    echo_PID.sh
    #!/bin/bash
    echo $BASHPID   ## 输出bash进程的PID
    
    $ echo $BASHPID
    1704
    $ ./echo_PID.sh
    1845
    $ bash echo_PID.sh
    1853
    $ source echo_PID.sh  # source 执行脚本的bash环境与当前bash一样(bash PID 一样)
    1704
    $ . echo_PID.sh       # . 执行脚本的bash环境与当前bash一样(bash PID 一样)
    1704
    

Shell 启动方式

我们可以直接使用 shell,也可以输入用户名密码后再使用 shell,前者叫做非登陆式,后者叫做登录式;我们可以在 shell 中依次输入命令,然后及时查看每个命令的运行结果,也可以运行一个 shell 脚本,让所有命令一次性执行,前者叫做交互式,后者叫做非交互式

要判断 shell 是否是交互式,可以

  • 查看变量 -,如果其中包含了字母 i,表明是交互式,否则不是

  • 查看变量 PS1,如果非空,表明是交互式,否则不是

要判断 shell 是否是登录式,可以

  • 执行 shopt login_shell,值为 on 表明是登录式,若为 off 则为非登录式

如下运行结果表明该 shell 是交互式且登录式的

$ echo $-; shopt login_shell
himBHs
login_shell     on

如下是一些常见的 shell 启动方式

  1. 在 Linux 系统中打开 terminal(终端)

  2. ssh 登录远程主机,打开 shell

  3. ssh 远程执行命令,但不登陆

    $ ssh hostname 'echo $-; shopt login_shell'
    hBc
    login_shell     off
    
  4. 执行 bash 命令,默认是非登录式,增加 --login 选项(简写是 -l)变为登录式

  5. 使用 () 包围的组命令或命令替换进入子 shell,子 shell 会继承父 shell 的交互属性与登录属性

Shell 配置文件

shell 在启动时,总需要配置其运行环境,如初始化环境变量,如 PATH 变量,设置命令行提示符等等。这个过程是通过加载一系列的配置文件来完成的,这些配置文件本质上是 shell 脚本文件。

与 bash 有关的配置文件有 /etc/profile~/.bash_profile~/.bash_login~/.profile~/.bashrc/etc/bashrc/etc/profile.d/*.sh 等,不同启动方式,加载的配置文件有所不同。

~ 称为家目录,root 用户的家目录为 /root/,普通用户的家目录为 /home/用户名/

  1. 登录式 Shell

    对于登录式的 shell,会先执行 /etc/profile( 该配置文件还会嵌套加载 /etc/profile.d/*.sh,即 /etc/profile.d/ 目录下所有以 .sh 结尾的文件 ),然后再执行用户家目录里的配置文件:~/.bash_profile~/.bash_login~/.profile,若家目录下的配置文件同时存在,则加载优先级依次降低(这三种配置文件会嵌套加载 ~/.bashrc,而 ~/.bashrc 又会嵌套加载 /etc/bashrc)。

    一个小坑:

    默认的 ~/.bashrc 开头会有如下内容

    case $- in
        *i*) ;;
          *) return;;
    esac
    

    即非交互式执行 ~/.bashrc 会直接返回,所以登录式非交互式 bash,虽然会执行 ~/.bashrc 文件,但刚执行就返回了。

    在非交互式 shell 中无论怎么执行 source ~/.bashrc,其内配置都不会生效,别问我怎么知道的。

  2. 交互式非登录式 shell

    若以非登录式的方式启动交互式 shell,就只会加载 ~/.bashrc

  3. 非交互式非登录式 shell

    大多数情况下,非登录式非交互的 shell 什么配置文件都不加载,如以程序方式(即 运行 shell 脚本 中的第 1 种方式)且非登陆式地执行 shell 脚本时,虽然开启了子 shell,但该子 shell 启动时什么配置文件都不加载。

    但用 ssh 连接远程主机,不登录,只执行命令时(即 shell 启动方式 中的第 3 种方式),虽启动的是非交互式非登录的 shell,但是却会加载 ~/.bashrc

综上,对于普通用户来说,~/.bashrc 才是最重要的 bash 配置文件,因为启动 bash 大多都会加载这个文件。因此我们可以将自己一些个性化的设置放到 ~/.bashrc 中,或让 ~/.bashrc. 命令嵌套加载我们的个性化配置文件。

命令提示符

前文代码块中,若是在 shell 命令行中执行的命令,我总是使用 $ 开头,$ 便是命令提示符,以提示用户在其后输入命令。命令提示符的格式并不是固定不变的,是可以修改的,如下也是一个 shell 命令提示符格式的示意:

[username@host directory]$
[username@host directory]#

其中 username 是用户名,host 是主机名,directory 是用户当前目录,$# 是命令提示符(超级用户 root 为 #,普通用户是 $

若命令不在一行内输入完成,需要换行,这时就能看见第二层命令提示符,第二层命令提示符默认为 >

[myname@host ~]$ echo "
> Hello
> World"

Hello
World

shell 通过 PS1PS2 这两个环境变量控制命令提示符格式,他们分别控制最外层和第二层命令提示符格式。通过 echo 命令能查看这两个环境变量的值

[myname@host ~]$ echo $PS1
[\u@\h \w]\$
[myname@host ~]$ echo $PS2
>

Linux 用 \ 开头的特殊字符来表示命令提示符中包含的要素,如:

  • \u:当前用户的用户名

  • \h:本地主机名

  • \w:当前工作目录完整路径;\W:当前工作目录

  • \$:root 用户显示为 #,普通用户显示为 $

  • \#:当前命令的命令编号

  • \e:ASSIC 转义字符(也可表示为 \033

  • \d:格式为 “Weekday Month Date” 的日期(如:“Tue May 26”)

  • \t:格式为 ”HH:MM:SS“ 的 24 小时制时间;\T:格式为 ”HH:MM:SS” 的 12 小时制时间;

    \A:格式为 “HH:MM” 的 24 小时制时间;\@:格式为 ”HH:MM AM/PM" 的 12 小时制时间

如下是一个修改命令行提示符的例子:

[myname@host ~]$ PS1="[\t]\# \u \w\$ "
[23:28:42]12 myname ~$ echo 'hi'
hi
[23:28:47]13 myname ~$ pwd  # 输出当前目录
/home/myname
[23:28:53]14 myname ~$

还可以在命令提示符中插入变量与指令的输出内容,指令输出用 $(command) 的方式,变量用 $variable ,如下是一个加入指令输出与变量的命令提示符例子,其中命令 date '+%b %d' 会输出 “Month Date” 格式的日期。

[myname@host ~]$ MY_PROMPT="(^_^)"   # 定义一个变量
[myname@host ~]$ PS1="$(date '+%b %d') $MY_PROMPT\$ "
May 15 (^_^)$

命令提示字串也可以使用彩色的文字,可以使用 “\e[FormatCodemContent\e[0m" 的格式将 Content 变换格式,FormatCode 中可用 30~37 及 90~97 设定字体颜色(39 为默认字体颜色),40~47 及 100~107 设定背景颜色(49 为默认背景颜色),数字间用 ; 分隔,下图所示是颜色显示效果(在不同终端上或许会有不同的颜色差别)

若想了解更多关于颜色设置的内容,可查看 Bash tips: Colors and formatting

shell_color

若想将 PS1="[\u@\h \w] \$ "\u@\h 的背景色改为 107 号,字体颜色改为 35 号,然后将 \w 字体颜色设置为 96 号,则 PS1 为 "[\e[107;35m\u@\h\e[0m \e[96m\w\e[0m] \$ "

shell_prompt

PS1 的修改若想在下次启动 shell 后仍然生效,需要放到 shell 配置文件 中,通常放到 ~/.bashrc 中。

Shell 编程

Shell 变量

在 bash shell 中,每个变量的都会以字符串的形式存储,这与大部分编程语言有所不同。如果有必要,也可以使用 shell declare 关键字显示定义变量的类型。

变量的定义可用如下三种形式(variable 为变量名,value 为要赋给变量的值):

variable=value
variable='value'
variable="value"

需要注意的是:

  • shell 变量命名规则和大多数语言一样(由数字、字母、下划线组成,不能用数字开头,并且不能是 shell 的关键字)
  • 特别需要注意的是,= 左右不能有空格
  • value 中含有空白字符,那么必须用引号给括起来。
  • 单引号 '' 和 双引号 "" 是不同的,'' 内字符串会原样赋给 variable,但是若用 "" ,解释器会先解析字符串内的变量和命令,然后将结果插入字符串。

使用 $variable${variable} 的形式来引用变量,其中 {} 是为了帮助解释器识别变量名的边界。

若想设置只读变量(只能读不能修改),使用 readonly 命令:

readonly roVar="some text"
roVar="new text"  ## 会报错,只读变量不能修改

若想删除变量,使用 unset 命令,即 unset variable

变量作用域

按变量作用域来分类,shell 变量可分为:

  • 局部变量:只能在当前函数内部使用
  • 全局变量:可以在当前 shell 中使用
  • 环境变量:不仅可以在当前 shell 中使用,还可以在子进程中使用

shell 在函数中定义的变量默认是全局变量(即默认情况下,函数外部也能访问函数内部定义的变量),要想在函数内部定义局部变量,需要用 local 命令,即 local variable=value。以下 shell 脚本的运行输出为空(若去掉 local,将输出 99)。

local_variable.sh
#!/bin/bash
## 定义函数
function func() {
    local a=99
}
## 调用函数
func
## 输出局部变量 a
echo $a

全局变量作用域是当前 shell 进程,不同 shell 进程间的全局变量是相互独立的。

若用 export 命令将全局变量导出,即 export variable ,那么它将在当前 shell 与子 shell 中都有效,这种变量称为环境变量

当在一个 shell 进程中创建一个新的 shell 进程,旧的 shell 进程称为父 shell 进程,新的称为子 shell 进程,子 shell 会继承父 shell 的所有环境变量(若配置文件中有对环境变量的修改,且子 shell 启动时会加载这个配置文件,那么继承下来的同名环境变量会被覆盖)。环境变量的传递是单向的,只能由父 shell 传向子 shell ,再由子 shell 传给孙 shell 。

若顶层定义环境变量的父进程被关掉,那么定义的环境变量就消失了,所以最好将经常使用的环境变量放入 配置文件 中。

特殊变量

shell 中有一些特殊变量,它们并不遵循普通变量的命名规则(普通变量命名规则:由数字、字母、下划线组成,不能用数字开头):

  • $0:shell 的名字或 shell 脚本的名字
  • $n\(n\ge1\)):传递给脚本或函数的参数,n 是一个数字,表示第几个参数,若 \(n\ge10\) ,则使用 ${n} 的格式
  • $*:传递给脚本或函数的所有参数
  • $@:传递给脚本或函数的所有参数,用 "" 括起来时,与 $* 有所不同
  • $#:传递给脚本或函数的参数个数
  • $?:上一命令退出状态,或函数返回值
  • $$:当前 Shell 进程 ID。

$*$@ 的区别:当没有被双引号 "" 包围时,他们没什么差别,但是当用 "" 包围时,"$*" 将所有的参数作为一个单独的变量,而 "$@" 将各个参数仍然视为分离的变量。

test.sh
#!/bin/bash

func() {
    echo Param 1:  $1
    echo Param 2:  $2
    echo Param 3:  $3
}

func "$*"
echo "---------"
func "$@"
$ ./test.sh aaa "bbb ccc" ddd
Param 1: "aaa bbb ccc ddd"
Param 2: ""
Param 3: ""
---------
Param 1: "aaa"
Param 2: "bbb ccc"
Param 3: "ddd"

Shell 字符串

字符串可以由单引号 '' 包围,也可由双引号 "" 包围,也可以不加引号。

  • 由单引号 '' 包围的字符串:
    • 所有字符都会原样输出
    • 字符串内不能使用单引号,将单引号转义也不行
  • 由双引号 "" 包围的字符串:
    • 变量、命令替换能被解析
    • 字符串内可以出现转义的双引号
  • 没有引号包围的字符串:
    • 变量、命令替换能被解析
    • 字符串内不能出现空格,否则空格后的字符串会被当成其他变量或命令

字符串长度:使用 ${#string_name} 便能获取字符串长度

字符串拼接:将字符串并排放在一起就能完成拼接(中间若有空格,需用双引号包围)

字符串子串

  1. ${string: start:length} 从字符串 start 号字符开始(从左开始由 0 开始的编号),向右截取 length 个字符(省略 length 表示截取到字符串结尾)
  2. ${string: 0-start:length} 截取从字符串倒数第 start 个字符开始向右 length 个字符(省略 length 表示截取到字符串结尾)
  3. ${string#*_} 返回删去以 _ 为结尾的最短前缀后剩下的字符串
  4. ${string##*_} 返回删去以 _ 为结尾的最长前缀后剩下的字符串
  5. ${string%_*} 返回删去以 _ 为开头的最短后缀后剩下的字符串
  6. ${string%%_*} 返回删去以 _ 为开头的最长后缀后剩下的字符串

shell 数组

shell 中,用 () 来表示数组,元素间用空格分隔。bash 只支持一维数组,不支持多维数组。

array_name=(ele1 ele2 ele3)

也可只给定特定元素的赋值

ages=([3]=24 [5]=35 [7]=65)   # 此时数组长度为3

数组操作

  • 元素的访问${array_name[index]}
  • 获取所有元素${array_name[*]}${array_name[@]}
  • 获取所有索引${!array_name[*]}${!array_name[@]}
  • 获取数组长度${#array_name[*]}${#array_name[@]}
  • 修改元素array_name[index]=valueindex 为需要修改的元素的索引)
  • 添加元素:使用 array_name[index]=valueindex 为未使用过的索引),或者 array_name+=(value1 value2)
  • 删除数组unset array_name
  • 删除数组元素unset array_name[index]
  • 数组合并newarr=($arr1[@] $arr2[*])

关联数组

Bash 在版本 4 之后引入关联数组,之前的普通数组只能以数字为索引,但关联数组可以将字符串作为索引(或键 key)。关联数组用带有 -A 选项的 declare 命令创建:

declare -A arr
arr["key1"]=val1
arr+=(["key2"]=val2 ["key3"]=val3)

或者,也可在创建关联数值的同时添加元素

declare -A arr=(["key1"]=val1 ["key2"]=val2)

其使用和普通数组类似

Shell 内建命令

shell 内建命令(builtin command)不需要调用其他的程序,由 shell 直接执行。

可以用 type 命令来确定一个命令是否是内建命令:

$ type echo
echo is a shell builtin
$ type ifconfig
ifconfig is /usr/sbin/ifconfig

而外部命令(external program)需要 shell 加载程序代码然后才能执行,系统变量 PATH 中包含的目录几乎聚集了系统中绝大多数外部命令。执行外部命令,不但会触发磁盘 I/O,还会 fork 出一个单独的进程来执行命令。

bash 中含有的内建命令,可查看 Bash Builtins

  • alias:定义或展示 shell 命令的别名
  • bg:恢复挂起的作业,让其在后台运行
  • bind:定义或展示键盘按键与函数绑定
  • break:从 forwhileselect 循环中退出
  • builtin:运行 shell 内建命令,当存在同名 shell 函数时很有用
  • caller:返回正在运行的子例程的上下文。
  • cd:更改工作目录
  • command
  • compgen
  • complete
  • compopt
  • continue:跳到 forwhileselect 循环中的下一个循环
  • declare:定义、展示变量或为变量声明属性
  • dirs

alias - 给命令创建别名

alias 用来给命令创建一个别名

alias [-p] [name[=value] ...]

不带参数的 alias 命令,或者带 -p 选项的 alias -p 命令,会将所有别名输出

$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'

下面举一个自定义命令别名的例子,你想知道现在的 12 小时制的时间,可以运行命令 date +%r 来查看,但是假如你总是记不住需要用到的格式代码 %r,或者你嫌弃它太长了,那么你可以给它定义一个别名:

$ alias now='date +%r'
$ now
01:23:08 AM

删除别名使用 unalias 内建命令

unalias [-a] [name ... ]

unalias 后参数是某个别名的名称,那就删除特定别名,若是用了 -a 选项,则会删除所有别名。

$ unalias now
$ now

Command 'now' not found, did you mean:

    command 'new' from deb mmh (0.4-2)
    command 'new' from deb nmh (1.7.1-6)
    command 'cow' from deb fl-cow (0.6-4.2)
    command 'nox' from deb nox (2019.5.30-2)
    command 'nop' from deb graphviz (2.42.2-3build2)
    command 'sow' from deb ruby-hoe (3.21.0+dfsg1-1)
    command 'nw' from deb netrw (1.3.2-3)

Try: sudo apt install <deb name>

注:若想别名永久有效,可以把别名写进 Shell 配置文件 中。

echo 输出字符串

echo 会将其后的参数输出,中间用空格分隔,默认最后会添加一个换行符。

test_echo.sh
echo [-neE] [arg ... ]
  • -n 选项表示最后不添加换行符
  • -e 选项表示将会解析以 \ 开头的转义字符
#!/bin/bash

var1="variable_1"
var2="variable_2"

echo "1. many arguments:" $var1 $var2
echo -n "2. a line without newline."
echo "3. This is another command"
echo -e "4. interpretation of the escape charactes:\n\tA. first\n\tB. second"
echo "5. disable the interpretation of these escape charactes:\n\tA. first\n\tB. second"
$ ./test_echo.sh
1. many arguments: variable_1 variable_2
2. a line without newline.3. This is another command
4. interpretation of the escape charactes:
        A. first
        B. second
5. disable the interpretation of these escape charactes:\n\tA. first\n\tB. second

read 读取输入

read 默认从标准输入(键盘)读取数据,若进行了重定向,还可从文件中读取数据。

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars]
    [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ]
  • -r:不对反斜杠字符进行转义
  • -s:不在终端中显示输入字符
  • -a aname:输入数据将赋值给数组,下标从 0 开始。
  • -d delim:指定 delim 为读取的结束字符,默认换行符
  • -n nchars:设定最多读取 nchars 个字符
  • -p prompt:以 prompt 作为提示信息输出。
  • -t timeout:设置超时时间,单位秒。若超时未获得输入,那么 read 返回非 0 退出状态,读取失败
  • -u fd:使用文件描述符 fd 作为输入源。

exit 退出当前进程

exit 用来退出当前 shell 进程,并返回退出状态。退出后,可用 $? 变量查看退出状态。exit 可接受一个 0~255 间的整数作为参数,代表退出状态,若不指定,默认值退出状态为 0。0 表示成功,其他状态都表示失败。

declare 设置变量属性

declare [-/+][aAfFgilrtux] [-p] [name[=value] ...]

- 表示设置属性,+ 表示取消属性

  • -a name:声明为普通数组
  • -A name:声明为关联数组
  • -f [name]:列出函数定义
  • -F [name]:列出函数名称
  • -g name
  • -i name:将变量定义为整数类型
  • -r name[=value]:将变量定义为只读变量
  • -x name[=value]:将变量设置为环境变量,等价于命令 export name[=value]
  • -p [name]:显示变量的属性和值

声明为整数的变量,可进行整数运算,而普通变量会进行字符串拼接

$ m=10; n=30
$ ret=$m+$n
$ echo $ret
10+30
$ declare -i m n ret
$ ret=$m+$n
$ echo ret
40

Shell 数学计算

Shell 与其他编程语言不同,其变量默认情况都为字符串,如果尝试将两个数字或普通变量用算术运算符进行运算,Shell 并不能获得计算结果,而是进行字符串拼接。

$ echo 10+10
10+10
$ a=2
$ echo $a+1
2+1

在 Shell 中要进行数学计算,常用的方法有:(())let$[]exprbcdeclare -i

(()) 命令

(()) 命令只能进行整数运算,其语法格式为

((表达式))
((表达式1,表达式2,表达式3))

要获取 (()) 命令的执行结果,可以使用 $,其执行结果为最后一个表达式的值。

  1. 使用运算结果

    $ a=10
    $ echo $((a+30))
    40
    
  2. 将运算结果赋值给变量:

    $ ((a=10+66))
    $ ((b=a*30))
    $ ((a++, sum=a+b, sub=a-b))
    

    或者

    $ a=$((10+16))
    $ b=$((a*30))
    
  3. 进行逻辑运算

    $ ((a>7 && b==c))
    $ if ((8>7 && 5==5)); then echo true; else echo false; fi
    true
    $ if ((8>7 && 5!=5)); then echo true; else echo false; fi
    false
    

let 命令

let 命令也是只能对整数进行运算

let 表达式
let 表达式1 表达式2 表达式3

(()) 命令不同,let 命令在计算了表达式的值后会将结果丢弃,所以要想保留计算结果,表达式需要为赋值语句

$ let x=3 y=5
$ let x+y
$ let z=x+y
$ echo $z
8

$[]

$[] 也是只能对整数进行运算

$[表达式]

需要注意的是,$[] 不能单独使用,必须使用能够接收运算结果的命令

$ echo $[3*10]
30
$ x=3
$ y=$[x**2+3]
$ echo $y
12
$ $[x+y]
15: command not found

expr 命令

expr 命令不止能进行整数计算,还能结合一些

Shell 命令替换

命令替换是指用命令的输出结果替换掉指定内容,命令替换有两种方式:` commands` (反引号) 和 $(commands)

如将 date 命令的运行结果赋值给变量 now

$ now=`date`
$ echo $now
Tue May 24 12:02:35 CST 2022

这两种变量替换的形式是等价的,但是 $() 支持嵌套,而 `` 不支持;而且由于反引号看起来比较像单引号,有时会造成不必要的困扰,因此在 bash 编程中比较推荐使用 $()