- 什么是shell?
- shell是一门通过命令行与操作系统沟通的语言
- 为什么要学习shell?
- 做一些供我们自己的shell脚本
shell脚本可以直接在命令行中执行,也可以将一套逻辑组织成一个文件,方便复用。
我们操作服务器使用的命令行就可以看成是一个shell脚本在逐行执行。
Linux中常见的shell脚本有很多种,常见的有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- zsh
- …
Linux系统中一般默认使用bash,接下来的学习笔记也是学习bash中的shell语法。
脚本示例:
新建一个test.sh文件,内容如下:
#! /bin/bash echo "Hello Shell!"
运行方式:
-
用解释器执行
bash test.sh
这里我用的是tmux,开了两个pane,想学用tmux的可以看看这篇文章我们来看右边的pane,可以发现我们输出了Hello Shell!,为什么会输出它呢?你可以将echo跟C++的cout或者python的print以及Java的System.out.println输出联想到一块,就是打印输出。
现在再让我们仔细一下看右图中的第一行,为什么会出错呢?这是因为当前我们编写的文件没有执行权限,可以使用ls -l test.sh查看该文件的权限信息。
执行权限的标志是x,这里只有rw,即read跟write(读写),那下面就让我们给它赋予执行权限吧!
赋予执行权限命令格式chmod +x 文件,从上图中看到,我们赋予完之后多了x,即代表该文件已经具有可执行权限了。
那么接下来就进入我们的第二种运行方式。 -
作为可执行文件直接执行
./test.sh #当前路径下执行 /xxx/xxx/test.sh # 绝对路径下执行
单行注释:
每行#之后的内容均是注释
# 这是一行注释 echo "Hello Shell" # 这也是一行注释
多行注释:
格式:
:<其中EOF换成其他任意字符串也可以噢,例如:
:<3. 变量 定义变量:
var1=str1 # 不加引号定义字符串 var2='str2' # 单引号定义字符串 var3="str3" # 双引号定义字符串注意:=两边不要乱加空格
使用变量:
使用变量,需要加上$符号,或者${}符号。花括号是可选的,主要为了帮助解释器识别变量边界。(最好要加{},噢不你一定要加!!!)var1=str1 echo $var1 # 输出str1 echo ${var1} # 输出str1 echo ${var1}mobai # 输出str1mobai只读变量:
使用readonly 变量名或者declare -r 变量名可以将变量变为只读,例如:var1=str1 readonly var1 # declare -r var1 均可 var1=str2执行一下,发现报错
不要慌,这就是我们把它设置成只读变量成功了嘿嘿删除变量:
unset 变量名 删除该变量,例如:var1=str1 unset var1 echo ${var1} # 输出空行输出空行,这是因为var1变量被我们成功删除咯
变量类型:
- 自定义变量(局部变量)
* 子进程不能访问的变量- 环境变量(全局变量)
* 子进程可以访问的变量自定义变量改成环境变量(两种方法):
4. export 变量名
5. declare -x 变量名环境变量改为自定义变量:declare +x 变量名
自定义变量声明:变量名=变量值
环境变量声明:export 变量名=变量值这个我们该如何进行测试呢?何来的子进程?
你在命令行输入bash,就回让当前进程进入睡眠,然后进入它的子进程中,按exit即可退出子进程,回到原本进程中。(别问我为啥哈,这是规矩规矩规矩,俺也没办法)
然后你就自己测着玩把嘿嘿!
算了算了我怕被说我不认真,我还是给你们示范一下叭!(看上图)
最后输出空白是因为我将环境变量又改回自定义变量,在子进程中访问不到了,所以就输出了空白。至于为什么可以在命令行中直接打这些东西,看我最开始在概述中提的shell脚本那部分。字符串:
字符串可以用单引号,也可以用双引号,也可以不用引号。单引号与双引号的区别:
- 单引号中的内容会原样输出,不会执行、不会取变量。
- 双引号中的内容可以执行、可以取变量。
var1=str1 echo 'hello,${var1} "djdj"' # 单引号字符串,输出hello,${var1} "djdj" echo "hello,${var1} "djdj"" # 双引号字符串,输出hello,str1 "djdj"4. 默认变量文件参数变量:
在执行shell脚本时,可以向脚本传递参数,$1是第一个参数,$2是第二个参数,以此类推。特殊的,$0是文件名(包含路径)。例如:
还用我们之前创建好的文件test.sh:#! /bin/bash echo "文件名: " $0 echo "第一个参数: " $1 echo "第二个参数: " $2 echo "第三个参数: " $3 echo "第四个参数: " $4然后执行该脚本:
./test.sh 1 2 3 4输出:
文件名: ./test.sh 第一个参数: 1 第二个参数: 2 第三个参数: 3 第四个参数: 4
其他参数相关变量:
参数 说明 $# 代表文件传入的参数个数 $* 由所有参数构成的用空格隔开的字符串,如上例中值为"$1 $2 $3 $4" $@ 每个参数分别用双引号括起来的字符串,如上例中值为"$1" "$2" "$3" "$4" $$ 运行当前脚本的进程ID $? 上一条命令的退出状态(注意不是stdout,而是exit code)。0表示正常退出,其他值表示错误 $(command) 返回command这条命令的stdout `command` 返回command这条命令的stdout 关于$$,补充一个小栗子:
5. 数组
上图中,我们查看出运行当前脚本的进程id是3418,接下来我们用top查看一下开的进程。
果不其然,3418进程就是bash命令,命令测试结束!数组中可以存放多个不同类型的值,只支持一维数组,初始化时不需要指明数组大小。
注:数组下标从0开始。
定义:
数组用小括号表示,元素之间用空格隔开。例如:array=(123 'abc' "def")也可以直接定义数组中某个元素的值:
array[0]=123 array[1]='abc' array[2]="def"读取数组中某个元素的值:
格式:${array[index]}
例如:array=(123 'abc' "def") echo ${array[0]} # 输出123 echo ${array[1]} # 输出abc echo ${array[2]} # 输出def输出:
123 abc def读取整个数组:
格式:${array[@]} # 第一种写法 ${array[*]} # 第二种写法栗子:
array=(123 'abc' "def") echo ${array[@]} # 输出123 abc def echo ${array[*]} # 输出123 abc def输出(1️⃣️1️⃣):
123 abc def 123 abc def数组长度:
类似于字符串${#array[@]} # 第一种写法 ${#array[*]} # 第二种写法栗子:
array=(123 'abc' "def") echo ${#array[@]} # 输出3 echo ${#array[*]} # 输出3输出(1️⃣️1️⃣):
3 3注意:
这里的数组,就算你是跳着赋值创建的数组,它的数组长度仍然是你实际使用的。
如图中所示,它不像C++的vector那样是11,而是3,即实际使用的长度。
6. expr命令expr命令求表达式的值,格式为:expr 表达式
表达式说明:
- 用空格隔开每一项
- 用反斜杠放在shell特定的字符前面(如发现表达式运行错误时,试试转义)
- 对包含空格和其他特殊字符的字符串要用引号括起来❗
- expr在stdout中输出结果,如果为逻辑关系表达式,若逻辑运算结果为真则输出1,否则是0。
- expr在exit code中输出结果,如果为逻辑关系表达式,若逻辑运算结果为真则输出0,否则是1。
字符串表达式:
- length str :返回str的长度
- index str charset:charset中任意单个字符在str中最前面的字符位置,下标从1开始。如果在str中完全不存在charset中的字符,则返回0。
- substr str position length:返回str字符串中从position开始,长度最大为length的子串。如果position或length为负数,0或非数值,则返回空字符串。(下标也是从1开始计算)
栗子:
str="Hello World!" echo `expr length "$str"` # ``不是单引号,表示执行该命令,输出12 echo `expr index "$str" aWd` # 输出7,下标从1开始 echo `expr substr "$str" 2 3` # 输出 ell整数表达式:
expr支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
- + -:加减运算。两端参数会转换为整数,如果转换失败则报错。
- * / %:乘、除、取模运算。两端参数会转换为整数,如果转换失败则报错。
示例:
a=1 b=2 echo `expr $a + $b` 输出3 echo `expr $a - $b` 输出-1 echo `expr $b / $a` 输出2 echo `expr $a % $b` 输出1 echo `expr ( $a + 1 ) * ( $b + 1 )` 输出6逻辑关系表达式:
- |:如果第一个参数非空且非0,则返回第一个参数的值,否则返回第二个参数的值,但要求第二个参数的值也非空或非零,否则返回0。如果第一个参数是非空或者非0时,不会计算第二个参数。
- &:如果两个参数都非空且非0,则返回第一个参数,否则返回0。如果第一个参数为0或为空,则不会计算第二个参数。
- < <= = == != >= >:比较两端的参数,如果为true,则返回1,否则返回0。"==“是”="的同义词。"expr"首先尝试将两端参数转换为整数,并作算术比较,如果转换失败,则按字符集排序规则作字符比较。
示例:
a=3 b=4 echo `expr $a > $b` # 输出0,>也需要转义 echo `expr $a '<' $b` # 输出1,也可以将特殊字符用引号引起来 echo `expr $a '>=' $b` # 输出0,跟''效果等同 echo `expr $a <= $b` # 输出1 c=0 d=5 echo `expr $c & $d` # 输出0 echo `expr $c '|' $d` # 输出5 echo `expr $a '|' $b` # 输出3 echo `expr $b '&' $a` # 输出47. read命令read命令用于从标准输入中读取单行数据。当读到文件 结束符时,exit code为1,否则为0。
参数说明:
- -p:后面可以接提示信息
- -t:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令
示例:
8.echo命令
echo命令用于输出字符串。命令格式:echo STRING
显示普通字符串:
echo hello echo 'hello' echo "hello"显示转义字符:
echo "hello" # 输出"hello" echo ""hello"" # 输出"hello"显示变量:
name=moyan echo "my name is $name" # my name is moyan,这里如果用单引号的话会输出my name is $name $失去作用。显示换行:
echo -e "n" # -e 开启转义,没有的话会输出n解释一下为什么前面使用"没有用-e也可以进行转义:这是因为像\ a b c d e f n r t v这样的必须要在有-e的时候才起作用,其他时候的转义是不用-e也能转义。
显示不换行:echo -e "Hi c" # -e 开启转义 c不换行 echo "siri"输出:
Hi siri显示结果定向至文件:
echo "hello" > output.txt # 将内容以覆盖的方式输出到output.txt中原样输出字符串(不进行转义或取变量):
djdj=apple echo '$djdj"'输出:
$djdj"显示命令的执行结果:
echo `date` # 注意不是单引号输出:
Wed Sep 29 18:00:25 CST 20219.printf命令printf命令用于格式化输出,类似于C语言中的printf函数。默认不会在字符串末尾加换行符
命令格式:printf format-string [arguments...]
用法示例:
脚本内容:printf "%10dn" 123 # 占10位,右对齐 printf "%-10.2fn" 3.1415926 # 占10位,左对齐,保留两位小数 printf "My name is %sn" "moyan" # 格式化输出字符串 printf "%d * %d = %dn" 2 3 `expr 2 * 3` # 表达式的值作为参数输出:
123 3.14 My name is moyan 2 * 3 = 610.逻辑运算符、test命令以及判断符号[]逻辑运算符&&和||:
- &&表示与,||表示或
- 二者具有短路原则:
expr1 && expr2:当expr1为假时,直接忽略expr2
expr1 || expr2 :当expr1 为真时,直接忽略expr2test命令:
在命令行中输入man test,可以查看test命令的用法。
test命令用于判断文件类型,以及对变量做比较。
test命令用exit code返回结果,而不是使用stdout。0表示真,非0表示假。示例:
test 2 -lt 3 # 为真,返回值为0 echo $? # 输出上个命令的exitcode,输出0文件类型判断:
命令格式:test 参数 filename
测试参数 代表意义 -e 文件是否存在 -f 是否为文件 -d 是否为目录 示例:
文件权限判断:
命令格式:test 参数 filename
测试参数 代表意义 -r 文件是否可读 -w 文件是否可写 -x 文件是否可执行 -s 文件是否为非空文件 示例:
有两个文件,分别是test.sh和output.txt,其中test.sh非空,output.txt是空的 test -s test.sh echo $? # 输出0,代表真,也就是文件是非空 test -s output.txt echo $? # 输出1,代表假,也就是文件不是非空的,是个空文件。整数间的比较:
命令格式:test 数a 参数 数b
测试参数 代表意义 -eq a是否等于b -ne a是否不等于b -gt a是否大于b -lt a是否小于b -ge a是否大于等于b -le a是否小于等于b 多重条件判定:
命令格式:test 条件1 参数 条件2
示例:test -r filename -a -x filename
测试参数 代表意义 -a 两条件是否同时成立 -o 两条件是否至少一个成立 ! 取反。如test ! -x file,当file不可执行时,返回true 判断符号:
[]与test用法几乎一模一样,更常用于if语句中。另外[[]]是[]的加强版,支持的特性更多。
例如:
[ 2 -lt 3 ] # 为真,返回值为0 echo $? # 输出上个命令的返回值,输出0 [ -e test.sh ] # 因为存在test.sh,为真,返回值为0 echo $? # 输出上个命令的返回值,输出0注意⚠:
- []内的每一项都要用空格隔开
- []内的变量,最好用双引号括起来
- []内的常量,最好用单或双引号括起来
例如:
name="gz moyan" [ $name == "gz moyan" ] # 错误,等价于 [ gz moyan == "gz moyan" ],参数太多 [ "$name" == "gz moyan" ] # 正确11. 判断语句 if…then形式类似于C/C++中的if-else语句。
单层if
命令格式:if condition then 语句1 语句2 ... fi 等价于C++中的if (condition){ 语句1 语句2 .... }示例:
if expr 3 '==' 3 then echo "hello" fi输出:
1 # expr命令在执行时会将自己执行结果的stdout输出一遍。 hello # 因为expr命令执行结果的exitcode是0,所以判断为真,输出hello。单层if-else
命令格式:if condition then 语句1 语句2 ... else 语句1 语句2 ... fi示例:
a=3 b=4 if [ ! "$a" -lt "$b" ] then echo ${a}不小于${b} else echo ${a}小于${b} fi输出:
3小于4等价于C++代码:
if (!(3 < 4)){ cout << "3不小于4" << endl; }else{ cout << "3小于4" << endl; }多层if-elif-elif-else
命令格式:if condition then 语句1 语句2 ... elif condition then 语句1 语句2 ... elif condition then 语句1 语句2 else 语句1 语句2 ... fi示例:
a=4 if [ $a -eq 1 ] then echo ${a}等于1 elif [ $a -eq 2 ] then echo ${a}等于2 elif [ $a -eq 3 ] then echo ${a}等于3 else echo 其他 fi输出:
其他case…esac形式类似于C/C++中的switch语句
命令格式
case $变量名称 in 值1) 语句1 语句2 ... ;; # 类似于C/C++中的break 值2) 语句1 语句2 ... ;; *) # 类似于C/C++中的default 语句1 语句2 ... ;; esac示例:
a=5 case $a in 1) echo a的值为1 ;; 2) echo a的值为2 ;; *) echo a的值太大了,不想算了 ;; esac输出:
a的值太大了,不想算了12.循环语句 for…in…do…done循环形式命令格式:
for var in var1 var2 var3 do 语句 done示例1:
输出a1 a2 1234for tmp in a1 a2 1234 do echo $tmp done示例2:
输出当前路径下所有文件名for name in `ls` do echo $name done示例3:
输出1-10for i in `seq 1 10` do echo $i done示例4:
使用{1..10}输出1-10或者{a..z}输出a-z
还可以反过来输出,{10..1}输出10-1for i in {a..z} do echo $i donefor ((…;…;…)) do…done循环形式与C++的for循环比较像
命令格式:for ((expression; condition; expression)) do 语句1 语句2 done示例:
输出1-10for ((i=1;i<=10;++i)) do echo $i donewhile…do…done循环形式命令格式:
while condition do 语句1 语句2 ... done示例:
不断读取用户输入,直到读到文件结束符ctrl+d或者是使用ctrl+c将该进程杀死while read name do echo $name doneuntil…do…done循环形式当条件为真时结束。
命令格式:until condition do 语句1 语句2 ... done示例:
示例,当用户输入yes或者YES时结束,否则一直等待读入。until [ "${word}" == "yes" ] || [ "${word}" == "YES" ] do read -p "Please input yes/YES to stop this program: " word donebreak命令跳出当前一层循环,注意与C/C++不同的是:break不能跳出case语句。case语句的跳出使用的是;;
示例:
每读入非EOF的字符串,会输出一遍1-7。while read name do for ((i=1;i<=10;i++)) do case $i in 8) break ;; *) echo $i ;; esac done donecontinue命令同C++循环中的continue作用一致,continue 会跳过当前循环中的代码,强迫开始下一次循环。
示例:
输出1-10中所有奇数for ((i=1;i<=10;i++)) do if [ `expr $i % 2` -eq 0 ] then continue fi echo $i done死循环处理方式
13.函数
- ctrl+c杀死进程
- 使用top查看关闭进程
bash中的函数类似于C/C++中的函数,但return的返回值与C/C++不同,返回的是exit code,取值为0-255,0表示正常结束。
如果想获取函数的输出结果,可以通过echo输出到stdout中,然后通过$(function_name)来获取stdout中的结果。
函数的return值可以通过$?来获取。
命令格式:[function] func_name() { # function关键字可以省略,一般都省略 语句1 语句2 ... }示例1:
不获取return值和stdout值func() { name=moyan echo "Hello $name" } func输出结果:Hello moyan
示例2:
获取 return值和stdout值
不写return时默认return 0func() { name=moyan echo "Hello $name" return 233 } output=$(func) ret=$? echo "output = $output" echo "return = $ret"输出结果:
output = Hello moyan return = 233示例3:
函数输入参数的使用
在函数内,$1表示第一个输入参数,$2表示第二个输入参数,依此类推。
注意:函数内的$0仍然是脚本文件名,而不是该函数名。func() { # 递归计算 $1 + ($1 - 1) + ($1 - 2) + ... + 0 word="" while [ "${word}" != 'y' ] && [ "${word}" != 'n' ] do read -p "要进入func($1)函数吗?请输入y/n:" word done if [ "$word" == 'n' ] then echo 0 return 0 fi if [ $1 -le 0 ] then echo 0 return 0 fi sum=$(func $(expr $1 - 1)) echo $(expr $sum + $1) } echo $(func 10)示例4:
函数局部变量的使用(作用范围仅在当前函数内)
命令格式:local 变量名=变量值#! /bin/bash func() { local name=moyan echo $name } func echo $name输出结果:
moyan # 空白输出结果解释:第一行为函数内的name变量,第二行为函数外调用name变量,此时该变量不存在。
14.exit命令exit命令用来退出当前shell进程,并返回一个退出状态;使用$?可以接收这个退出状态。
exit命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。示例:
创建脚本test.sh,内容如下:
#! /bin/bash if [ $# -ne 1 ] # 如果传入参数个数等于1,则正常退出;否则非正常退出。 then echo "arguments not valid" exit 1 else echo "arguments valid" exit 0 fi执行该脚本:
[root@iZuf6430s16l9s63a0eremZ ~]# ./test.sh mobai # 传入一个参数 arguments valid [root@iZuf6430s16l9s63a0eremZ ~]# echo $? 0 [root@iZuf6430s16l9s63a0eremZ ~]# ./test.sh mobai moyan # 传入多个参数 arguments not valid [root@iZuf6430s16l9s63a0eremZ ~]# echo $? 115.文件重定向每个进程默认打开3个文件描述符:
stdin标准输入,从命令行读取数据,文件描述符为0
stdout标准输出,向命令行输出数据,文件描述符为1
stderr标准错误输出,向命令行输出数据,文件描述符为2
可以用文件重定向将这三个文件重定向到其他文件中。重定向命令列表
命令 说明 command > file 将stdout重定向到file中 command < file 将stdin重定向到file中 command >> file 将stdout以追加的方式重定向到file中 command n> file 将文件描述符n重定向到file中 command n>> file 将文件描述符n以追加的方式重定向到file中 输入输出重定向
#! /bin/bash echo -e "Hello c" > output.txt # 将stdout重定向到output.txt echo "World" >> output.txt # 将字符串追加到output.txt中 read str < output.txt # 从output.txt中读取字符串 echo $str # 输出output.txt第一行内容,即Hello World同时重定向stdout和stdin
创建test.sh#! /bin/bash read a read b echo `expr "$a" + "$b"`创建input.txt
3 4执行:
[root@iZuf6430s16l9s63a0eremZ ~]# vim input.txt [root@iZuf6430s16l9s63a0eremZ ~]# clear[root@iZuf6430s16l9s63a0eremZ ~]# ./test.sh < input.txt > output.txt [root@iZuf6430s16l9s63a0eremZ ~]# cat output.txt 7 [root@iZuf6430s16l9s63a0eremZ ~]# ./test.sh > output.txt < input.txt # 顺序无影响 [root@iZuf6430s16l9s63a0eremZ ~]# [root@iZuf6430s16l9s63a0eremZ ~]# cat output.txt 716. 引入外部脚本类似于C/C++中的include操作,bash也可以引入其他文件中的代码。
语法格式:. filename # 注意点和文件名之间有一个空格 或 source filename示例:
创建test1.sh,内容为:#! /bin/bash echo "I am test1.sh" name=moyan # 定义变量name然后创建test2.sh,内容为:
#! /bin/bash source test1.sh # 或 . test1.sh echo My name is : ${name} # 使用test1.sh中的变量执行:
[root@iZuf6430s16l9s63a0eremZ ~]# chmod +x test2.sh [root@iZuf6430s16l9s63a0eremZ ~]# ./test2.sh I am test1.sh My name is: moyan零碎知识
- Linux可执行文件是绿色的
- Linux一般用9位二进制数字表权限,每三位是一组,分别代表文件所有者组、同组用户组、以及其他用户组,每组的三位二进制数字分别对应的是读写执行权限的开启和关闭。
- echo "c"中的c可以理解为取消回车
- 如果只是查看文件内容的话,cat更方便
- :q!是在vim中是强制退出
- []返回的是true或false,真正要取回表达式运算结果还得用expr
那么,文章到这里就结束啦
这是我整理最仔细的一次笔记,有很多东西我搞了好久才搞明白
但是收获也是蛮大的,对之前学的tmux掌握更加熟悉,也对一些linux指令,进程概念有了更进一步的了解
谢谢你的观看❀
有什么问题欢迎在评论区或者私信找我说噢!一起加油~



