命令入门
基本内容
bash命令基本都是下面格式
1 | $ command [ arg1 ... [ argN ]] |
command
是具体的命令或者一个可执行文件,arg1...argN
是传递给命令或文件的参数
有些参数是命令的配置项,这些配置项一般都以一个连词线开头,比如
1 | $ ls -l |
同一个配置项往往有长和短两种形式,比如-l
是短形式,--list
是长形式,它们的作用完全相同。短形式便于手动输入,长形式一般用在脚本之中,可读性更好,利于解释自身的含义
Bash单个命令一般都是一行,用户按下回车就开始执行,有些命令比较长,写成多行有利于阅读,这时就可以在每一行结尾加上\
(反斜杠),Bash就会将下一行跟当前行放在一起解释
命令组合
;
可以用来在同一行中分隔不同命令,此时不管分号前的命令执行成功或失败,分号后的命令都会执行
&&
与||
可以用于控制命令间的继发关系,如:
1 | #如果command1 命令运行成功,则运行command2命令 |
echo命令
使用echo
命令输出一行文本可以不用双引号包含,如:
1 | $ echo Hello world! |
如果想输出多行文本,则需要用单引号或双引号包含,如:
1 | $ echo "<HTML> |
echo
命令输出的文本末尾会自带一个回车符,可以用-n
参数取消末尾的回车符
1 | $ echo -n a; echo b |
使用-e
参数可以将引号中的转义字符(如\n
)转义
1 | $ echo "Hello\nAjsoabk!" |
判断内置命令
type
命令用于判断某个命令是内置命令还是外部程序,如
1 | $ type echo |
如果要查看一个命令的所有定义,可以使用type
命令的-a
参数
1 | $ type -a echo |
type
命令的-t
参数,可以返回一个命令的类型(alias
别名,keyword
关键词,function
函数,builtin
内置命令,file
文件)
1 | $ type -t bash |
快捷键
ctrl
+l
,清屏ctrl
+c
,中止当前命令的执行shift
+PageUp
/PageDown
,向上/下滚动ctrl
+u
/k
,从光标位置删除到行首/行尾ctrl
+d
,关闭shell对话
除了上面这些快捷键,Bash还有自动补全功能。命令输入到一半的时候,可以按下Tab
键自动补全,路径也支持自动补全。如果有多个可能的选择,只需要按两次Tab
,Bash会显示所有选项让你选择
注释
注释的方式有很多
#
开头的一行会被当成注释<<BLOCK
和BLOCK
之间的内容会被当成注释:'
和'
之间的内容会被当成注释
变量
变量名规则与c相同,大小写敏感
变量定义规则:
- 变量名与值之间的
=
两侧都不能有空格 - 读取或打印变量时需要使用
$
+变量名 - 不需要指定数据类型,bash会自动判断数据类型
模式拓展
Shell接收到用户输入的命令以后,会根据空格将用户的输入拆分成一个个词元。然后Shell会将词元里面的特殊字符进行拓展,拓展完成后才会调用相应的命令
Bash允许用户关闭拓展:
1 | $ set -o noglob |
拓展有许多种,当文件名中包含通配符的时候需要将文件名放在单引号或双引号里面
~
目录拓展
~
会自动拓展成当前用户的主目录:
1 | $ echo ~ |
~user
,表示拓展成用户user
的主目录:
1 | $ echo ~ajsoabk |
~+
会拓展成当前所在的目录,等同于pwd
命令
1 | $ cd ~/foo |
?
单字符拓展
?
字符代表文件路径里面的任意单个字符,不包括空字符。
1 | # 当前目录存在文件a.txt和b.txt |
如果文件不存在,则不进行拓展
*
通配拓展
*
字符代表文件路径里面的任意数量的任意字符。但不会匹配隐藏文件(以.
开头的文件),如果需要匹配隐藏文件要使用.*
1 | # 存在文件 a.txt、b.txt和 ab.txt |
如果没有匹配的文件,则不进行拓展
[]
匹配拓展
[]
包裹的任意一个字符匹配则匹配成功:
1 | # 存在文件 a.txt 和 b.txt |
可以在左方括号后紧跟一个^
或!
,表示匹配不在方括号里面的字符
1 | # 存在 aaa、bbb、aba三个文件 |
如果需要匹配[
字符,可以放在方括号内,如果需要匹配-
,只能放在方括号内部的开头或结尾,比如[-aeiou]
,除此之外,-
可以用来匹配一个连续的范围,如[a-d]
等同于[abcd]
{}
全部拓展
{}
拓展表示分别拓展成大括号里面的所有值:
1 | $ echo d{a,e,i,u,o}g |
大括号内部的逗号前后不能有空格,否则扩展失效
逗号前面可以没有值,表示拓展失效:
1 | $ cp a.log{,.bak} |
大括号可以嵌套,也可以与其他模式联用,并且总是先于其他模式进行拓展
{start..end}
表示拓展成一个连续序列,并且支持逆序
这种简写形式还可以使用第二个双点号用来指定拓展的步长
1 | $ echo {0..8..2} |
$
变量拓展
$
开头的词元将被视为变量,变量名除了放在$
后面,也可以放在${}
里面
1 | $ echo $SHELL |
${|string*}
或${!string@}
返回所有匹配string
的变量名
1 | $echo ${!S*} |
$()
命令拓展
$()
或反引号可以拓展成另一个命令的运行结果,可嵌套,但是结果中的连续空白字符将被替换成单个空格:
1 | $ echo $(date) |
$(())
算术拓展
$(())
可以拓展成整数运算的结果:
1 | $ echo $((2+2)) |
[[:class:]]
类匹配拓展
[[:graph:]]
:匹配A-Z、a-z、0-9和标点符号[[:alnum:]]
:匹配A-Z、a-z、0-9[[:alpha:]]
:匹配A-Z、a-z[[:upper:]]
:匹配A-Z[[:lower:]]
:匹配a-z[[:digit:]]
:匹配0-9[[:punct:]]
:匹配标点符号(除了A-Z、a-z、0-9的可打印字符)[[:blank:]]
:匹配空格和Tab
键[[:space:]]
:匹配空格、Tab
、LF(10)、VT(11)、FF(12)、CR(13)[[:cntrl:]]
:匹配ASCII码0-31的不可打印字符[[:xdigit:]]
:匹配A-F、a-f、0-9
在第一个方括号后面加上!
表示否定
量词语法
量词语法用于控制模式匹配的次数
量词语法有以下五个
?(pattern-list)
:匹配零个或一个模式@(pattern-list)
:匹配一个模式*(pattern-list)
:匹配零个或多个模式+(pattern-list)
:匹配一个或多个模式!(pattern-list)
:匹配给定模式外的任何内容
1 | #当前目录下有abctxt和abc.txt |
shopt
命令
shopt
命令可以调整Bash的行为,它有好几个参数跟通配符拓展有关
-s
参数打开后面跟着的参数,-u
关闭后面跟着的参数
dotglob
,打开后拓展结果可以包含隐藏文件,默认关闭nullglob
,打开后让通配符不匹配任何文件名时返回空字符,默认关闭failglob
,打开后让通配符不匹配任何文件名时直接报错而不交由命令处理,默认关闭extglob
,打开后支持量词语法,默认打开nocaseglob
,打开后让通配符拓展不区分大小写,默认关闭globstar
,打开后可以用**
匹配零个或多个子目录,默认关闭1
2
3$ shopt -s globstar
$ ls **/*.txt
a.txt sub1/b.txt sub1/sub2/c.txt
转义
某些字符在bash里面有特殊含义($
,&
,*
),要想输出这些字符需要在它们前面加上反斜杠,时其变成普通字符,这就叫做转义
1 | $ echo \$date |
反斜杠本身也是特殊字符
是否转义主要有两种区别,一个是包裹字符串的是单引号还是双引号,一个是否使用了-e
参数
-e
参数会强制转义,不管包裹字符串的是单引号还是双引号。
而在没有-e
参数的情况下,单引号包裹的字符串全部原样打印,不会有任何转义或拓展:
1 | $ echo 'a\\b\nc' |
只有三个特殊字符在双引号中会保留特殊含义($
,`
,\
),因此双引号包裹的字符串会对特殊字符进行转义,但不会对不可打印字符进行转义,如:
1 | $ echo "$SHELL" |
Here文档
Here文档用于输入多行字符串,格式如下:
1 | << token |
它的格式分为开始标记和结束标记,结束标记必须顶格写
Here文档内部$
与\
保留特殊含义,不支持通配符拓展
1 | $ foo='Ajsoabk' |
如果不希望发生变量替换,可以把Here文档的开始标记放在单引号之中
1 | $ foo='Ajsoabk' |
Here文档的本质是重定向,它将字符串重定向输出给某个命令,相当于包含了echo
命令
1 | $ command << token |
所以,Here文档知识和哪些可以接受标准输入作为参数的命令,对于其他命令无效,比如echo
命令就不能用Here文档作为参数
此外,Here文档也不能作为变量的值,只能用于命令的参数
Here字符串
Here文档还有一个变体,叫做Here字符串,使用<<<
表示,它的作用是将字符串通过标准输入传递给命令
有些命令直接接受给定的参数与通过标准输入接受参数结果是不一样的,如:
1 | $ cat <<< 'hi there' |
而如果是直接将字符串放在cat
命令后面的话则会被解释成字符串
变量
环境变量
环境变量是Bash环境自带的变量,进入Shell时已经定义好了,可以直接使用。
可以通过命令env
, printenv
来查看所有环境变量
下面是一些常见的环境变量
BASH
,Shell名称,如:1
BASH=/user/bin/bash
BASH_VERSION
,Bash持有的shell版本,如:1
BASH_VERSION=4.2.46(2)
COLUMNS
,屏幕的列数,如:1
COLUMNS=80
HOME
为用户的主目录,如:1
HOME=/home/ajsoabk
LOGNAME
为日志的用户名,如:1
LOGNAME=ajsoabk
USER
当前用户的用户名,如:1
USER=ajsoabk
LINUNO
返回它在脚本里面的行号FUNCNAME
返回一个包含当前函数调用堆栈的数组BASH_SOURCE
返回当前脚本调用堆栈
自定义变量
set
命令可以显示所有变量(环境变量和自定义变量)已经所有的Bash函数
变量可以重复赋值,后面的赋值会覆盖前面的赋值
如果变量的值本身也是变量,可以使用${!var}
的语法读取最终的值
1 | $ myvar=USER |
export
变量
export
变量能够被子Shell读取,在子Shell中更改export变量不会影响父Shell,如:
1 | $ export foo=bar |
特殊变量
$?
为上一个命令的退出码,用来判断上一个命令是否执行成功,若成功则为0
,反之则为非零值:
1 | $ ls doesnotexist |
$$
为当前Shell的进程ID,可以用来命令临时文件
1 | LOGFILE=/t,p/output_log.$$ |
$_
为上一个命令的最后一个参数
1 | $ grep dictionary /usr/share/dict/words |
$0
为当前Shell的名称或者脚本名
变量的默认值
1 | ${varname:-word} |
declare
命令
declare
命令可以声明一些特殊类型的变量,比如只读类型和整数类型的变量,其语法形式为:
1 | declare OPTION VARIABLE=value |
其参数有:
-a
,数组变量-i
,整数变量-r
,只读变量-x
,环境变量-f
,输出所有函数定义-F
,输出所有函数名-l
,变量值总是会被转为小写字母-u
,变量值总是会被转为大写字母-p
,输出变量信息
不带任何参数的declare
命令等同于不带任何参数的set
命令
declare
命令如果用在函数中,声明的变量旨在函数内部有效,等同于local
命令
注意,一个变量声明为整数以后,依然可以被改写为字符串
1 | $ declare -i var=12 |
上面例子中,变量var
声明为整数,覆盖以后,Bash不会报错,但会赋以不确定的值
readonly
命令
readonly
命令等同于declare -r
,用来声明只读变量,有三个参数
-f
,声明的变量为函数名-p
,打印出所有的只读变量-a
,声明的变量为数组
let
命令
let
命令声明变量时,可以直接执行算术表达式
1 | $ let foo=1+2 |
let
命令的参数表达式如果包含空格,就需要使用引用
命令行参数
命令行参数通过将数据以参数的形式传递给脚本,使脚本更具动态性,如:
1 | ./bash_script.sh parameter1 parameter2 |
如何使用命令行参数
$0
,脚本名称$1-9
,存储前9个参数的名称$#
,传递给脚本的参数总数$@
, 以数组形式存储的参数列表$*
,所有参数连接在一起后的形式
Shift命令与参数移位
shift
命令能够控制命令行参数整体左移,同时$#
也会减少相应个数,默认移一位,如:
1 |
|
字符串
可以使用${#varname}
获取字符串长度
提取子串
可以使用${varname:offset:length}
提取子串,可以省略length
:
1 | $ count=frogfootman |
offset
和length
都可以为负值
1 | $ foo="This string is long." |
上面例子中,length
为-2
时要排除从字符串末尾开始的2个字符,所以返回lon
搜索和替换
${string#pattern}
和${string##pattern}
可以检查字符串的开头是否匹配并将匹配的部分删除,其中前者是最短匹配,后者是最长匹配
${string/#pattern/sub}
和${string/##pattern/sub}
可以检查字符串的开头是否匹配并将匹配的部分替换,同样的一个最短一个最长
将#
换成%
则是检查末尾,其余同上
${string/pattern/sub}
替换第一个最长的匹配
${string//pattern/sub}
替换所有最长匹配
1 | #将路径分隔符:换成换行符 |
改变大小写
${string^^}
可以把字符串转为大写
${string,,}
可以把字符串转为小写
算术
(())
语法可以进行整数的算术运算,并且会自动忽略内部的空格
这个命令在算术结果不为0
的时候执行成功,算术结果为0
的时候就算执行失败
1 | $ ((3 - 3)) |
这个命令本身不返回值,如果要读取算数运算的结果,需要在第一个左括号前面加上美元符号
支持的算数运算符包括加减乘除、求余、递增递减、位运算、逻辑运算(包括三目运算符)、赋值运算(包括一系列复杂赋值)、逗号运算以及指数运算(**
),注意/
的返回结果总是整数
++
与--
的前缀后缀版本的区别与C语言中相同
在算术表达式中可以使用其它进制
0number
,八进制0xnumber
,十六进制bass#number
,base
进制的数
expr
命令
expr
命令支持算术运算,可以不使用(())
语法
脚本入门
Shebang行
脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!
Shebang字符开头
如果没有Shebang行,就只能手动将脚本传给解释器来执行
控制流语句
if[-elif-else]-fi
if
语法如下:
1 | if commands; then |
if
后面可以跟任意数量的命令,这时所有命令都会执行,但是判断真伪只看最后一个命令
case
结构
case
结构用于多值判断,跟包含多个elif
的if
结构等价,但是语义更好
1 | case expression in |
如果需要匹配多个条件,可以用;;&
终止每个条件块
while
循环
格式为:
1 | while condition; do |
condition
可以为命令
until
循环
until
循环与while
循环恰好相反,只要不符合判断条件,就不断循环执行指定的语句,一旦符合判断条件就退出循环:
1 | until condition; do |
until
一般用得比较少,完全可以统一使用while
for...in
循环
for...in
循环用于遍历列表的每一项
1 | for variable in list; do |
列表可以是以空格分隔的值(for i in word1 word2 word3; do...
)
也可以由通配符产生(for i in *.png; do
)
也可以通过子命令产生(for i in $(cat ~/.bash_profile
)
in list
的部分可以省略,此时list
默认等于脚本的所有参数$@
,但是为了可读性最好还是不要省略
for
循环
1 | for (( expression1; expression2; expression3 )); do |
它等同于
1 | (( expression1 )) |
break
、continue
与C中相同
select
结构
select
结构用于给用户提供选择
1 | select element |
Bash会对select
依次进行下面的处理
select
打印一个菜单,内容是列表list
的每一项,并且每一项前面还有一个数字编号- Bash提示用户选择一项,输入它的编号
- 用户输入以后,Bash会将该项的内容存在变量
name
,该项的编号存入环境变量REPLY
。如果用户没有输入,就按回车键,Bash会重新输出菜单,让用户选择 - 执行命令体
commands
- 执行结束后回到第二步,重复这个过程
下面是一个例子
1 | #!bin/bash |
执行上面的脚本,Bash会输出一个品牌的列表让用户选择
1 | 1) Samsung |
select
可以与case
结合
test
命令
1 | test expression |
第三种形式还支持正则判断。如果expression
表达式为真则test
执行成功
文件判断
test
命令有许多参数可以用来判断文件状态:
[ -a file ]
,存在[ -b file ]
,存在且是一个块(设备)文件[ -c file ]
,存在且是一个字符(设备)文件[ -d file ]
,存在且是一个目录[ -e file ]
,存在[ -f file ]
,存在且是普通文件[ -g file ]
,存在且设置了组ID[ -G file ]
,存在且设置了有效的组ID[ -h file ]
,存在且是符号链接[ -k file ]
,存在且设置了它的“sticky bit”[ -L file ]
,存在且是符号链接[ -N file ]
,存在且自上次读取后已被修改[ -O file ]
,存在且属于有效的用户ID[ -p file ]
,存在且是命名管道[ -r file ]
,存在且当前用户可读[ -s file ]
,存在且长度大于零[ -S file ]
,存在且是一个socket[ -t fd ]
,如果fd
是一个文件描述符且重定向到终端则为真。可以判断是否重定向了标准输入输出/错误流[ -u file ]
,存在且设置了setuid
位[ -w file ]
,存在且当前用户可写[ -x file ]
,存在且当前用户可执行[ file1 -nt file2 ]
,file1
比file2
的更新时间最近,或者file1
存在而file2
不存在[ file1 -ot file2 ]
,file2
比file1
的更新时间最近,或者file2
存在而file1
不存在[ FILE1 -ef FILE2 ]
,FILE1
和FILE2
引用相同的设备和inode
编号
字符串判断
[ string ]
,不为空[ -n string ]
,不为空[ -z string ]
,为空[ string1 = string2]
,判断等价[ string1 == string2]
,判断等价[ string1 '>' string2 ]
,判断string
的字典序在string2
之后[ string1 '>' string2 ]
,判断string
的字典序在string2
之前1
2
3
4$ str1=abc
$ str2=abb
$ if [ "$str1" '<' "$str2" ]; then echo "greater"; fi
greater
整数判断
[ -z num1 ]
,判断为空[ num1 -eq num2 ]
,判断等价[ num1 -ne num2 ]
,判断不等价[ num1 -le num2 ]
,判断小于等于[ num1 -lt num2 ]
,判断小于[ num1 -ge num2 ]
,判断大于等于[ num1 -gt num2 ]
,判断大于
算术判断
可以包裹(())
进行算术运算的判断
正则判断
[[ expression ]]
这种判断形式支持正则表达式
[[ string1 =~ regex ]]
,其中=~
是正则比较运算符:
1 |
|
逻辑判断
可以用&&
、||
、!
或-a
、-o
来结合多个test
判断表达式
1 | #!bin/bash |
使用!
操作符时,最好用圆括号确定转义的范围,并且圆括号必须使用引号或者转义,否则会被Bash解释
脚本参数
调用脚本的时候,脚本文件名后面可以带有参数:
1 | $ script.sh word1 word2 word3 |
脚本文件内部可以使用特殊变量引用这些参数
$1-9
,脚本的第一个到第九个参数$#
,参数的总数$@
,以数组形式存储的全部的参数$*
,以$IFS
值的第一个字符(默认为空格)分隔的全部的参数
如果脚本的参数多于9个,那么第十个参数可以用${10}
的形式引用,以此类推
shift
命令
shift
命令可以改变脚本参数,每次执行都会移出脚本的前几个参数(默认为1),并将后面所有的参数向前移相应位数,$#
的值也会相应减少(如果原来有参数的话)
getopts
命令
getopts
命令用在脚本内部,可以解析复杂的脚本命令行参数,通常与while
循环一起使用,去除脚本所有的带有前置-
的参数,其形式为:
1 | getopts opt name |
第一个参数opt
是字符串,给出脚本所有的连词线参数(不带连词线)。
比如某个脚本可以有灿哥配置项参数-l
、h
、-a
,其中只有-a
可以带有参数值,而-l
和-h
是开关参数,那么getopts
的第一个参数写成lha:
,顺序不重要。注意,a
后面有一个冒号表示该参数带有参数值
第二个参数name
是一个变量名,用来保存当前取到的配置项参数,即l
、h
、或a
以下是一个使用getopts
获取参数的例子
1 | while getopts 'lha:' OPTION; do |
变量OPTION
保存的是当前处理的连词线参数(即l
、h
或a
)。如果用户输入了没有指定的参数,那么OPTION
等于?
如果某个连词线参数带有参数值,那么$OPTARG
保存的就是参数值
注意,只要遇到不带连词线的参数,getopts
就会执行失败,从而退出while
循环。另外,多个连词线参数写在一起的形式,比如command -lh
,getopts
也可以正确处理
变量OPTIND
在getopts
开始执行前是1
,然后每次执行就会加1
。
配置项参数终止符--
-
和--
开头的参数,会被Bash当作配置项解释,但有时它们是实体参数,如果要确保某个参数不会被当作配置项解释,就要在它前面放上参数终止符--
:
1 | $ ls -- $myPath |
exit
命令
exit
命令用于终止当前脚本的执行,并向Shell返回一个退出值(0
表示正常,1
表示发生错误,2
表示用法不对,126
表示不是可执行脚本,127
表示命令没有发现
exit
命令与return
命令的区别是return
命令是函数的退出
source
命令
source
命令用于执行一个脚本,但不像直接执行脚本时会新建一个子Shell,所以通常用于重新加载一个配置文件,而且source
命令执行脚本时不需要export
变量
source
命令的另一个用途,是在脚本内部加载外部库,这样就可以在脚本里面使用这个外部库定义的函数
source
命令有一个简写形式,可以使用一个点(.
)来表示
alias
命令
alias
用于为一个命令指定一个更便于记忆的命令别名,其格式为:
1 | alias NAME=DEFINITION |
其中NAME
是命令别名,DEFINITION
是别名对应的原始命令,如:
1 | alias search=grep |
也可以用来为长命令指定一个更短的别名
1 | $ alias today='date +"%A, %B %-d, %Y"' |
直接调用alias
命令可以显示所有别名
unalias
命令可以解除别名
获取用户输入
read
命令格式如下:
1 | read [-options] [variable...] |
variable
是用来保存输入数值的一个或多个变量名。如果没有提供变量名,环境变量REPLY
会包含用户输入的一整行数据
如果用户的输入项少于read
命令给出的变量数目,那么额外的变量值为空
如果用户的输入项多于read
命令给出的变量数目,那么多余的输入项会包含到最后一个变量中
read
命令除了读取键盘输入也可以用来读入文件:
1 |
|
参数
-t
参数设置等待的秒数,环境变量TMOUT
也可以起到同样作用
-p
参数设置提示信息:
1 | read -p "Enter one or more values > " |
-a
参数把用户的输入赋值给一个数组,下标从0
开始:
1 | $ read -a people |
-n
参数指定读取字符的个数:
1 | $ read -n 3 letter |
-e
参数允许用户输入的时候使用readline
库提供的快捷键,比如自动补全
-d delimiter
将终止符设定为delimiter
而不是\n
-r
,raw模式,表示不把用户输入的反斜杠字符解释为转义字符
-s
,使用户的输入不显示在屏幕上,用于信息保密
-u fd
:使用文件描述符fd
作为输入
IFS
(Internal Field Separator)环境变量
read
命令读取的值默认以空格分隔。可以通过自定义环境变量IFS
来修改分隔标志。
IFS
的默认值是空格、tab
、\n
,通常取第一个
在文件读取中经常把IFS
定义成:
或;
可以把IFS的赋值命令和read
命令写在一行,这样的话IFS
的改变仅对后面的命令生效,不然就要用到OLD_IFS
变量来恢复IFS
使用内置的read
命令:
1 |
|
使用-p
参数
Bash函数
Bash函数定义的语法是:
1 | function fn() { |
其中function
可以省略
下面是一个多行函数的例子,显示当前日期时间
1 | today() { |
删除一个函数可以使用unset
命令
1 | unset -f functionName |
查看当前Shell已经定义的所有函数的详细信息可以使用declare
命令
1 | $ declare -f |
参数变量
函数体内可以使用参数变量,与脚本参数变量是一致的
return
命令
return
命令可以返回一个值给调用者。如果命令行直接执行函数,下一个命令可以用$?
获取返回值
local
命令
Bash函数体内直接声明的变量(包括函数体内声明的普通变量)都属于全局变量,整个脚本都可以读取
如果希望声明局部变量,需要使用local
命令:
1 |
|
其运行结果为:
1 | $ bash test.sh |
数组
数组是一个包含多个值的变量,成员的编号从0开始
数组可以采用一次性赋值的方式创建
1 | array=(val1 val2 ... valn) |
成员读取
数组的成员读取方式为${arr[n]}
。如果没有花括号,Bash会读取arr
的第一个元素,再把[n]
原样输出
可以用${arr[@]}
和${arr[*]}
获取数组的所有成员
将其放在双引号中时,后者会将数组元素合并成一个字符串返回
所以拷贝一个数组的最方便方法就是:
1 | $ hobbies=( "${activities[@]}" ) |
提取数组序号
${!arr[@]}
或${!arr[*]}
可以返回数组的成员序号
因此可以使用这种语法循环数组的下标
1 | arr=(a b c d) |
提取数组成员
${arr[@]:pos:len}
的语法可以提取一个区间内的成员
1 | $ food=( apples bananas cucunmbers dates eggs fajitas grapes ) |
如果省略length
则返回从指定位置开始的所有成员
追加数组成员
可以使用+=
赋值运算符追加数组成员
删除数组
使用unset
命令删除数组或数组成员
关联数组
declare -A
可以声明关联数组
1 | delcare -A colors |
访问关联数组成员的方法几乎与整数索引数组相同
set
命令与shopt
命令
set
命令可以帮助你写出更安全的Bash脚本
直接运行set
会显示所有的环境变量和Shell函数
-u
设置了-u
参数之后,脚本遇到不存在的变量就会报错并停止执行
-u
参数和-o nounset
是等价的
-x
设置了-x
参数之后,脚本每运行一条命令就会在终端输出,用于调试,与-o xtrace
等价
如果要关闭命令输出,可以使用set +x
-e
设置了-e
参数之后,脚本只要发生错误(返回值非0
),就会终止执行
有些命令的非零返回值可能不表示失败,这时可以使用command || true
使得该命令即使执行失败也不会终止执行
等价于-o errexit
-o pipefail
set -e
不适用于管道命令
管道命令指多个子命令通过管道运算符|
组合成一个大的命令。Bash会把最后一个子命令的返回值作为整个命令的返回值。
set -o pipefail
用来保证只要一个子命令失败整个管道命令就失败
-E
设置了-e
参数会导致函数内的错误不会被trap
命令捕获,加上-E
参数后可以使得函数也能继承trap
命令
1 |
|
执行上面这个脚本就可以看到trap
命令生效了
1 | $ bash test.sh |
-n
等同于-o noexec
不运行命令,只检查语法是否正确
-f
等同于-o noglob
表示不对通配符进行文件名拓展
-v
等同于-o verbose
,表示打印Shell接收到的每一行输入
总结
上面介绍的set
命令的几个参数,一般都放在一起使用:
1 | set -Eeuxo pipefail |
这种写法建议放在所有Bash脚本的头部
另一种方法是在执行Bash脚本的时候,从命令行传入这些参数
shopt
命令
shopt
命令用来调整Shell 的参数,跟set
命令的作用类似
直接输入shopt
可以查看所有参数以及其状态,也可以单独查询某个参数的状态
-s
用来打开某个参数
-u
用来关闭某个参数
-q
也用来查询某个参数是否打开,但不直接输出查询结果,而是通过shopt
命令的返回结果$?
表示查询结果:
1 | $ shopt -1 globstar |
mktemp
与trap
Bash脚本有时需要创建临时文件或临时目录,常见的做法是在/tmp
目录里面创建文件或目录,但这样做有很多问题,使用mktemp
命令是最安全的做法
临时文件的安全问题
/tmp
目录是所有人可读写的,如果攻击者知道临时文件的文件名就可能导致严重的安全问题。
而且脚本意外退出时往往会糊涂清理临时文件
生成临时文件淫应当遵循下面的规则
- 创建前检查文件是否已经存在
- 确保临时文件已成功创建
- 临时文件必须有权限限制
- 临时文件名要不可预测
- 脚本退出时要删除临时文件
mktemp
直接运行mktemp
命令就能生成一个临时文件,其文件名随机并且只有用户本人可读写
为了保证脚本退出时临时文件被删除,可以使用trap
命令指定退出时的清除操作
1 |
|
-d
参数可以创建一个临时目录
-p
参数可以指定临时文件所在的目录,默认使用$TMPDIR
环境变量指定的目录,如果这个变量没设置则使用/tmp
目录
-t
参数可以指定临时文件的文件名模板,模板末尾至少包含索格连续的X
字符表示随机字符,默认的文件名模板是tmp.
后接十个随机字符
trap
命令
trap
命令用来在Bash脚本响应系统信号,其形式为:
1 | $ trap [action] [signal1] [signal2] ... |
其中action
是一个Bash命令,常用的信号有
EXIT
,编号0
,退出脚本时产生HUP
,编号1
,脚本与所在的终端脱离联系INT
,编号2
,用户按下ctrl + C
,意图让脚本停止运行QUIT
,编号3
,用户按下ctrl+/
,意图退出脚本
注意,trap
命令必须放在脚本的开头,否则它上方的任何命令导致脚本退出,都不会被它捕获
文件权限
可以通过ls -l
命令来罗列出所有文件和目录
上图七列分别是:
- 文件类型(
-
常规文件,c
特殊档案,p
命名管道,b
块设备,s
套接字,d
目录,l
链接)及超级用户、用户组与其他用户的权限(r
阅读权限,w
写入权限,x
执行权限,-
占位) - 存储块的数量
- 文件的所有者或具有管理权限的超级用户
- 所有者、筹集用户组
- 文件大小
- 文件的最后修改日期
- 文件或目录的名称
chmod
命令可以更改不同用户类型的文件权限,其基本形式为
1 | chmod [class][operator][permission] filename |
u
超级用户g
用户组o
其他用户a
所有类型
+
添加-
删除权限
r
读取w
修改x
运行
重定向
输出重定向
>
会以命令中的写入内容覆盖原文件内容,如果指定的文件不存在,那么它将会创建一个以指定文件名命名的新文件
1 |
|
>>
则是内容附加,如果文件不存在也会新建
输入重定向
<
,与<<
深入理解
stdin
:标准输入文件,其文件描述符为0,默认由此读取数据stdout
:标准输出文件,其文件描述符为1,默认向它输出数据stderr
:标准错误文件,其文件描述符为2,默认向它写入错误信息
1 | #将stdin重定向到file1,将stdout重定向到file2 |
文件相关
过于简单:cd
,mkdir
,stat
,rmdir
, rm
(删除目录或文件)
touch
命令可以用来修改文件读取时间和修改时间,-t
参数自定义修改时间
cat 命令
cat
的全拼是concatenate,主要有三大功能
显示整个文件
1 | $cat filename |
从键盘创建新文件
1 | #将stdout重定向到filename |
文件合并
1 | $cat file1 file2 > file |
参数:
-n
or-number
输出带行数-b
or-number-nonblank
,输出带行数,但对空行不编号-s
or-squeeze-blank
,当遇到有两行以上的空行,压缩为一行空行-v
or-show-nonprinting
,