阅读更多
1 Basics
1.1 I/O Redirectio
命令(可以加空格地方都加了空格,否则就是不能加空格) | 说明 |
---|---|
[command] 0< [file] |
重定向命令的stdin 到文件file ,其中0 可以省略 |
[command] 1> [file] |
重定向命令的stdout 到文件file ,其中1 可以省略 |
[command] 1>> [file] |
重定向并追加命令的stdout 到文件file ,其中1 可以省略 |
[command] 2> [file] |
重定向命令的stderr 到文件file |
[command] 2>> [file] |
重定向并追加命令的stderr 到文件file |
&> [file] |
重定向stdout 和stderr 到文件file |
&>> [file] |
重定向并追加stdout 和stderr 到文件file |
[fd_m]> [file] |
重定向文件描述符fd_m 到文件file ,fd_m 可以省略,默认为1 |
[fd_m]>&[fd_n] |
重定向文件描述符fd_m 到文件描述符fd_n ,fd_m 可以省略,默认为1 |
[fd_m]>>&[fd_n] |
重定向并追加文件描述符fd_m 到文件描述符fd_n ,fd_m 可以省略,默认为1 |
[fd_m]<> [file] |
把文件file 打开, 并且将文件描述符fd_m 分配给它,fd_m 可以省略,默认为0 |
0<&- |
关闭stdin ,其中0 可以省略 |
[fd_in]<&- |
关闭输入文件描述符fd_in |
1>&- |
关闭stdout ,其中1 可以省略 |
[fd_out]>&- |
关闭输出文件描述符fd_out |
<< tag |
将开始标记 tag 和结束标记 tag 之间的内容作为输入 |
注意:
0
:stdin
的文件描述符1
:stdout
的文件描述符2
:stderr
的文件描述符&-
:用于关闭文件描述符
1.1.1 Here Document
Here Document
是Shell
中的一种特殊的重定向方式,用来将输入重定向到一个交互式Shell
脚本或程序
它的基本的形式如下
1 | command << delimiter |
例如
1 | wc -l << EOF |
1.1.2 Here String
A here string is a feature in Unix-like operating systems that allows you to pass a single string as input to a command, without the need for command substitution or creating a separate file. It’s a convenient way to provide input to a command that expects data from standard input.
In Bash and other Unix-like shells, the syntax for a here string is <<<
. You use it like this:
1 | command <<< "your_string_here" |
For example, if you have a command grep 'pattern'
that searches for a specific pattern in text, you can use a here string to provide the text directly as input without the need to create a separate file. Here’s how it would look:
1 | text="This is some example text containing the pattern" |
The here string is particularly useful when you want to avoid the overhead of creating temporary files for passing small amounts of input data to a command.
1.1.3 How to capture stderr while discarding stdout
1 | error_msg=$(cmd 2>&1 >/dev/null) |
2>&1
: redirects stderr (file descriptor 2) to where stdout (file descriptor 1) is currently pointing, which means the terminal for this example.>/dev/null
: redirects stdout (file descriptor 1) to /dev/null.
1.2 Pipe
管道命令使用的是|
这个界定符号,这个管道命令|
仅能处理有前面一个命令传来的正确信息
,也就是standard output
的信息,对于standard error并没有直接处理的能力
每个管道后面接的第一个数据必须是命令
,而且这个命令必须能够接受standard input
的数据才行,例如less,more,head,tail
等都可以接受standard input的管道命令
管道是作为子进程的方式来运行的。因此在管道中进行一些变量赋值操作,在管道结束后会丢失
1.3 Command Substitution
命令替换(command substitution)
可以让括号里
或单引号里
的命令提前于整个命令运行,然后将执行结果
插入在命令替换符号处。由于命令替换的结果经常交给外部命令,不应该让结果有换行的行为,所以默认将所有的换行符替换为了空格(实际上所有的空白符都被压缩成了单个空格)
命令替换(command substitution)
有如下种形式
`command`
$(command)
- 同时,上述两种方式也可以被包围在
""
之中,这样就可以保留命令行执行结果的空白
1 | echo "what date it is? $(date +%F)" |
1.4 Process Substitution
进程替换(Process Substitution)的作用有点类似管道,但在实现方式上有所区别,管道是作为子进程的方式来运行的,而进程替换会在/dev/fd/
下面产生类似/dev/fd/63
,/dev/fd/62
这类临时文件,用来传递数据。用法如下:
<(command)
:用来产生标准输出,借助输入重定向,它的结果可以作为另一个命令的输入cat <(ls)
>(command)
:用来接收标准输入,借助输出重定向,它可以接收另一个命令的输出结果ls > >(cat)
- 注意
<
、>
与(
之间不能有空格
1 | # 将 <(ls) 当做临时文件,文件内容是ls的结果,cat这个临时文件 |
典型示例,统计文件个数
1 | # 正确方式 |
进程替换和管道的差异,Process substitution and pipe:
-
Multiple process inputs
:进程替换可以同时处理多路输入,而管道不行。例如:1
diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)
-
Preserving stdin
:进程替换可以保留标准输入,而管道不行。例如:1
2
3
4
5
6
7
8
9
10
11cat > script.sh << 'EOF'
#/bin/bash
read LINE
echo "You said ${LINE}!"
EOF
# wrong way
cat script.sh | bash
# right way
bash <(cat script.sh) -
Outbound process substitution
:进程替换可以将标准输出和标准异常导向不同的程序,而管道不行。例如:1
2
3
4
5
6
7
8
9
10
11
12
13cat > main.cp << 'EOF'
#include <iostream>
int main() {
std::cout << "This is normal output" << std::endl;
std::cerr << "This is error output" << std::endl;
return 0;
}
EOF
gcc -o main -lstdc++ -std=gnu++11 main.cpp
./main 1> >(sed -n '/output/s/^.*$/Find stdout/p') 2> >(sed -n '/output/s/^.*$/Find stderr/p') -
管道中对于变量的修改,流程的控制比如
continue
、break
、return
对于外部都是不生效的,例如: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
32function return_inside_pipe() {
echo "return_inside_pipe"
content="a\nb\nc"
for((i=0;i<3;i++))
do
echo -e "${content}" | while IFS= read -r item
do
echo "item=${item}"
if [ "${item}" == "b" ]; then
return
fi
done
done
}
function return_inside_substitution() {
echo "return_inside_substitution"
content="a\nb\nc"
for((i=0;i<3;i++))
do
while IFS= read -r item
do
echo "item=${item}"
if [ "${item}" == "b" ]; then
return
fi
done < <(echo -e "${content}")
done
}
return_inside_pipe
return_inside_substitution
1.5 Env
1.5.1 Pass to specific program
1 | FOO=bar env | grep FOO |
1.5.2 Pass to all programs of the pipe
1 | FOO=bar bash -c 'somecommand someargs | somecommand2' |
1.5.3 Pass to all programs of the session
1 | export FOO=bar |
1.5.4 Frequently-used env variables
SHELL
:当前使用的shellTERM
LANG
:语言Locale Category
LC_ALL
: Overrides all other individual locale-related variables and sets a specific locale for all aspects of the system.LC_COLLATE
: Specifies the collation order used for sorting and comparison of strings.LC_CTYPE
: Defines the character classification and case conversion rules.LC_MESSAGES
: Sets the language used for system messages and the output of certain programs.LC_MONETARY
: Determines the formatting of monetary values (currency symbols, decimal separators, etc.).LC_NUMERIC
: Defines the formatting of numeric values (decimal point, thousands separator, etc.).LC_TIME
: Specifies the format used for date and time representation.
2 Special Symbols
shell中的特殊符号包括如下几种
#
:注释符号(Hashmark[Comments])- 在shell文件的行首,作为shebang标记,
#!/bin/bash
- 其他地方作为注释使用,在一行中,#后面的内容并不会被执行
- 但是用单/双引号包围时,#作为#号字符本身,不具有注释作用
- 在shell文件的行首,作为shebang标记,
;
:分号,作为多语句的分隔符(Command separator [semicolon])- 多个语句要放在同一行的时候,可以使用分号分隔
;;
:连续分号(Terminator [double semicolon])- 在使用case选项的时候,作为每个选项的终结符
.
:点号(dot command [period])- 相当于
bash
内建命令source
- 作为文件名的一部分,在文件名的开头
- 作为目录名,一个点代表当前目录,两个点号代表上层目录(当前目录的父目录)
- 正则表达式中,点号表示任意一个字符
- 相当于
"
:双引号(partial quoting [double quote])- 双引号包围的内容可以允许变量扩展,允许转义字符的存在。如果字符串内出现双引号本身,需要转义。因此不一定双引号是成对的
'
:单引号(full quoting [single quote])- 单引号内的禁止变量扩展,不允许转义字符的存在,所有字符均作为字符本身处理(除单引号本身之外)。单引号必须成对出现
,
:逗号(comma operator [comma])- 用在连接一连串的数学表达式中,这串数学表达式均被求值,但只有最后一个求值结果被返回。例如
echo $[1+2,3+4,5+6]
返回11 - 用于参数替代中,表示首字母小写,如果是两个逗号,则表示全部小写。这个特性在
bash version 4
的时候被添加的(Mac OS不支持)a="ATest";echo ${a,}
输出aTesta="ATest";echo ${a,,}
输出atest
- 用在连接一连串的数学表达式中,这串数学表达式均被求值,但只有最后一个求值结果被返回。例如
\
:反斜线,反斜杆(escape [backslash])- 放在特殊符号之前,转义特殊符号的作用,仅表示特殊符号本身,这在字符串中常用
- 放在一行指令的最末端,表示紧接着的回车无效(其实也就是转义了Enter),后继新行的输入仍然作为当前指令的一部分
/
:斜线,斜杆(Filename path separator [forward slash])- 作为路径的分隔符,路径中仅有一个斜杆表示根目录,以斜杆开头的路径表示从根目录开始的路径
- 在作为运算符的时候,表示除法符号
`
:反引号,后引号(Command substitution[backquotes])- 命令替换。这个引号包围的为命令,可以执行包围的命令,并将执行的结果赋值给变量
:
:冒号(null command [colon])- 空命令,这个命令什么都不做,但是有返回值,返回值为0
- 可做while死循环的条件
- 在if分支中作为占位符(即某一分支什么都不做的时候)
- 放在必须要有两元操作的地方作为分隔符
- 可以作为域分隔符,比如环境变量$PATH中,或者passwd中,都有冒号的作为域分隔符的存在
!
:感叹号,取反一个测试结果或退出状态(reverse (or negate) [bang],[exclamation mark])- 表示反逻辑,例如
!=
表示不等于
- 表示反逻辑,例如
*
:星号(wildcard/arithmetic operator[asterisk])- 作为匹配文件名扩展的一个通配符,能自动匹配给定目录下的每一个文件
- 正则表达式中可以作为字符限定符,表示其前面的匹配规则匹配任意次
- 算术运算中表示乘法
**
:双星号(double asterisk)- 算术运算中表示求幂运算
?
:问号(test operator/wildcard[Question mark])- 表示条件测试
- 条件语句(三元运算符)
- 参数替换表达式中用来测试一个变量是否设置了值,例如
echo ${a?}
,若a已经设定过值,则与echo ${a}
相同,否则会出现异常信息parameter null or not set
- 作为通配符,用于匹配文件名扩展特性中,用于匹配单个字符
- 正则表达式中,表示匹配其前面规则0次或者1次
$
:美元符号(Variable substitution[Dollar sign])- 作为变量的前导符,用作变量替换,即引用一个变量的内容
- 在正则表达式中被定义为行末
- 特殊变量
-
$*
:返回调用脚本文件的所有参数,且将所有参数作为一个整体(字符串)返回 -
$@
:返回调用脚本文件的所有参数,且返回的是一个列表,每个参数作为一项。如果原参数是一个包含空格的字符串,那么最好用"$@"
,这样会保留这个参数中的空格,而不是拆成多个参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25cat > test.sh << 'EOF'
#!/bin/bash
echo "Using \"\$*\":"
for a in "$*"; do
echo $a;
done
echo -e "\nUsing \$*:"
for a in $*; do
echo $a;
done
echo -e "\nUsing \"\$@\":"
for a in "$@"; do
echo $a;
done
echo -e "\nUsing \$@:"
for a in $@; do
echo $a;
done
EOF
bash test.sh one two "three four" -
$#
:表示传递给脚本的参数数量 -
$?
:此变量值在使用的时候,返回的是最后一个命令、函数、或脚本的退出状态码值,如果没有错误则是0,如果为非0,则表示在此之前的最后一次执行有错误 -
$$
:进程ID变量,这个变量保存了运行当前脚本的进程ID值
-
()
:圆括号(parentheses)- 命令组(Command group)。由一组圆括号括起来的命令是命令组,命令组中的命令是在子shell(subshell)中执行。因为是在子shell内运行,因此在括号外面是没有办法获取括号内变量的值,但反过来,命令组内是可以获取到外面的值,这点有点像局部变量和全局变量的关系,在实作中,如果碰到要cd到子目录操作,并在操作完成后要返回到当前目录的时候,可以考虑使用subshell来处理
- 用于数组初始化
{}
:代码块(curly brackets)- 这个是匿名函数,但是又与函数不同,在代码块里面的变量在代码块后面仍能访问
[]
:中括号(brackets)[
是bash的内部命令(注意与[[
的区别)- 作为test用途,不支持正则
- 在数组的上下文中,表示数组元素的索引,方括号内填上数组元素的位置就能获得对应位置的内容,例如
${ary[1]}
- 在正表达式中,方括号表示该位置可以匹配的字符集范围
[[]]
:双中括号(double brackets)[[
是 bash 程序语言的关键字(注意与[
的区别)- 作为test用途。
[[]]
比[]
支持更多的运算符,比如:&&,||,<,>
操作符。同时,支持正则,例如[[ hello = hell? ]]
- bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码
$[...]
:表示整数扩展(integer expansion)- 详见数值运算
(())
:双括号(double parentheses)- 详见数值运算
> >& >> < &< << <>
:重定向(redirection)- 详见I/O重定向
|
:管道- 详见管道
(command)> <(command)
:进程替换(Process Substitution)- 详见进程替换
&& ||
:逻辑操作符- 在测试结构中,可以用这两个操作符来进行连接两个逻辑值
&
:与号(Run job in background[ampersand])- 如果命令后面跟上一个&符号,这个命令将会在后台运行
-
:命令中的单个-
表示标准输入或输出。具体代表标准输入还是标准输出取决于程序本身cat -
:此时,-
代表标准输入echo "This is Test" | socat - /tmp/hello.html
:此时,-
代表标准输出socat - /tmp/hello.html
:此时,-
代表标准输入
3 Data Type
3.1 Numerical Calculation
3.1.1 $[]
整数扩展,会返回执行后的结果。如果有,
分隔,那么只返回最后一个表达式执行的结果
1 | echo $[ 1 + 3 ] |
3.1.2 $(())
$(())
有如下两个功能
- 数值计算
- 进制转换
- 在
$(())
中的变量名称,可于其前面加$符号来替换,也可以不用
1 | # 数值计算 |
3.1.3 (())
整数扩展,只计算,不返回值。通常用于重定义变量值,只有赋值语句才能起到重定义变量的作用
1 | a=1 |
3.1.4 expr
expr
是一个用于数值计算的命令,运算符号两边必须加空格,不加空格会原样输出,不会计算
1 | expr 1 + 3 |
3.1.5 bc
浮点数运算:
1 | echo "scale=2; 1/3" | bc |
浮点数比较:
1 | if [ $(echo "1.2 < 1.212" | bc) -eq 1 ]; then |
3.2 String
3.2.1 Concat
1 | your_name="qinjx" |
3.2.2 Length
1 | text="abcdefg" |
3.2.3 Substring Removal
下面以字符串http://www.aaa.com/123.htm
为例,介绍几种不同的截取方式
#
:删除左边字符,保留右边字符
- 其中
var
是变量名,#
是运算符,*
是通配符,表示从左边开始删除第一个//
号及左边的所有字符。即删除http://
,结果是www.aaa.com/123.htm
1 | var='http://www.aaa.com/123.htm' |
##
:删除左边字符,保留右边字符
- 其中
var
是变量名,##
是运算符,*
是通配符,表示从左边开始删除最后(最右边)一个/
号及左边的所有字符。即删除http://www.aaa.com/
,结果是123.htm
1 | var='http://www.aaa.com/123.htm' |
%
:删除右边字符,保留左边字符
- 其中
var
是变量名,%
是运算符,*
是通配符,表示从从右边开始,删除第一个/
号及右边的字符。即删除/123.htm
,结果是http://www.aaa.com
1 | var='http://www.aaa.com/123.htm' |
%%
:删除右边字符,保留左边字符
- 其中
var
是变量名,%%
是运算符,*
是通配符,表示从右边开始,删除最后(最左边)一个/
号及右边的字符。即删除//www.aaa.com/123.htm
,结果是http:
1 | var='http://www.aaa.com/123.htm' |
从左边第几个字符开始,从左往右截取若干个字符
- 其中
var
是变量名,0
表示从左边第1
个字符开始,5
表示截取5
个字符,结果是http:
1 | var='http://www.aaa.com/123.htm' |
从左边第几个字符开始,从左往右截取,一直到结束
- 其中
var
是变量名,7
表示从左边第8
个字符开始,结果是www.aaa.com/123.htm
1 | var='http://www.aaa.com/123.htm' |
从右边第几个字符开始,从左往右截取若干个字符
- 其中
var
是变量名,0-7
表示从右边第7
字符开始,3
表示截取3
个字符,结果是123
1 | var='http://www.aaa.com/123.htm' |
从右边第几个字符开始,从左往右截取,一直到结束
- 其中
var
是变量名,0-7
表示从右边第7
字符开始,结果是123.htm
1 | var='http://www.aaa.com/123.htm' |
3.2.4 Substring Replacement
${variable/pattern/string}
: Replaces the first match ofpattern
withstring
.${variable//pattern/string}
: Replaces all matches ofpattern
withstring
.${variable/#pattern/string}
: Ifpattern
matches the beginning of$variable
, it’s replaced withstring
.${variable/%pattern/string}
: Ifpattern
matches the end of$variable
, it’s replaced withstring
.
1 | ips=( "1.1.1.1" "2.2.2.2" "3.3.3.3" ) |
3.2.5 Conditional Assignment
变量为空时,返回默认值
1 | echo ${FOO:-val2} # 输出val2 |
变量为空时,将变量设置为默认值,然后返回变量的值
1 | echo ${FOO:=val2} # 输出val2 |
变量不为空时,返回默认值
1 | FOO=val1 |
当变量为空时,输出错误信息并退出
1 | echo ${FOO:?error} # 输出error |
3.2.6 Read by Line
方式1
1 | # 文件名不包含空格的话,可以不要引号 |
方式2
- 注意,在这种方式下,在while循环内的变量的赋值会丢失,因为管道相当于开启另一个进程
1 | STDOUT | while read line |
3.2.7 Contains
方式1:利用运算符=~
1 | str1="abcd" |
方式2:利用通配符
1 | str1="abcd" |
方式3:利用grep
1 | str1="abcd" |
3.2.8 trim
3.2.8.1 Method 1
1 | function trim() { |
3.2.8.2 Method 2
1 | function trim() { |
3.2.9 Case Conversion
1 | echo 'hello' | tr 'a-z' 'A-Z' |
3.2.10 Extract
1 | var='[hello]' |
3.2.11 String with special character
1 | var="SELECT * FROM t0" |
3.2.12 Array to String
1 | items=( "hello world" "how have you been" ) |
3.2.13 Multi-line Content
1 | item="something" |
3.3 Array
Shell 数组用括号来表示,元素用空格
符号分割开,语法格式如下:
1 | array_name=() |
3.3.1 Operations
3.3.1.1 Get All Elements
1 | ${array[@]} |
3.3.1.2 Get Length
1 | ${#array[@]} |
3.3.1.3 Get All Indexes
1 | ${!array[@]} |
3.3.1.4 Combine Arrays
1 | array1=(1 2 3) |
3.3.1.5 Append Elements
1 | # 方法1 |
示例:
1 | text='my name is liuye' |
3.3.2 Start Index
sh/bash
:数组下标从0开始zsh
:数组下标从1开始
要始终获得一致的行为,请使用:${array[@]:offset:length}
offset
:起始索引,sh/bash/zsh
都是从0
开始length
:取多少个元素。若length = 1
,那么只取某个元素;若length > 1
,那么取的是一个子集- 不写length表示直到最后一个元素
1 | array=(1 2 3 4 5 6 7 8 9 10) |
3.3.3 Collection Calculation
假设F1
和F2
是两个数组
F1和F2的交集
1 | F1=( 1 2 3 ) |
F1和F2的并集
1 | F1=( 1 2 3 ) |
F1和F2的并集-F1和F2的交集,即差集
1 | F1=( 1 2 3 ) |
3.3.4 Iterate Array
1 | array=(1 2 3 4 5) |
1 | array=(1 2 3 4 5) |
3.3.4.1 Array Delimiter
如果数组中的内容包含空白,foreach
默认会以空格作为分隔符,这就有可能破坏数组元素原有的结构,例如
1 | array=("Hello Foo" "I'm Bar!") |
我们可以通过修改IFS
来修改foreach
的默认行为
1 | array=("Hello Foo!" "I'm Bar!") |
3.3.5 Echo the array with a specified delimiter
1 | # echo not respect IFS |
3.4 Map
Shell map用括号来表示,元素用空格
符号分割开,语法格式如下:
1 | declare -A map_name=([key1]=value1 ... [keyn]=valuen) |
属性
@
或*
可以获取map中的所有value
1 | ${map[@]} |
- 获取map长度的方法与获取字符串长度的方法相同,即利用
#
1 | ${#map[@]} |
!
可以获取map中关键字集合
1 | ${!map[@]} |
示例:
1 | declare -A map |
4 Condition
4.1 test
4.1.1 Filetype
关于某个文件名的文件类型判断,如test -e filename
-e
:该名字对应的文件或目录是否存在-f
:该名字对应的文件是否存在-d
:该名字对应的目录是否存在-s
:该名字对应的文件或目录是否存在且不为空- 如果名字对应的是一个已存在的文件,那判断文件内容是否为空,非空返回真
- 如果名字对应的是一个已存在的目录,那返回真
- 如果名字不存在,返回假
-b
:该文件名是否存在且为一个block device设备-c
:该文件名是否存在且为一个character device设备-S
:该文件名是否存在且为一个Socket文件-p
:该文件名是否存在且为以FIFO(pipe)文件-L
:该文件名是否存在且为一个链接文件
4.1.2 Permission
关于文件的权限检测,如test -r filename
-r
:检测该文件名是否存在且具有"可读"的权限-w
:检测该文件名是否存在且具有"可写"的权限-x
:检测该文件名是否存在且具有"可执行"的权限-u
:检测该文件名是否存在且具有"SUID"的属性-g
:检测该文件名是否存在且具有"GUID"的属性-k
:检测该文件名是否存在且具有"Sticky bit(SBIT)"的属性-s
:检测该文件名是否存在且为"非空白文件"
两个文件之间的比较,如test file1 -nt file2
-nt
:(newer than) 判断file1是否比file2新-ot
:(older than) 判断file1是否比file2旧-ef
:判断file1与file2是否为同一文件,可用在判断hard link的判断上,主要意义在于判断两个文件是否均指向同一个inode
4.1.3 Number
数值比较,如test n1 -eq n2
-eq
:两数值相等(equal)-ne
:两数值不等(not equal)-gt
:n1大于n1(greater than)-lt
:n1小于n2(less than)-ge
:n1大于等于n2(greater than or equal)-le
:n1小于n2(less than or equal)
4.1.4 String
字符串比较,例如test n1 = n2
-z
:判定字符串是否为空,空返回true- 在
[]
中,变量需要加引号,例如[ -z "${a}" ]
- 在
[[]]
中,变量不需要加引号,例如[[ -z ${a} ]]
- 在
-n
:判断字符串是否为空,非空返回true- 在
[]
中,变量需要加引号,例如[ -n "${a}" ]
- 在
[[]]
中,变量不需要加引号,例如[[ -n ${a} ]]
- 在
=
:判断str1是否等于str2!=
:判断str1是否等于str2- 字符串变量的引用方式:
"${var_name}"
,即必须加上双引号""
4.1.5 Logical
逻辑与/或/非
-a
:两个条件同时成立,返回true,如test -r file1 -a -x file2-o
:任一条件成立,返回true,如test -r file1 -o -x file2!
:反向状态
4.2 []
判断符号[]
与判断式test
用法基本一致,有以下几条注意事项
- 如果要在bash的语法当中使用括号作为shell的判断式时,必须要注意在中括号的两端需要有空格符来分隔
- 逻辑与逻辑或,用的是
&&
和||
,且需要将条件分开写,如下[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]
- 不可以使用通配符
- 仅支持
=
作为相等比较的运算符,不支持==
(取决于shell的实现,bash
就支持这两种,但是zsh
对语法更严格,仅支持=
)
示例:
1 | # 正确写法 |
4.3 [[]]
判断符号[[]]
与判断式test
用法基本一致,有以下几条注意事项
- 如果要在bash的语法当中使用括号作为shell的判断式时,必须要注意在中括号的两端需要有空格符来分隔
- 逻辑与逻辑或,用的是
&&
和||
,用一个[]
或者用两个[[]]
都可以,但使用[[]]
时不能用-a
和-o
- 可以使用通配符
- 支持
=
与==
作为相等比较的运算符
示例:
1 | # 正确写法 |
4.4 Command
我们可以用如下方式来判断命令执行是否成功:
1 | if <cmd> [<options>]; then |
例如:
1 | if ls ~ > /dev/null; then |
5 Control Flow
5.1 if
1 | if condition |
5.2 if else
1 | if condition |
5.3 if else-if else
1 | if condition1 |
5.4 for
1 | for var in item1 item2 ... itemN |
1 | for ((i=1;i<=10;i++)) |
5.5 while
1 | while condition |
5.6 until
1 | until condition |
5.7 case
1 | case 值 in |
5.7.1 Determine if numerical number
1 | function isNumber() { |
6 Function
6.1 Pass Parameter
传递数组
- 用
""
将数组转化成字符串
1 | function func() { |
6.2 Standard Output
返回数组
1 | function func() { |
6.3 Return Value
1 | function func() { |
7 builtin
bash shell
的命令分为两类:外部命令和内部命令。外部命令是通过系统调用或独立的程序实现的,如sed
、awk
等等。内部命令是由特殊的文件格式(.def
)所实现,如cd
、history
、exec
等等
通过man builtins
查看说明文档
7.1 shift
shift用于移动参数的位置
格式:
shift [n]
:n是数字,默认是1
1 | eval set -- a b c d |
7.2 eval
通过连接参数构造命令,如果包含间接引用,也会保持原有语义,下面以一个例子来说明
1 | foo=10 x=foo |
7.3 set
格式:
set [option]
参数说明:
-e
:当任意一个命令的返回值为非0时,立即退出-x
:将每个命令及其详细参数输出到标准输出中-o pipefail
:针对管道命令,取从右往左第一个非零返回值作为整个管道命令的返回值-o vi/emacs
:vi
模式或emacs
模式,默认是emacs
模式-H
:Enable!
style history substitution
示例:
set -e
set -x
set -o pipefail
eval set -- "some new params"
:设置当前shell的参数
7.4 source
执行shell
脚本有2种方式:
./script.sh
或者sh script.sh
- 当前
shell
是父进程,生成一个子shell
进程,在子shell
中执行脚本。脚本执行完毕,退出子shell
,回到当前shell
- 当前
source script.sh
或者. script.sh
- 在当前上下文中执行脚本,不会生成新的进程。脚本执行完毕,回到当前
shell
- 在当前上下文中执行脚本,不会生成新的进程。脚本执行完毕,回到当前
示例:
1 | cat > test.sh << 'EOF' |
7.5 exec
exec
用于进程替换(类似系统调用exec
),或者标准输入输出的重定向
示例:
exec 1>my.log 2>&1
:将标准输出、以及标准异常重定向到my.log文件中,对后续的所有命令都生效
7.5.1 Work with pipe
1 |
|
文件描述符用变量替代的版本1:
- 在
exec
中用{}
而不是${}
来引用变量
1 |
|
文件描述符用变量替代的版本2:
- 使用
eval
1 |
|
7.6 shopt
用于启用/禁用shell扩展功能
示例:
shopt -s extglob
:启用extglob
shopt -u extglob
:禁用extglob
7.7 read
格式:
read [-ers] [-a name] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
参数说明:
-a
:后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符
-d
:后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志
-p
:后面跟提示信息,即在输入前打印提示信息
-e
:在输入的时候可以使用命令补全功能
-n
:后跟一个数字,定义输入文本的长度,很实用
-r
:屏蔽\
,如果没有该选项,则\
作为一个转义字符,有的话\
就是个正常的字符了
-s
:安静模式,在输入字符时不再屏幕上显示,例如login时输入密码
-t
:后面跟秒数,定义输入字符的等待时间
-u
:后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的
简单读取
1 |
|
-p
参数,允许在read
命令行中直接指定一个提示
1 |
|
-t
参数指定read
命令等待输入的秒数,当计时满时,read
命令返回一个非零退出状态
1 |
|
-n
参数设置read
命令计数输入的字符。当输入的字符数目达到预定数目时,自动退出,并将输入的数据赋值给变量
1 |
|
-s
选项能够使read
命令中输入的数据不显示在命令终端上(实际上,数据是显示的,只是read
命令将文本颜色设置成与背景相同的颜色)。输入密码常用这个选项
1 |
|
按行读取文件
1 |
|
7.8 getopts
格式:getopts [option[:]] VARIABLE
option
:选项,为单个字母:
:如果某个选项(option)后面出现了冒号(:
),则表示这个选项后面可以接参数VARIABLE
:表示将某个选项保存在变量VARIABLE
中
getopts
是linux系统中的一个内置变量,一般用在循环中。每当执行循环时,getopts
都会检查下一个命令选项,如果这些选项出现
在option中,则表示是合法选项,否则不是合法选项。并将这些合法选项保存在VARIABLE
这个变量中
getopts
还包含两个内置变量,及OPTARG
和OPTIND
OPTARG
:选项后面的参数OPTIND
:下一个选项的索引(该索引是相对于$*
的索引,因此如果选项有参数的话,索引是非连续的)
示例::getopts ":a:bc:" opt
(参数部分:-a 11 -b -c 5
)
- 第一个冒号表示忽略错误
- 字符后面的冒号表示该选项必须有自己的参数
$OPTARG
存储相应选项的参数,如例中的11
、5
两个参数$OPTIND
总是存储原始$*
中下一个要处理的选项的索引(注意不是参数,而是选项),此处指的是a
,b
,c
这三个选项(而不是那些数字,当然数字也是会占有位置的)的索引OPTIND
初值为1,遇到x
(选项不带参数),则OPTIND += 1
;遇到x:
(选项带参数),则OPTARG
=argv[OPTIND+1],OPTIND += 2
1 |
|
1 | # case0 |
注意:如果getopts置于函数内部时,getopts解析的是函数的所有入参,可以通过$@
将脚本的所有参数传递给函数
7.9 getopt
格式:getopt [options] -- parameters
参数说明:
-o, --options <选项字符串>
:要识别的短选项- 单个字符表示选项。例如
-o "abc"
,a
、b
、c
表示3个选项 - 第一个冒号
:
表示忽略错误 - 单个字符后接一个冒号
:
,表示该选项后必须跟一个参数,参数紧跟在选项后或者以空格隔开。例如-o a:bc
,选项a
必须要有参数,选项b
、c
无需参数 - 单个字符后接两个冒号
::
,表示该选项后必须跟一个参数,且参数必须紧跟在选项后不能以空格隔开。例如-o a::bc
- 单个字符表示选项。例如
-a, --alternative
:允许长选项以-
开始,否则默认长选项要求以--
开头-l, --longoptions <长选项>
:要识别的长选项- 只有
-l
选项,无-o
选项时,-l
选项无效(如果没有短选项,可以加上-o ''
或者-o ':'
) - 以逗号
,
分隔的长字符串表示选项。例如-l "along,blong"
,along
、blong
表示2个选项 - 字符串后接一个冒号
:
,表示该选项后必须跟一个参数,参数和选项必须用空格隔开(仅有这一条冒号规则)
- 只有
-n, --name <程序名>
:将错误报告给的程序名
输出:getopt会将参数项进行重组和排序,会分成两组,以--
符号分隔
--
之前是合法的选项
、参数
集合--
之后是多余的参数
集合,注意,这里不包含非法选项
1 | getopt -o 'a' -- -a |
1 | # 当没有-o选项时,-l无效 |
示例:
1 | cat > test.sh << 'EOF' |
7.10 printf
主要用于格式转换,以及格式化输出,当入参是数组的时候,会对数组的每个元素应用指定的格式化模式
1 | printf %x 255 |
7.11 declare
declare
用于定义变量、增减属性、查看变量信息。若在函数内部使用declare
,那么默认是local
的
格式:declare [-/+ aAfFgilrtux] [-p] [name[=value] ...]
参数说明:
-/+
:-
增加属性,+
删除属性a
:数组A
:mapf
:函数i
:整数
-p
:查看变量信息,包括类型以及值
示例:
1 | # 查看函数定义 |
7.12 local
用于在函数内定义局部变量,其作用域就是函数本身
格式:local [option] [name[=value] ...]
,其中option
部分参考declare
即可
示例:
1 | function test() { |
7.13 typeset
功能属于declare
的子集,不推荐使用
7.14 alias
alias
用于设置别名
示例:
alias lh='ls -alt'
alias xxx="${BASE_DIR}/xxx.sh"
,${BASE_DIR}
的解析发生在配置时,在zsh
等shell中,xxx
会高亮alias xxx='${BASE_DIR}/xxx.sh'
,${BASE_DIR}
的解析发生在执行时,在zsh
等shell中,xxx
不会高亮
How to disable alias: \cp
will use the original cp
command, rather than the alias if there exists.
7.15 export
示例:
export -p
:打印所有导出的符号export <name>
:导出变量export -f <func_name>
:导出函数
7.16 ulimit
设置或查看各类限制
示例:
ulimit -a
ulimit -v 1000000
:将进程的最大内存设置为1000000
字节,仅在当前shell
以及shell
的子进程中生效
7.17 pushd & popd
pushd
和popd
是用于操作目录栈的命令,在切换目录时非常有用。它们可以让你在不同目录之间快速切换,并在需要时返回到先前的目录
示例:
1 | set -x |
8 Tips
8.1 Option Separator
选项分隔符为--
,它有什么用呢?
举个简单的例子,如何创建一个名为-f
的目录?mkdir -f
肯定是不行的,因为-f
会被当做mkdir命令的选项,此时我们就需要选项分隔符来终止mkdir
对于后续字符串的解析,即mkdir -- -f
8.2 Trap Signal
trap
常用来做一些清理工作,比如你在脚本中将一些进程放到后台执行,但如果脚本异常终止(比如用ctrl+c),那么这些后台进程可能得不到及时处理,这个时候就可以用trap
来捕获信号,从而执行清理动作
格式
trap "commands" signal-list
注意
- 如果commands中包含变量,那么该变量在执行
trap
语句时就已解析,而非到真正捕获信号的时候才解析
示例1
1 | # do other things |
示例2(错误),该示例与示例1的差别就是用sudo执行ping命令
- 这样是没法杀死
ping
这个后台进程的,因为ping_pid
变量获取到的并不是ping
的pid
,而是sudo
的pid
1 | # do other things |
实例3(对实例2进行调整)
1 | # do other things |
8.3 Color
8.3.1 ANSI escape codes
1 | NOCOLOR='\033[0m' |
8.3.2 tput
设置颜色:
1 | tput setab [1-7] # Set the background colour using ANSI escape |
其中颜色编号表如下:
1 | Num Colour #define R G B |
样式设置:
1 | tput bold # Select bold mode |
其他:
1 | tput sgr0 # Reset text format to the terminal's default |
示例:
1 | tput setaf 1; tput setab 2; tput bold; echo "this is text" |
9 Reference
- shell教程
- linux exec与重定向
- Shell脚本8种字符串截取方法总结
- shell的命令替换和命令组合
- shell脚本–数值计算
- Linux—shell中
$(())
、$()
与${}
的区别 - shell中各种括号的作用
()
、(())
、[]
、[[]]
、{}
- shell特殊符号用法大全
- shell的getopts命令
- 如何获取后台进程的PID?
- shell——trap捕捉信号(附信号表)
- Shell进程替换
- Bash scripting cheatsheet
- how-to-trim-whitespace-from-a-bash-variable
- How to change the output color of echo in Linux
- jonsuh/.bash_profile
- shell并行执行程序
- How to use a variable to indicate a file descriptor in bash?