本文
主要对shell脚本的基础语法知识进行记录。
背景
- 主机: Thinkpad S2
- 系统: Deepin GNU/Linux 15.11
- 内核: Debian 6.3.0-18+deb9u1
- shell:bash
学习网站
字符串截取
计数截取
- 左边开始计数:${string: start :length}
- start 是起始位置(从左边开始,从 0 开始计数)
- length 是要截取的长度(省略的话表示直到字符串的末尾)
1
2
3
|
url="c.biancheng.net"
echo ${url: 2: 9}
# 结果:biancheng
|
- 右边开始计数:${string: 0-start :length}
- 右边开始计数时,起始数字是 1
- 不管从哪边开始计数,截取方向都是从左到右。
1
2
3
|
url="c.biancheng.net"
echo ${url: 0-13: 9}
# 结果:biancheng
|
指定子字符串截取
- 截取右边字符:${string#*chars}
- #*chars 表示忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
- 当有多个匹配时,只从左到右第一个有效
1
2
3
|
url="http://c.biancheng.net/index.html"
echo ${url#*/}
#结果:/c.biancheng.net/index.html
|
- 截取右边字符:${string##*chars}
- ##*chars 表示忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
- 当有多个匹配时,只从左到右最后一个有效
1
2
3
|
url="http://c.biancheng.net/index.html"
echo ${url##*/}
#结果:index.html
|
- 截取左边字符:${string%chars*}
- %chars* 表示忽略右边的所有字符,直到遇见 chars(chars 不会被截取)
- 当有多个匹配时,只从右到左第一个有效
1
2
3
|
url="http://c.biancheng.net/index.html"
echo ${url%/*}
#结果:http://c.biancheng.net
|
- 截取左边字符:${string%%chars*}
- %%chars* 表示忽略右边的所有字符,直到遇见 chars(chars 不会被截取)
- 当有多个匹配时,只从右到左最后一个有效
1
2
3
|
url="http://c.biancheng.net/index.html"
echo ${url%%/*}
#结果:http:
|
总结
格式 |
说明 |
${string: start :length} |
从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。 |
${string: start} |
从 string 字符串的左边第 start 个字符开始截取,直到最后。 |
${string: 0-start :length} |
从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。 |
${string: 0-start} |
从 string 字符串的右边第 start 个字符开始截取,直到最后。 |
${string#*chars} |
从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string##*chars} |
从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string%*chars} |
从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
${string%%*chars} |
从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
字符串替换
简单的替换
- b=\({a/123/321};将\){a}里的第一个123替换为321
- b=\({a//123/321};将\){a}里的所有123替换为321
删除前后空格
1
2
3
4
5
6
|
a=" 123 456 "
# 方法一
b=`echo $a`
# 方法二
b=`echo $a | sed -r 's/^[ \t]*//g'` # 删除行首空格
b=`echo $b | sed -r 's/[ \t]*$//g'` # 删除行尾空格
|
sed与正则表达式
基础
- 命令格式:
- 面对文件:sed [options] ‘command’ file(s)
- 面对变量:echo $b | sed [options] ‘command’
- 常用选项:
-n :使用安静(silent)模式,只显示处理的行。
-r :支持延伸型正则表达式
-i :直接修改读取的文件内容,而不是输出到终端。
- 常用功能:
- s:替换字符串
- d:删除行
- a:新增行
- i:插入行
- c:替换行
- p:输出到终端
举例
1
2
3
4
|
sed 's/aa/AA/' test.txt # 替换aa为AA,注意每行只替换从左到右第一个匹配处
sed 's/aa/AA/g' test.txt # 替换aa为AA,注意每行替换所有匹配处
sed '5,$s/aa/AA/g' test.txt # 指定第5行至文件末尾为操作域
sed '/^[0-9]/s/aa/AA/g' test.txt # 指定数字开头的行为操作域
|
1
2
|
sed '1,4d' test.xx # 删除1至4行
sed '/^2/d' test.txt # 删除以数字2开头的行(//里内容是正则表达式,并且必要时可以添加-r选项)
|
1
2
|
sed '1a hello world' test.txt # 在第1行后面新增hello world(也就是处理后的第2行)
sed '1i hello world' test.txt # 在第1行前面插入hello world(也就是处理后的第1行)
|
1
2
|
sed '1c hello world' test.txt # 第1行替换为hello world
sed '/^2/c hello world' test.txt # 以数字2开头的行替换为hello world
|
1
2
|
sed -n '2p' test.txt # 把第二行输出到终端
sed -n -r '/^2/p' test.txt # 把以数字2开头的行输出到终端
|
- 文件修改的方法:
- sed ‘command’ file(s) > tmp_file (重定向到临时文件)
- sed -i ‘command’ file(s) (直接修改文件,最好处理前备份一下)
条件判断 if
if-elif-else-fi
1
2
3
4
5
6
7
|
if (( $age <= 18 )); then
echo "儿童"
elif (( $age > 18 && $age <= 40 )); then
echo "青年"
else
echo "老年"
fi
|
数值比较
1
2
3
4
5
6
|
a=100
b=50
c=$(($a + $b))
d=$((100 + 50))
echo $c
echo $d
|
- 数值的比较使用(()):支持 >、>=、<、<=、==、!=
1
2
3
4
5
6
7
|
a=100
b=50
if (($a > $b));then
echo "max is a"
else
echo "max is b"
fi
|
字符串比较
- 字符表达式的比较使用 [[]]:支持 =、!=、-z 和 &&、|| 、! 运算符 (注意,中括号与表达式间要保留空格)
1
2
3
4
5
|
# str1 或 str2 非空
if [[ -z $str1 || -z $str2 ]]
# 支持通配符
if [[ hest = h??t ]] # ? 表示任意字符
if [ hest = h*t ]] # * 表示任意个任意字符
|
- 支持正则表达式:=~
- ^ 匹配字符串的开头(一个位置);
- [0-9]{10} 匹配连续的十个数字;
- $匹配字符串的末尾(一个位置)
1
2
3
4
5
6
|
if [[ $tel =~ ^1[0-9]{10}$ ]]
then
echo "你输入的是手机号码"
else
echo "你输入的不是手机号码"
fi
|
文件表达式
文件表达式的测试使用 [[]] ,其运算符如下:
表达式 |
含义 |
-e filename |
如果 filename 存在,则为真 |
-d filename |
如果 filename 为目录,则为真 |
-f filename |
如果 filename 为常规文件,则为真 |
-L filename |
如果 filename 为符号链接,则为真 |
-r filename |
如果 filename 可读,则为真 |
-w filename |
如果 filename 可写,则为真 |
-x filename |
如果 filename 可执行,则为真 |
filename1 -nt filename2 |
如果 filename1 比 filename2 新,则为真 |
filename1 -ot filename2 |
如果 filename1 比 filename2 旧,则为真 |
循环for和while
for
1
2
3
4
|
for line in `cat filename` # 或 for line in $(cat filename)
do
echo $line
done
|
这里有时候会出现问题,bash没有将换行符作为一行的分隔符,而是将空格作为了分隔符。这时候需要更改shell的系统变量IFS。
1
2
3
|
IFS='\n' # 无效
IFS=$"\n" # 无效
IFS=$'\n' # 有效
|
以上这三个赋值看起来都比较像”将换行符赋值给IFS“,但实际上只有最后一种写法才是我想要的结果,一定要注意。
- IFS=‘\n’ //将字符n作为IFS的换行符。
- IFS=$”\n” //这里\n确实通过$转化为了换行符,但仅当被解释时(或被执行时)才被转化为换行符。
- IFS=$’\n’ //这才是真正的换行符。
while
1
2
3
4
|
while read -r line
do
echo $line
done < filename
|
break与continue
- break:表示跳出当前的整个循环。break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
1
2
3
4
5
6
7
|
while read n; do
if((n>0)); then
((sum+=n))
else
break
fi
done
|
- continue:表示过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
1
2
3
4
5
6
|
while read n; do
if((n<1 || n>100)); then
continue
fi
((sum+=n))
done
|
重定向
输出重定向
1
|
echo $str >>demo.txt #将输入结果以追加的方式重定向到文件
|
类 型 |
符 号 |
作 用 |
标准输出重定向 |
command >file |
以覆盖的方式,把 command 的正确输出结果输出到 file 文件中。 |
标准输出重定向 |
command »file |
以追加的方式,把 command 的正确输出结果输出到 file 文件中。 |
标准错误输出重定向 |
command 2>file |
以覆盖的方式,把 command 的错误信息输出到 file 文件中。 |
标准错误输出重定向 |
command 2»file |
以追加的方式,把 command 的错误信息输出到 file 文件中。 |
正确输出和错误信息同时保存 |
command >file 2>&1 |
以覆盖的方式,把正确输出和错误信息同时保存到同一个文件(file)中。 |
正确输出和错误信息同时保存 |
command »file 2>&1 |
以追加的方式,把正确输出和错误信息同时保存到同一个文件(file)中。 |
正确输出和错误信息同时保存 |
command >file1 2>file2 |
以覆盖的方式,把正确输出保存到 file1 文件,把错误信息保存到 file2 文件中。 |
正确输出和错误信息同时保存 |
command »file1 2»file2 |
以追加的方式,把正确的输出结果输出到 file1 文件中,把错误信息输出到 file2 文件中 |
输入重定向
1
2
3
|
while read str; do
echo $str
done <readme.txt
|
符号 |
说明 |
command <file |
将 file 文件中的内容作为 command 的输入。 |
command «END |
从标准输入(键盘)中读取数据,直到遇见分界符 END 才停止(分界符可以是任意的字符串,用户自己定义)。 |
command file2 |
将 file1 作为 command 的输入,并将 command 的处理结果输出到 file2。 |
case-esac
以读取命令行参数为例:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
usage()
{
echo "usage: $0 [--testname testname] [--basetest btname] [--dump] [--debug] [--simpath simlogpath] [--nocmp] [--seed seednum] [--cov] [--funcov] [--xrun]"
}
while [[ x$1 != x]]; do
case $1 in
--testname )
shift
testname=$1
;;
--basetest )
shift
btname=$1
;;
--dump )
dump=1
;;
--debug )
debug=1
;;
--simpath )
shift
simlogpath=$1
;;
--nocmp )
nocmp=1
;;
--seed )
shift
seednum=$1
;;
--cov )
cov=1
;;
--funcov )
funcov=1
;;
--xrun )
xrun=1
;;
-h )
usage
exit 0
;;
* )
usage
exit 0
;;
esac
shift
done
|
特殊变量
变量 |
含义 |
$0 |
当前脚本的文件名。 |
$n(n≥1) |
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。 |
$# |
传递给脚本或函数的参数个数。 |
$* |
传递给脚本或函数的所有参数。 |
$@ |
传递给脚本或函数的所有参数。当被双引号” “包含时,$@ 与 $* 稍有不同,我们将在《Shell $*和$@的区别》一节中详细讲解。 |
$? |
上个命令的退出状态,或函数的返回值,我们将在《Shell $?》一节中详细讲解。 |
$$ |
当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。