Skip to content

Latest commit

History

History
879 lines (656 loc) · 22.5 KB

File metadata and controls

879 lines (656 loc) · 22.5 KB

Linux_Shell

Shell 是一个命令行解释器,它接收应用程序/用户的命令,然后调用操作系统内核。此外,Shell也是一个编程语言,也会有诸如流程控制、函数等概念。

Shell脚本入门

  1. 脚本格式

    #!/bin/bash
    ...
    ...

    脚本以#!/bin/bash开头,指定解析器。

  2. 编写Hello Worldvim helloworld.sh

    #!/bin/bashecho"helloworld"
  3. 执行脚本

    • 1、采用 bashsh 后跟脚本的相对路径或绝对路径(不用赋予脚本+x 权限)
    # 相对路径sh、bash
    [root@localhost shells]$ sh ./helloworld.sh
    helloworld
    [root@localhost shells]$ bash ./helloworld.sh
    helloworld
    # 绝对路径 sh、bash
    [root@localhost shells]$ sh /mydata/helloworld.sh
    helloworld
    [root@localhost shells]$ bash /mydata/helloworld.sh
    helloworld

    这种执行方式,本质是由bash解析器执行脚本,所以脚本本身不需要执行权限。

    • 2、采用脚本的绝对路径或相对路径执行脚本(必须具有可执行权限+x)
    [root@localhost shells]$ chmod +x helloworld.sh
    # 相对路径
    [root@localhost shells]$ ./helloworld.sh
    helloworld
    # 绝对路径
    [root@localhost shells]$ /mydata/helloworld.sh
    helloworld

    这种执行方式,本质是脚本需要自己执行(“亲历亲为”),因此需要执行器权限。

    • 3、在脚本的路径前加上 . 或者 source
    [root@localhost shells]$ . helloworld.sh
    helloworld
  • 前两种方式都是在当前 Shell(Shell 进程)中 fork 一个子 Shell 来执行脚本内容,当脚本执行结束,子 Shell 关闭,回到父 Shell 中。

  • 而第三种方式,可以让脚本直接在当前 Shell 执行,无需 fork 子 Shell。典型例子是修改完/etc/profile文件后,需要source刷新一下环境配置文件。

  • 它们的区别在于,环境变量的继承关系。如果在子 Shell 中设置的变量,父 Shell 是不可见的。

变量

系统预定义变量

常见系统变量有$HOME$PWD$SHELL$USER等。

查看系统变量的值

[root@localhost shells]$ echo$HOME

列出当前 Shell 中所有变量

[root@localhost shells]$ set

用户自定义变量

语法:变量名=变量值,如age=18=前后不能有空格。

  • 定义变量时,变量名不需要加$,只有在使用时才会在变量名前加$

  • 变量名和等号之间不能有空格。

  • 变量命名规范

    • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。

    • 中间不能有空格,但可以使用下划线_连接。

    • 不能使用标点符号。

    • 不能使用bash里的关键字(可用help命令查看保留关键字)。

  • 变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界(推荐给所有变量加上花括号)。

  • 已定义的变量,可以被重新定义。

  • 变量的值如果有空格,需要使用引号将其括起来。

  • 在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算。

运行shell时,会同时存在三种变量:

  • 局部变量: 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

  • 环境变量: 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

  • shell变量: shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。

全局变量&只读变量&撤销变量

全局变量和系统变量通常划等号。

[root@localhost shells]$ B=9
# 将变量提升为全局变量,可以被其它 shell 程序访问
[root@localhost shells]$ export B
# 只读变量(静态变量)
[root@localhost shells]$ readonly name='Tommy'
[root@localhost shells]$ name='Jack'
-bash: name: readonly variable
# 定义变量
[root@localhost shells]$ age=18
[root@localhost shells]$ echo$age
18
# 撤销变量
[root@localhost shells]$ unset age
[root@localhost shells]$ echo$age

####特殊变量

  • trap

  • dirname

  • basename

  • envsubst

  • random/urandom

  • mknod/mkfifo

  • exec

  • pushd/popd

注释

# 单行注释<<EOF多行注释...EOF# EOF 也可以使用其他符号<<!多行注释...!

字符串

字符串可以用单引号,也可以用双引号,也可以不用引号。

  • 单引号

    • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。

    • 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

  • 双引号

    • 双引号里可以有变量。

    • 双引号里可以出现转义字符。

拼接字符串

your_name="runoob"# 使用双引号拼接,输出结果为:hello, runoob ! hello, runoob !
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"echo$greeting$greeting_1# 使用单引号拼接,输出结果为:hello, runoob ! hello, ${your_name} !
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'echo$greeting_2$greeting_3

获取字符串长度

string="abcd"echo${#string}# 输出 4# 变量为数组时,${#array} 等价于 ${#array[0]}:
array=(1 2 3 4)
echo${#array[0]}# 输出 1# 计算字符长度也可使用 length。
string="hello,everyone my name is mingming"# string字符串里边有空格,所以需要添加双引号
expr length "$string"# 输出 34# 使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身
expr 5+6 # 直接输出 5+6
expr 5 + 6 # 输出 11# 对于某些运算符,还需要我们使用符号"\"进行转义,否则就会提示语法错误
expr 5 * 6 # 输出错误
expr 5 \* 6 # 输出30

提取子字符串

# 以下实例从字符串第 2 个字符开始截取 4 个字符
string="runoob is a great site"echo${string:1:4}# 输出 unoo

查找子字符串

# 查找字符 i 或 o 的位置(哪个字母先出现就计算哪个)
string="runoob is a great site"echo`expr index "$string" io`# 输出 4

字符串截取

url=https://www.bilibili.com/123.html
# url 是变量名,# 是运算符,*// 表示从左边开始删除第一个 // 符号及左边的所有字符,即删除 https://echo${url#*//}# 结果是 :www.bilibili.com/123.html# ##*/ 表示从左边开始删除最后一个 / 符号及左边的所有字符,即删除 https://www.bilibili.com/echo${url##*/}# 结果是 123.htm# %/* 表示从右边开始,删除第一个 / 符号及右边的字符echo${url%/*}# 结果是:https://www.bilibili.com# %%/* 表示从右边开始,删除最后一个 / 符号及右边的字符echo${var%%/*}# 结果是:https:# 0 表示从左边第一个字符开始,5 表示字符的总个数echo${var:0:5}# 结果是:https:# 从左边第几个字符开始(从指定第几个字符 +1 开始),一直到结束echo${var:7}# 结果是 :www.bilibili.com/123.html# 0-7 表示从右边算起第七个字符结束,3 表示字符个数echo${var:0-7:3}# 结果是:123# 表示从右边第七个字符开始,一直到结束。(字符截取顺序从左到右)echo${var:0-7}# 结果是:123.htm
  • ###表示从左边开始删除,一个 # 表示从左边删除到第一个指定的字符;两个 # 表示从左边删除到最后一个指定的字符。

  • %%% 表示从右边开始删除。一个 % 表示从右边删除到第一个指定的字符;两个 % 表示从右边删除到最后一个指定的字符。

  • 删除包括了指定的字符本身。

字符串替换

# 使用 string/pattern/string(source/pattern/target) 进行首个 pattern 的替换
string="text, dummy, text, dummy"echo${string/text/TEXT}# TEXT, dummy, text, dummy# 使用 string//pattern/string 进行全部 pattern 的替换
string="text, dummy, text, dummy"echo${string//text/TEXT}# TEXT, dummy, TEXT, dummy

标准输入

获取键盘输入信息

语法:read [-options] [variable...]

# -p 参数由于设置提示信息read -p "input a val:" a #获取键盘输入的 a 变量数字read -p "input b val:" b #获取键盘输入的 b 变量数字
r=$[a+b] #计算a+b的结果 赋值给r 不能有空格echo"result = ${r}"#输出显示结果 r

参数传递

向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,其中 $0 为执行的文件名(包含文件路径),1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推......

[root@localhost shells]$ vim test.sh
echo"Shell 传递参数实例!";echo"执行的文件名: $0";echo"第一个参数为: $1";echo"第二个参数为: $2";echo"第三个参数为: $3";
[root@localhost shells]$ chmod +x test.sh [root@localhost shells]$ ./test.sh 1 2 3
Shell 传递参数实例!
执行的文件名: ./test.sh
第一个参数为: 1
第二个参数为: 2
第三个参数为: 3
参数表现形式说明
$#传递到脚本的参数个数
$*以一个单字符串显示所有向脚本传递的参数,如果$*存在于双引号内部,则其表现形式为"$1 $2 $3 ... $n"
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@$*相同,但如果$@存在于双引号内部,则其表现形式为"$1" "$2" ... "$n",数组形式
$-显示Shell使用的当前选项,与set命令功能相同
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误

演示

echo"Shell 传递参数实例!";echo"第一个参数为:$1";echo"参数个数为:$#";echo"传递的参数作为一个字符串显示:$*";
$ chmod +x test.sh $ ./test.sh 1 2 3
Shell 传递参数实例!
第一个参数为:1
参数个数为:3
传递的参数作为一个字符串显示:1 2 3

数组

bash 仅支持一维数组,且没有限定数组的容量。

数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。

在 Shell 中,用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为: 数组名=(值1 值2 ... 值n)

array_name=(value0 value1 value2 value3)
# 或者
array_name=(
value0
value1
value2
value3
)
# 还可以单独定义数组的各个分量。可以不使用连续的下标,而且下标的范围没有限制。
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

获取数组元素

# ${数组名[索引]}
value=${array_name[n]}# 使用 @ 符号可以获取数组中的所有元素echo${array_name[@]}

获取数组长度

# 获取数组长度的方法与获取字符串长度的方法相同# 取得数组元素的个数
length=${#array_name[@]}# 或者
length=${#array_name[*]}# 取得数组单个元素的长度
length=${#array_name[n]}

关联数组

其表现形式类似 map

语法:declare -A array_name-A 选项就是用于声明一个关联数组。关联数组的键是唯一的。

# 创建一个关联数组 site,并创建不同的键值declare -A site=(["A"]="1" ["B"]="2" ["C"]="3")
# 也可以先声明一个关联数组,然后再设置键和值declare -A site
site["A"]="1"
site["B"]="2"
site["C"]="3"# 访问关联数组元素可以使用指定的键echo${site["B"]}# 执行脚本,输出结果:2# 在数组前加一个感叹号!可以获取数组的所有键echo"数组的键为: ${!site[*]}"# A B Cecho"数组的键为: ${!site[@]}"# A B C

字符串转数组

words="aaa bbb ccc"# 字符串转数组,空格是分隔符
array=(${words/// }) # 打印数组最后一个成员echo${array[${#array[*]}-1]} # ccc# 打印数组长度echo${#array[*]}# 3# 字符串不转换为数组,在循环实现以空格为分隔符打印每个成员forwordin${words};doecho${word}# aaa \n bbb \n cccdone

运算符

原生bash不支持简单的数学运算,所以需要使用第三方的命令来实现,例如 awk 和 expr。

expr是一个表达式计算工具,awk是文本处理工具。

val=`expr 2 + 2`echo"两数之和为 : $val"# 两数之和为 : 4

表达式和运算符之间要有空格,如2 + 2

运算符描述eg
算数运算符假定变量 a 为 10,变量 b 为 20
+expr $a + $b 结果为 30
-expr $a - $b 结果为 -10
*expr $a * $b 结果为 200
/expr $a / $b 结果为 0.5
%expr $a % $b 结果为 10
=a=$b 把变量 b 的值赋值给 a
==用于比较两个数字,相同则返回 trueexpr $a == $b 结果为 false
!=用于比较两个数字,不相同则返回 trueexpr $a != $b 结果为 true
关系运算符
-eq==[ $a -eq $b ] 返回 false
-ne!=[ $a -ne $b ] 返回 true
-gt>[ $a -gr $b ] 返回 false
-lt<[ $a -lt $b ] 返回 true
-ge>=[ $a -ge $b ] 返回 false
-le<=[ $a -le $b ] 返回 true
布尔运算符
!非运算[ !false ] 返回true
-o或运算[ $a -lt 20 -o $b -gt 100 ] 返回 true
-a与运算[ $a -lt 20 -a $b -gt 100 ] 返回 false
逻辑运算符
&&逻辑 AND[[ $a -lt 100 && $b -gt 100 ]] 返回 false
逻辑 OR
字符串运算符假定变量 a 为 "abc",变量 b 为 "efg"
=比较两个字符串是否相等,相等返回 true[ $a = $b ] 返回 false
!=比较两个字符串是否不相等,不相等返回 true[ $a != $b ] 返回 true
-z字符串长度是否为0,为0返回 true[ -z $a ] 返回 false
-n字符串长度是否不为 0,不为 0 返回 true[ -n "$a" ] 返回 true
$字符串是否不为空,不为空返回 true[ $a ] 返回 true
文件运算符
-b file检测文件是否是块设备文件,如果是,则返回 true
-c file检测文件是否是字符设备文件,如果是,则返回 true
-d file检测文件是否是目录,如果是,则返回 true
-f file检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true
-g file检测文件是否设置了 SGID 位,如果是,则返回 true
-k file检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true
-p file检测文件是否是有名管道,如果是,则返回 true
-u file检测文件是否设置了 SUID 位,如果是,则返回 true
-r file检测文件是否可读,如果是,则返回 true
-w file检测文件是否可写,如果是,则返回 true
-x file检测文件是否可执行,如果是,则返回 true
-s file检测文件是否为空(文件大小是否大于0),不为空返回 true
-e file检测文件(包括目录)是否存在,如果是,则返回 true

条件表达式要放在方括号之间,并且要有空格。如[ $a == $b ]

逻辑运算符:||(逻辑OR),[[ $a -lt 100 || $b -gt 100 ]] 返回 true

条件判断

# ifif condition
then
command1 command2
...
commandN fi# if elseif condition
then
command1 command2
...
commandN
elsecommandfi# if else-if elseif condition1
then
command1
elif condition2 then command2
else
commandN
fi

if else 的 [...] 判断语句中大于使用 -gt,小于使用 -lt。如果使用((...))将其括起来,大于和小于可以直接使用 > 和 <。

a=10
b=20
if [ $a==$b ]
thenecho"a 等于 b"elif(($a>$b))thenecho"a 大于 b"elif [ $a-lt$b ]
thenecho"a 小于 b"elseecho"没有符合的条件"fi

流程控制

for循环

for变量名in item1 item2 ... itemN
do
command1
command2
...
commandN
done# 或者for变量名in 数组名
do
command1
command2
...
commandN
done

示例

forloopin 1 2 3 4 5
doecho"The value is: $loop"done# 输出结果:
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5
forstrin This is a string
doecho$strdone# 输出结果:
This
is
a
string

while循环

while condition
docommanddone

示例

a=1
while(($a<=5))doecho$alet"a++"done# 输出结果
1
2
3
4
5

until循环

until循环的循环条件与while循环恰好相反,until循环的循环条件为 true 时停止循环,而while循环则是为 false 时停止循环。

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。

# condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。until condition docommanddone

示例

a=0
# a < 10until [ !$a-lt 10 ]
doecho$a
a=`expr $a + 1`done# 输出结果为:
0
1
2
3
4
5
6
7
8
9

case分支

类似switch case分支选择结构。

case 变量 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

示例

echo'输入 1 到 4 之间的数字:'echo'你输入的数字为:'read aNum
case$aNumin
1) echo'你选择了 1'
;;
2) echo'你选择了 2'
;;
3) echo'你选择了 3'
;;
4) echo'你选择了 4'
;;
*) echo'你没有输入 1 到 4 之间的数字'
;;
esac

如果没有匹配到分支,可以用*来执行默认分支逻辑,类似default

break 命令允许跳出所有循环(终止执行后面的所有循环)。

continue 命令与 break 命令类似,它不会跳出所有循环,仅仅跳出当前循环。进入下一个循环。

#!/bin/bashwhile:doecho -n "输入 1 到 5 之间的数字:"read aNum
case$aNumin
1|2|3) echo"你输入的数字为 $aNum!"continue
;;
4|5) echo"你输入的数字为 $aNum!"
;;
*) echo"你输入的数字不是 4 到 5 之间的!"break
;;
esacdone

函数

定义格式

[ function] funname [()]
{
action;
[return int;]
}

可以带function fun() 定义,也可以直接fun() 定义。默认返回值将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)

funWithParam(){
echo"第一个参数为 $1 !"echo"第二个参数为 $2 !"echo"第十个参数为 $10 !"echo"第十个参数为 ${10} !"echo"第十一个参数为 ${11} !"echo"参数总数有 $# 个!"echo"作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
# 输出结果:
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

正则

匹配开头

[root@localhost shells]$ cat /etc/passwd | grep ^a # 会匹配出所有以 a 开头的行

匹配结尾

[root@localhost shells]$ cat /etc/passwd | grep t$
# 会匹配出所有以 t 结尾的行

匹配任意字符

[root@localhost shells]$ cat /etc/passwd | grep r..t
# 会匹配包含 rabt,rbbt,rxdt,root 等的所有行

匹配一个字符若干次

配合左边相邻的字符连用,表示匹配左边相邻的字符0次或多次。

[root@localhost shells]$ cat /etc/passwd | grep ro*t
# 会匹配 rt, rot, root, rooot, roooot 等所有行

字符区间

  • [ ] 表示匹配某个范围内的一个字符
  • [6,8] ------ 匹配 6 或者 8
  • [0-9] ------ 匹配一个 0-9 的数字
  • [0-9]* ------ 匹配任意长度的数字字符串
  • [a-z] ------匹配一个 a-z 之间的字符
  • [a-z]* ------ 匹配任意长度的字母字符串
  • [a-c, e-f] ----- 匹配 a-c 或者 e-f 之间的任意字符

文本处理工具

cut

cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。

语法:cut [参数] [file]

如果不指定 File 参数,cut 命令将读取标准输入。必须指定 -b、-c 或 -f。

参数说明
-b以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志。
-c以字符为单位进行分割。
-d自定义分隔符,默认为制表符。
-f-d一起使用,指定显示哪个区域。
-n取消分割多字节字符。仅和 -b 标志一起使用。如果字符的最后一个字节落在由 -b 标志的 List 参数指示的范围之内,该字符将被写出;否则,该字符将被排除

示例

# 当执行who命令时,会输出类似如下的内容:
$ who
rocrocket :0 2009-01-08 11:07
rocrocket pts/0 2009-01-08 11:23 (:0.0)
rocrocket pts/1 2009-01-08 14:15 (:0.0)
# 如果我们想提取每一行的第3个字节,如下:
$ who|cut -b 3
c
c

sed

sed 主要用来批量新增、替换、删除多个文件中指定的字符。

awk

awk 是一种处理文本文件的语言,是一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。