惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
S
Schneier on Security
H
Help Net Security
aimingoo的专栏
aimingoo的专栏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MongoDB | Blog
MongoDB | Blog
云风的 BLOG
云风的 BLOG
H
Hacker News: Front Page
C
Check Point Blog
P
Privacy International News Feed
IT之家
IT之家
爱范儿
爱范儿
AWS News Blog
AWS News Blog
The Hacker News
The Hacker News
Stack Overflow Blog
Stack Overflow Blog
Project Zero
Project Zero
Microsoft Azure Blog
Microsoft Azure Blog
量子位
The Cloudflare Blog
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
I
Intezer
Martin Fowler
Martin Fowler
Scott Helme
Scott Helme
酷 壳 – CoolShell
酷 壳 – CoolShell
A
Arctic Wolf
T
Threat Research - Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Exploit Database - CXSecurity.com
B
Blog
Simon Willison's Weblog
Simon Willison's Weblog
U
Unit 42
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Microsoft Security Blog
Microsoft Security Blog
博客园_首页
J
Java Code Geeks
K
Kaspersky official blog
Webroot Blog
Webroot Blog
C
CERT Recently Published Vulnerability Notes
H
Heimdal Security Blog
G
Google Developers Blog
T
Tor Project blog
W
WeLiveSecurity
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Blog — PlanetScale
Blog — PlanetScale
小众软件
小众软件
有赞技术团队
有赞技术团队

逸思杂陈

家用网络 vlan 单线复用 Linux 平台 intel UHD 6xx 核显 openvino 探索 UI 区域检测的 vibe coding 复盘 mitmproxy 使用 esim 使用相关 阻止 bilibili 网页自动关注 Hexo 版本更新与技术债务 配置 Linux 作为主力操作系统 在 Linux 虚拟机中使用 PyAutoGUI 做自动化 语言的力量 联想笔记本 BIOS 跳过检测强制降级 redroid “设备未获得play保护机制认证” 问题 在 iOS 上访问安卓应用 在 VSCode 中用 Rust 刷LeetCode 跨域的那些事 HomeBrew 与无 root 权限 Linux 环境包管理 给 macOS 词典增加生词本功能 关闭子进程打开的文件描述符 容器内进程优雅退出 Python 循环变量泄露与延迟绑定 MySQL 自定义数据库路径
bash 语法备忘
Jay.Run · 2021-11-28 · via 逸思杂陈

bash 语法作为程序员好像都了解一些,但又缺少体系化学习,需要使用到某些功能时又经常手忙脚乱地查。
本文主要参考阮一峰的bash教程,对bash的知识点进行了梳理。
本文目的是作为bash的语法备忘录、语法速查表。

模式扩展

模式扩展(globbing),类似C语言中的宏展开,我们通常使用的通配符*就是其中之一。

Bash 一共提供八种扩展,前4种为文件扩展,只有文件路径确实存在才会扩展。

  • ~ 波浪线扩展
  • ? 问号扩展
  • * 星号扩展
  • [] 方括号扩展
  • {} 大括号扩展
  • $var 变量扩展
  • $(date) 命令扩展
  • $((1 + 1)) 算术扩展

波浪线扩展

波浪线~会自动扩展成当前用户的主目录。
~user表示扩展成用户user的主目录。如果用户不存在,则波浪号扩展不起作用。

1
2
3
4
5
6
7
8
bash-5.1$ echo ~/projects/
/Users/ruan/projects/

bash-5.1$ echo ~root/.ssh
/var/root/.ssh

bash-5.1$ echo ~aaa/.ssh
~aaa/.ssh

问号扩展

?字符代表文件路径里面的任意单个字符,不包括空字符。
只有文件确实存在的前提下,才会发生扩展。

1
2
3
4
5
6
7
bash-5.1$ touch {a,b}.txt ab.txt

bash-5.1$ ls ?.txt
a.txt b.txt

bash-5.1$ ls ??.txt
ab.txt

星号扩展

*字符代表文件路径里面的任意数量的任意字符,包括零个字符。

1
2
3
4
5
6
7
8
9
bash-5.1$ ls *.txt
a.txt ab.txt b.txt

bash-5.1$ ls /usr/local/Cellar/*/*/bin/z*
/usr/local/Cellar/ffmpeg/4.4_2/bin/zmqsend
/usr/local/Cellar/mysql-client/8.0.26/bin/zlib_decompress
/usr/local/Cellar/netpbm/10.86.24/bin/zeisstopnm
/usr/local/Cellar/perl/5.34.0/bin/zipdetails
/usr/local/Cellar/zstd/1.5.0/bin/zstd

方括号扩展

方括号扩展的形式是[...],只有文件确实存在的前提下才会扩展。

[^...][!...]。它们表示匹配不在方括号里面的字符

方括号扩展有一个简写形式[start-end],表示匹配一个连续的范围

1
2
3
4
5
6
7
8
bash-5.1$ ls [ab].txt
a.txt b.txt

bash-5.1$ ls [^b]b.txt
ab.txt

bash-5.1$ ls [a-b].txt
a.txt b.txt

大括号扩展

大括号扩展{...}表示分别扩展成大括号里面的所有值
大括号也可以与其他模式联用,并且总是先于其他模式进行扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash-5.1$ echo {1,2,3}
1 2 3

bash-5.1$ echo a{1,2,3}b
a1b a2b a3b

bash-5.1$ echo --exclude={a,b,c}
--exclude=a --exclude=b --exclude=c

bash-5.1$ echo foo{1,2{1,2}0,3}bar
foo1bar foo210bar foo220bar foo3bar

bash-5.1$ echo {1..3}
1 2 3

bash-5.1$ echo {1..10..2}
1 3 5 7 9

变量扩展

Bash 将美元符号$开头的词元视为变量,将其扩展成变量值

1
2
bash-5.1$ echo $HOME
/Users/ruan

命令扩展

$(...)可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值。

1
2
3
4
5
bash-5.1$ echo $(date)
日 11 28 16:22:09 CST 2021

bash-5.1$ echo `date`
日 11 28 16:22:24 CST 2021

算术扩展

$((...))可以扩展成整数运算的结果

1
2
bash-5.1$ echo $((1+1))
2

引号使用

单引号

单引号用于保留字符的字面含义,在单引号里转义字符和模式扩展都会失效。

1
2
3
4
5
bash-5.1$ ls '[ab].txt'
ls: cannot access '[ab].txt': No such file or directory

bash-5.1$ ls '*'
ls: cannot access '*': No such file or directory

双引号

双引号比单引号宽松,三个特殊字符除外:美元符号($)、反引号(`)和反斜杠(\)。这三个字符,会被 Bash 自动扩展。

也就是说,相比单引号在双引号中变量扩展,命令扩展,算术扩展以及转义字符是有效的。

1
2
3
4
5
6
7
8
9
10
11
bash-5.1$ echo "$((1+1))"
2

bash-5.1$ echo "$HOME"
/Users/ruan

bash-5.1$ echo "$(date)"
日 11 28 16:35:27 CST 2021

bash-5.1$ echo -e "1\t2"
1 2

引号嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14

bash-5.1$ echo '"'
"
bash-5.1$ echo '"${HOME}"'
"${HOME}"
# 单引号中使用双引号
bash-5.1$ echo "'${HOME}'"
'/Users/ruan'
bash-5.1$ echo "'\"${HOME}\"'"
'"/Users/ruan"'

# 引号嵌套中使用模式扩展,将需要扩展的字符放在单引号中;典型的有json变量填充
bash-5.1$ echo '{"user": "'${USER}'"}'
{"user": "ruan"}

here doc

Here 文档(here document)是一种输入多行字符串的方法,格式如下。
它的格式分成开始标记(<< token)和结束标记(token), 一般用字符串EOF作为token

1
2
3
<< token
text
token

例如

1
2
3
4
5
6
7
8
bash-5.1$ cat << EOF
> 11
> 22
> 33
> EOF
11
22
33

here string

Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(<<<)表示。
它的作用是将字符串通过标准输入,传递给命令。

1
2
3
4
bash-5.1$ cat <<< foobar
foobar
bash-5.1$ echo foobar | cat
foobar

变量

bash 是基于标准输入在不同进程间交互数据的,大部分功能都是在操作字符串,所以变量的默认类型也是字符串

声明变量和读取变量

声明时等号两边不能有空格。
Bash 变量名区分大小写,HOMEhome是两个不同的变量。

1
2
3
4
bash-5.1$ foo=1
bash-5.1$ echo $foo
1

变量查看和删除

1
2
3
4
5
6
7
8
9
10
11
12

bash-5.1$ set
UID=501
USER=ruan
bar=2
foo=1

bash-5.1$ unset foo
bash-5.1$ set
UID=501
USER=ruan
bar=2

变量输出

用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。
如果希望子进程能够读到这个变量,需要使用export命令。

1
2
3
4
bash-5.1$ bash -c set  | grep foo
bash-5.1$ export foo=1
bash-5.1$ bash -c set | grep foo
foo=1

环境变量

平时所说的环境变量,就是init进程export输出的。子进程对变量的修改不会影响父进程,也就是说变量不是共享的。

1
2
3
4
5

bash-5.1$ env
SHELL=/bin/zsh
LSCOLORS=Gxfxcxdxbxegedabagacad
ITERM_PROFILE=Default

下面是一些常见的环境变量。

  • BASHPID:Bash 进程的进程 ID。
  • BASHOPTS:当前 Shell 的参数,可以用shopt命令修改。
  • DISPLAY:图形环境的显示器名字,通常是:0,表示 X Server 的第一个显示器。
  • EDITOR:默认的文本编辑器。
  • HOME:用户的主目录。
  • HOST:当前主机的名称。
  • IFS:词与词之间的分隔符,默认为空格。
  • LANG:字符集以及语言编码,比如zh_CN.UTF-8
  • PATH:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。
  • PS1:Shell 提示符。
  • PS2: 输入多行命令时,次要的 Shell 提示符。
  • PWD:当前工作目录。
  • RANDOM:返回一个0到32767之间的随机数。
  • SHELL:Shell 的名字。
  • SHELLOPTS:启动当前 Shell 的set命令的参数
  • TERM:终端类型名,即终端仿真器所用的协议。
  • UID:当前用户的 ID 编号。
  • USER:当前用户的用户名。

特殊变量

Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。

  • $?: 上一个命令的退出码, 0为成功,其他为失败
  • $$: 当前进程的pid
  • $_: 为上一个命令的最后一个参数
  • $!: 为最近一个后台执行的异步命令的进程 ID。
  • $0: bash脚本的参数列表,0是脚本文件路径,1到n是第1到第n个参数

变量默认值

  • ${varname:-word}: 如果变量varname存在且不为空,则返回它的值,否则返回word
  • ${varname:=word}: 如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word。
  • ${varname:+word}: 如果变量名存在且不为空,则返回word,否则返回空值。它的目的是测试变量是否存在。
  • ${varname:?message}: 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。

declare 命令

declare命令的主要参数(OPTION)如下。

  • -a:声明数组变量。
  • -A:声明关联数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。
  • -p:查看变量信息。
  • -r:声明只读变量。
  • -x:该变量输出为环境变量。

数据类型

bash 有字符串,数字,数字,关联数组四种数据类型,默认是字符串,其他类型需要手动声明。

字符串

定义

语法 varname=value

1
2
3
bash-5.1$ s1=abcdefg
bash-5.1$ echo $s1
abcdefg

获取长度(length)

语法 ${#varname}

1
2
bash-5.1$ echo ${#s1}
7

子字符串(substr)

语法 ${varname:offset:length}, offset为负数的时候,前面要加空格,防止与默认值语法冲突。

1
2
3
4
5
6
7
bash-5.1$ s1=abcdefg
bash-5.1$ echo ${s1:1:3}
bcd
bash-5.1$ echo ${s1: -6:2}
bc
bash-5.1$ echo ${s1: -6:3}
bcd

替换 (replace)

字符串头部的模式匹配
  • ${variable#pattern}: 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
  • ${variable##pattern}: 删除最长匹配(贪婪匹配)的部分,返回剩余部分

匹配模式pattern可以使用*?[]等通配符。

1
2
3
4
5
6
7
$ myPath=/home/cam/book/long.file.name

$ echo ${myPath
cam/book/long.file.name

$ echo ${myPath
long.file.name
字符串尾部的模式匹配
  • ${variable%pattern}: 删除最短匹配(非贪婪匹配)的部分,返回剩余部分
  • ${variable%%pattern}: 删除最长匹配(贪婪匹配)的部分,返回剩余部分
1
2
3
4
5
6
7
$ path=/home/cam/book/long.file.name

$ echo ${path%.*}
/home/cam/book/long.file

$ echo ${path%%.*}
/home/cam/book/long
任意位置的模式匹配

如果匹配pattern则用replace替换匹配的内容

  • ${variable/pattern/replace}: 替换第一个匹配
  • ${variable//pattern/replace}: 替换所有匹配
1
2
3
4
5
6
7
$ path=/home/cam/foo/foo.name

$ echo ${path/foo/bar}
/home/cam/bar/foo.name

$ echo ${path//foo/bar}
/home/cam/bar/bar.name

数字

使用 declare -i声明整数变量。

1
2
3
4
5
6
7
8
9
10
11
12

bash-5.1$ declare -i val1=12 val2=5
bash-5.1$ echo $val1
12
bash-5.1$ val1+=val2
bash-5.1$ echo $val1
17


bash-5.1$ val1=aaa
bash-5.1$ echo $val1
0

数值的进制

Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。

  • number:没有任何特殊表示法的数字是十进制数(以10为底)。
  • 0number:八进制数。
  • 0xnumber:十六进制数。
  • base#numberbase进制的数。
1
2
3
4
5
6
7
8
9
10
11
bash-5.1$ declare -i a=0x77
bash-5.1$ echo $a
119

bash-5.1$ declare -i a=0xfe
bash-5.1$ echo $a
254

bash-5.1$ declare -i a=2#111
bash-5.1$ echo $a
7

算术表达式

((...))语法可以进行整数的算术运算。
支持的算术运算符如下。

  • +:加法
  • -:减法
  • *:乘法
  • /:除法(整除)
  • %:余数
  • **:指数
  • ++:自增运算(前缀或后缀)
  • --:自减运算(前缀或后缀)

如果要读取算术运算的结果,需要在((...))前面加上美元符号$((...)),使其变成算术表达式,返回算术运算的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash-5.1$ echo $((1+1))
2
bash-5.1$ echo $((1-1))
0
bash-5.1$ echo $((1*2))
2
bash-5.1$ echo $((1/2))
0
bash-5.1$ echo $((5%2))
1
bash-5.1$ a=1
bash-5.1$ echo $((a++))
1
bash-5.1$ echo $((a++))
2
bash-5.1$ echo $((++a))
4

数组

创建数组

array=(item1 item2) 语法可初始化数组,括号内可以换行,多行初始化可以用#注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

bash-5.1$ a=(1 2 3)
bash-5.1$ echo ${a[@]}
1 2 3


bash-5.1$ a=(
> 1
> 2
> 3
>
> )
bash-5.1$ echo ${a[@]}
1 2 3


bash-5.1$ a=({1..3})
bash-5.1$ echo ${a[@]}
1 2 3


bash-5.1$ declare -a b
bash-5.1$ b+=(1)
bash-5.1$ echo ${b[@]}
1


bash-5.1$ read -a c
11 22 33
bash-5.1$ echo ${c[@]}
11 22 33

访问数组元素

array[index] 语法可访问数组元素,不带index访问则是访问数组首个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash-5.1$ a=(1 2 3)

bash-5.1$ echo ${a[1]}
2

bash-5.1$ a[1]+=1
bash-5.1$ echo ${a[1]}
21
bash-5.1$ a[1]=22
bash-5.1$ echo ${a[1]}
22

bash-5.1$ a[0]=1111
bash-5.1$ echo ${a}
1111
bash-5.1$ echo ${a[0]}
1111

数组长度

${#array[@]}${#array[*]} 可访问获得数组长度

1
2
3
4
5
bash-5.1$ a=(1 2 3)
bash-5.1$ echo ${#a[*]}
3
bash-5.1$ echo ${#a[@]}
3

获取非空元素下标

${!array[@]}${!array[*]}, 可以获得非空元素的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash-5.1$ a=(1 2 3)
bash-5.1$ a[6]=6
bash-5.1$ echo ${a[0]}
1
bash-5.1$ echo ${a[@]}
1 2 3 6
bash-5.1$ echo ${a[3]}

bash-5.1$ echo ${a[4]}

bash-5.1$ echo ${!a[@]}
0 1 2 6

bash-5.1$ echo ${#a[@]}
4

数组元素切片

${array[@]:position:length}的语法可以提取数组成员。

1
2
3
4
5
bash-5.1$ a=({1..10})
bash-5.1$ echo ${a[@]}
1 2 3 4 5 6 7 8 9 10
bash-5.1$ echo ${a[@]:1:3}
2 3 4

数组追加元素

数组末尾追加元素,可以使用+=赋值运算符。

1
2
3
4
5
6
bash-5.1$ a=({1..10})
bash-5.1$ echo ${a[@]}
1 2 3 4 5 6 7 8 9 10
bash-5.1$ a+=(11 12)
bash-5.1$ echo ${a[@]}
1 2 3 4 5 6 7 8 9 10 11 12

删除元素

删除一个数组成员,使用unset命令。

1
2
3
4
5
6
7
8
bash-5.1$ a=(1 2 3)

bash-5.1$ unset a[1]
bash-5.1$ echo ${a[@]}
1 3

bash-5.1$ unset a
bash-5.1$ echo ${a[@]}

关联数组

declare -A可以声明关联数组,关联数组使用字符串而不是整数作为数组索引。

除了初始化外,使用方法和数组基本相同

1
2
3
4
5
6
7
bash-5.1$ declare -A a
bash-5.1$ a['red']=1
bash-5.1$ a['blue']=2
bash-5.1$ echo ${a[@]}
2 1
bash-5.1$ echo ${a[@]:0:1}
2

控制流

注释

#表示注释,每行从#开始之后的内容代表注释,会被bash忽略.

1
2
bash-5.1$ echo 1111 # 222
1111

条件判断

bash 和常规编程语言一样使用if作为分支条件的关键字, fi作为结束的关键字,else
elif子句是可选的

其中ifelifcondition所判断的内容是命令的状态码是否为0,为0则执行关联的语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 因为bash中分号(;)和换行是等价的,所以有下面两种风格,其他多行语句也是类似的
# 本人偏好风格1
# 风格1
if condition; then
command
elif condition; then
command
else
command
fi

# 风格2
if condition
then
command
elif condition
then
command
else
command
fi

这里的condition可以是多个命令,如command1 && command2,或者command1 || command2,则if判断的是这两个命令的状态码的逻辑计算结果。

condition也是可以是command1; command2, 则则if判断的是最后一个命令的状态码。

这里最常用的conditiontest命令, 也就是[[]][]. test是bash的内置命令,会执行给定的表达式,结果为真满足则返回状态码0, 否则返回状态码1.

下文循环语言的condition也是相同的,就不赘述了

1
2
3
4
5
6
bash-5.1$ test 1 -eq 1
bash-5.1$ echo $?
0
bash-5.1$ test 1 -eq 2
bash-5.1$ echo $?
1

[[]][]的区别是[[]]内部支持&&||逻辑判断,所以以下三种写法是等价的。

由于[]是命令, 所以两侧一定要有空格,也是就是[ 1 -eq 1 ],否则bash会认为命令找不到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# test
if test 1 -eq 2 || test 1 -eq 1; then
echo True
fi

# [ ]
if [ 1 -eq 2 ] || [ 1 -eq 1 ]; then
echo True
fi

# [[ ]]
if [[ 1 -eq 2 || 1 -eq 1 ]]; then
echo True
fi

逻辑操作符

判断条件支持且(&&)或(||)非(!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# not  
if [[ ! 'aa' == 'bb' ]]; then
echo True
fi

# or
if [[ 1 -eq 2 || 1 -eq 1 ]]; then
echo True
fi

# and
if [[ 1 -ne 2 && 1 -eq 1 ]]; then
echo True
fi

判断时引号使用(quote)

使用[test时,变量引用注意加双引号,否则得不到正确的结果,[[则不需要。

1
2
3
4
5
6
7
8
bash-3.2$ echo "$SSH_CLIENT"

bash-3.2$ if [ -n $SSH_CLIENT ]; then echo 1; else echo 0; fi
1
bash-3.2$ if [ -n "$SSH_CLIENT" ]; then echo 1; else echo 0; fi
0
bash-3.2$ if [[ -n $SSH_CLIENT ]]; then echo 1; else echo 0; fi
0

字符串判断

bash默认数据类型为字符串,所以常见的 >, <是用于字符串判断。

注意:字符串判断不支持>=<=, 得使用逻辑组合来替代

  • -z string:字符串串长度为0
  • -n string: 字符串长度大于0
  • string1 == string2: string1 等于 string2
  • string1 = string2: string1 等于 string2
  • string1 > string2: 如果按照字典顺序string1排列在string2之后
  • string1 < string2: 如果按照字典顺序string1排列在string2之前

数字(整数)判断

下面的表达式用于判断整数。

  • [ integer1 -eq integer2 ]:如果integer1等于integer2,则为true
  • [ integer1 -ne integer2 ]:如果integer1不等于integer2,则为true
  • [ integer1 -le integer2 ]:如果integer1小于或等于integer2,则为true
  • [ integer1 -lt integer2 ]:如果integer1小于integer2,则为true
  • [ integer1 -ge integer2 ]:如果integer1大于或等于integer2,则为true
  • [ integer1 -gt integer2 ]:如果integer1大于integer2,则为true

文件判断

以下表达式用来判断文件状态。仅列举常用判断,详细支持列表参考 https://tldp.org/LDP/abs/html/fto.html

  • [ -a file ]:如果 file 存在,则为true
  • [ -d file ]:如果 file 存在并且是一个目录,则为true
  • [ -e file ]:如果 file 存在,则为true, 同-a
  • [ -f file ]:如果 file 存在并且是一个普通文件,则为true
  • [ -h file ]:如果 file 存在并且是符号链接,则为true
  • [ -L file ]:如果 file 存在并且是符号链接,则为true, 同-h
  • [ -p file ]:如果 file 存在并且是一个命名管道,则为true
  • [ -r file ]:如果 file 存在并且可读(当前用户有可读权限),则为true
  • [ -s file ]:如果 file 存在且其长度大于零,则为true
  • [ -w file ]:如果 file 存在并且可写(当前用户拥有可写权限),则为true
  • [ -x file ]:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true

switch case

bash也支持,switch case,语法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case EXPRESSION in
PATTERN_1)
STATEMENTS
;;
PATTERN_2)
STATEMENTS
;;
PATTERN_N)
STATEMENTS
;;
*)
STATEMENTS
;;
esac

例如

1
2
3
4
5
6
7
8
9
10
11
a=2
case $a in
1)
echo 11
;;
2)
echo 22
;;
*)
;;
esac

循环

while 循环

while循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。
condition与if语句的相同,就不赘述了。

1
2
3
while condition; do
command
done

unitl 循环

until循环与while循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。

1
2
3
until condition; do
command
done

for-in 循环

for...in循环用于遍历列表的每一项。

1
2
3
for variable in list; do
commands
done

常见的几种用法

1
2
3
4
5
6
7
8
9
10
11
12
for i in 1 2 3; do
echo $i
done

for i in {1..3}; do
echo $i
done

list=(1 2 3)
for i in ${list[@]}; do
echo $i
done

for 循环

for循环还支持 C 语言的循环语法。

1
2
3
for (( expression1; expression2; expression3 )); do
commands
done

上面代码中,expression1用来初始化循环条件,expression2用来决定循环结束的条件,expression3在每次循环迭代的末尾执行,用于更新值。

注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号$

例如

1
2
3
for ((i=1; i<=3; i++)); do
echo $i
done

跳出循环

Bash 提供了两个内部命令breakcontinue,用来在循环内部跳出循环。

break命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。
continue命令立即终止本轮循环,开始执行下一轮循环。

函数

函数定义

Bash 函数定义的语法有两种,其中fn为定义的函数名称。

1
2
3
4
5
6
7
8
9
# 第一种
fn() {
# codes
}

# 第二种
function fn() {
# codes
}

函数参数

函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的。

  • ${N}:函数的第一个到第N个的参数。
  • $0:函数所在的脚本名。
  • $#:函数的参数总数。
  • $@:函数的全部参数,参数之间使用空格分隔。
  • $*:函数的全部参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。

函数调用

funcname arg1 arg ... argN 的语法进行函数调用。主要函数的返回值和输出值(标准输出)的区别,这和主流编程语言不同

1
2
3
4
5
6
7
8
9
10
11
add() {
declare -i res
res=0
for i in $@; do
res+=$i
done
echo $res
}

# 结果为10
add 1 2 3 4

函数返回值

return命令用于从函数返回一个值。返回值和命令的状态码一样,可以用$?拿到值。
return也可以不接具体的值,则返回值是return命令的上一条命令的状态码。
如果不加return,则返回值是函数体最后一条命令的状态码。

1
2
3
function func_return_value {
return 10
}

关键概念

shebang

Shebang(也称为Hashbang)是一个由井号和叹号构成的字符序列#!, 其出现在可执行文本文件的第一行的前两个字符。
在文件中存在Shebang的情况下,类Unix操作系统的程序加载器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令.

例如,shell脚本

1
2
3
#!/bin/bash

echo Hello, world!

python 脚本

1
2
3


print("Hello, world!")

状态码

每个命令都会返回一个退出状态码(有时候也被称为返回状态)。

成功的命令返回 0,不成功的命令返回非零值,非零值通常都被解释成一个错误码。行为良好的 UNIX 命令、程序和工具都会返回 0 作为退出码来表示成功,虽然偶尔也会有例外。

状态码一般是程序的main函数的返回码,如c,c++。
如果是bash脚本,状态码的值则是 exit 命令的参数值。
当脚本以不带参数的 exit 命令来结束时,脚本的退出状态码就由脚本中最后执行的命令来决定,这与函数的 return 行为是一致的。

特殊变量$?可以查看上个命令的退出状态码

文件描述符

文件描述符在形式上是一个非负整数。指向内核为每一个进程所维护的该进程打开文件的记录表。
当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

标准输入输出

每个Unix进程(除了可能的守护进程)应均有三个标准的POSIX文件描述符,对应于三个标准流:

  • 0:标准输入
  • 1:标准输出
  • 2:错误输出

打开新的文件描述符

手动指定描述符

1
2
3
exec 3<> /tmp/foo  #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.

系统自动分配描述符,bash4.1开始支持(在macos报错,原因不明)

1
2
3
4
5
6
7
8
#!/bin/bash

FILENAME=abc.txt

exec {FD}<>"$FILENAME"
echo 11 >&FD
echo 22 >&FD
$FD>&-

描述符重定向

  • command > file: 将输出重定向到 file。
  • command < file: 将输入重定向到 file。
  • command >> file: 将输出以追加的方式重定向到 file。
  • n > file: 将文件描述符为 n 的文件重定向到 file。
  • n >> file: 将文件描述符为 n 的文件以追加的方式重定向到 file。
  • n >& m: 将输出文件 m 和 n 合并。
  • n <& m: 将输入文件 m 和 n 合并。

所以命令中常见的ls -al > output.txt 2>&1, 就是将标准输出和错误输出都重定向到一个文件。
等价于ls -al &>output.txt,本人偏好这种写法,比较简洁。

IFS (Input Field Separators)

IFS决定了bash在处理字符串的时候是如何进行单词切分。
IFS的默认值是空格,TAB,换行符,即 \t\n

1
2
3
$ echo "$IFS" | cat -et
^I$
$

例如,在for循环的时候,如何区分每个item

1
2
3
for i in `echo -e "foo bar\tfoobar\nfoofoo"`; do
echo "'$i' is the substring";
done

也可以自定义

1
2
3
4
5
6
7
OLD_IFS="$IFS"
IFS=":"
string="1:2:3"
for i in $string; do
echo "'$i' is the substring";
done
IFS=$OLD_IFS

任务管理

linux进程分前台(fg)和后台(bg)。

在命令的末尾添加&可以将命令后台执行,一般配合输出重定向使用。

jobs可以查看当前bash进程的子进程,并通过fgbg进行前台和后台切换。

%1代表后台的第一个进程,以此类推%N代表第n个.

control + Z可以将当前前台程序暂停,配合bg可以将其转后台。

wait [pid]可以等待子进程结束,如果不带pid参数则等待所有子进程结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash-5.1$ sleep 100
^Z
[1]+ 已停止 sleep 100
bash-5.1$ jobs
[1]+ 已停止 sleep 100
bash-5.1$ bg %1
[1]+ sleep 100 &
bash-5.1$ sleep 200 &
[2] 63603
bash-5.1$ jobs
[1]- 运行中 sleep 100 &
[2]+ 运行中 sleep 200 &
bash-5.1$ wait %1
[1]- 已完成 sleep 100

后台进程并发控制

可以利用jobs对后台进程并发数目进行控制

1
2
3
4
5
6
7
8
for i in {1..30}; do
sleep $((30+i)) &
if [[ $(jobs | wc -l ) -gt 10 ]]; then
jobs
wait
fi
done
wait

参考