shell学习笔记 yxc的linux
shell语法目录
概论
shell是我们通过命令行与操作系统沟通的语言。它是一个解释性语言,不需要编译运行。
shell脚本可以直接在命令行中执行,也可以将一套逻辑组织成一个文件,方便复用。
而我们使用的终端,可以将其想象成一个大的文件,在逐行执行命令。
Linux常见shell脚本有很多种,一般默认使用bash。文件开头指明脚本解释器 #! /bin/bash
运行方式
新建一个文件,bash文件后缀是.sh
#! /bin/bash
echo "Hello World!"
直接用解释器执行
root@kali:~$ bash filename.sh
Hello World! # 脚本输出
作为可执行文件运行
首先要赋予文件权限
root@kali:~$ chmod +x filename.sh #使脚本具有可执行权限x
接下来有三种执行方式,一般第一种最常用
root@kali:~$ ./filename.sh #当前路径下执行
Hello World! # 脚本输出
root@kali:~$ /home/root/filename.sh #绝对路径下执行
Hello World! # 脚本输出
root@kali:~$ ~/filename.sh # 家目录下执行
Hello World! # 脚本输出
注释
单行注释
#
后均为注释
#注释
echo 'Hello World' #注释
多行注释
:<<EOF
注释1
注释2
注释3
EOF
其中EOF可以替换为任意字符串
变量
定义变量
定义字符串,以下三种都可以
name='Genevieve_xiao'
name="Genevieve_xiao"
name=Genevieve_xiao
引用变量
加上$
或${}
表示引用变量,{}
可省略,但是遇到变量边界歧义时最好加上。
name=Genevieve_xiao
echo $name
echo ${name}hahaha
只读变量
readonly
或者declare
再加变量
name=Genevieve_xiao
reaonly name
declare -r name
name=hahaha #报错
删除变量
unset
name=Genevieve_xiao
unset name
echo $name #输出空行
变量类型
分为自定义变量(局部)和环境变量(全局)。
子进程不能访问自定义变量。
name=Genevieve_xiao #自定义变量
export name #method1
declare -x name #method2局部变全局
export name=Genevieve_xiao #定义环境变量
declare +x name #全局变局部
字符串
单双引号的区别:
- 单引号内容会原样输出,不会执行、不取变量、不会转义
- 双引号内容可以执行、可以取变量、可以转义
不加引号同双引号效果。
获取字符长度
name=Genevieve_xiao
echo ${#name}
提取子串
nam=Genevieve_xiao
echo ${name:0:5}
默认变量
文件参数变量
在执行shell时向脚本传递的参数。
$0
是./文件名
$1
第一个参数
$2
第二个参数
#! /bin/bash
echo $0
echo $!
echo $2
root@kali:~$ chmod +x filename.sh
root@kali:~$ ./filename.sh 1 2
./filename.sh #脚本输出$0
1 #脚本输出$1
2 #脚本输出$2
其他参数相关变量
$#
文件传入的参数个数$*
所有参数构成的由空格隔开的字符串"$1 $2"
$@
每个参数分别由双引号括起来的字符串"$1" "$2"
$$
脚本当前运行的进程ID$?
上一条命令的退出状态exit code。0表示正常退出,其他值表示错误$(commmand)
返回这条指令的stdout 可嵌套'command'
返回这条指令的stdout 不可嵌套
数组
可以存放不同类型的值,只支持一维数组,初始化不用指明数组大小,下标从零开始。
定义
array=(1 abs "hahaha" bala)
或者
array[0]=1
array[1]=abs
array[2]="hahaha"
array[3]=bala
调用数组元素中的值
调用单个元素
${array[index]}
调用整个数组
${array[@]}
${array[*]}
数组长度
${#array[@]}
${#array[*]}
expr命令
用于求表达式的值 expr 表达式
重要说明
- 用空格隔开每一项
- 用反斜杠放在shell特定的字符前面(转义)
- 对包含空格和其他特殊字符的字符串要用单引号括起来
- expr会在stdout中输出结果,不需要嵌套echo。如果为逻辑关系表达式,若结果为真,stdout为1,否则为0。
- expr的exit code:如果为逻辑关系表达式,若结果为真,exit code为0,否则为1。
- 在这里出现的string 下标从1开始
- 优先级:字符串表达式>算术表达式>逻辑关系表达式
字符串表达式
lengrh STRING
返回string的长度index STRING CHARSET
任意cherset中的单个字符在string最先出现的位置 下标从1开始 如果不存在返回0substr STRING POSITION LENGTH
返回string从position开始长度为length的子串 下标从1开始。若pos或len不合法则返回空字符串
str="Hello World!"
expr length "$str" #输出12
expr index "$str" abcde #输出2
expr substr "$str" 2 3 #输出ell
整数表达式
+ -
* / %
()
注意*
和()
需要转义
expr $a + $b
expr $a - $b
expr $a \* $b
expr $a / $b
expr $a % $b
expr \( $a + 1 \) \* \( $b + 1 \)
逻辑关系表达式
|
如果第一个参数非空且非0,则返回第一个参数的值且忽略第二个参数,否则返回第二个参数的值,但要求第二个参数的值也是非空或非0,否则返回0。&
如果两个参数都非空且非0,则返回第一个参数,否则返回0。若第一个参数为0,则直接忽略第二个参数并返回0。< <= = == != >= >
如果为true则返回1,否则返回0。这里==与=等价()
a=3
b=0
expr $a \> $b #1
expr $a '>' $b #1
expr $a \& $b #0
expr $a \| $b #3
read命令
标准输入中读取单行数据。当读到文件结束符时,exit code
为1,即返回假,否则为0 ,循环语句中会用到
参数
-p
接提示信息-t
跟秒数,超过等待时间则自动忽略此条命令
read -p "input u name:" -t 30 name
echo命令
用于输出字符串echo SRING
显示普通字符串
echo "Hello World"
echo Hello World
显示转义字符
echo "\"Hello World\""
echo \"Hello World\"
显示变量
name=Genevieve_xiao
echo "My name is $name"
显示换行
-e
开启转义
echo -e "hi\nhahaha"
输出
hi
hahaha
显示不换行
\c
不换行
echo -e "hi \c"
echo "hahaha"
输出
hi hahaha
显示结果定向至文件
echo "Hello World" > output.txt
相当于直接创建了一个新的文件output.txt并将输出结果放进去
原样输出
单引号
echo '\"$name"'
输出
\"name"
显示命令执行结果
注意是反引号`
而不是单引号 '
.
echo `expr 3 + 4`
输出
7
printf命令
用于格式化输出,类似于c中的printf函数
默认不添加换行符
printf format-string [arguments...]
举例
printf "%d * %d =%d\n" 5 6 `expr 5 \* 6`
就相当于是printf函数去掉了括号和逗号分隔符,shell里的分隔符是空格
test命令与判断符号[]
逻辑运算符
&&
与,||
或- 用于连接两个expr,而单个的
& |
则是在expr里面进行逻辑关系运算 - 短路原则
- exit code 为0,表示真,非0,表示假
test
test命令用exit code返回结果,不同于expr用stdout返回
test用于判断文件类型,以及对变量做比较。
一个很妙的运算
test -e filename.sh && echo "exist" || echo "not exist"
如果文件存在,则第一项为真,执行&&
后的那一项,也就是第二项,输出exist,这样||
前面为真,则直接忽略第三项;
如果文件不存在,则第一项为假,自动忽略第二项,执行第三项, 输出not exist。
文件类型判断
-e
是否存在-f
是否为文件-d
是否为目录
文件权限判断
格式test -r fliename
-r
是否可读-w
是否可写-x
是否可执行-s
是否为非空文件
整数间的比较
格式test $a -eq $b
-eq
等于-ne
不等于-gt
大于-lt
小于-ge
大于等于le
小于等于
这几个是用于数值间的比较,而符号是用于字符串间的比较。
字符串比较
-z
是否为空-n
是否非空==
是否等于!=
是否不等于
test -z $str
test -n $str
test str1==str2
test str1!=str2
多重条件判定
格式test -r filename -a -x filename
-a
all两个条件是否同时成立-o
or两个条件是是否至少一个成立!
取反 例如!-x
判断符号[]
与test用法几乎一样,更常用与if语句中,[[]]是[]的加强版。
不过本质上[
是个命令,]
是个标志。
[ -e filename.sh ] && echo "exist" || "not exist"
注意
- 每一项用空格隔开
- 变量最好用双引号引起来
- 常数最好用单引号或双引号引起来
如果不引起来的话,万一变量里有空格,就有可能会报错。
一般来说,比起使用expr和test,在进行逻辑关系表达时,更多地还是使用[]
判断语句
所有的if或者elif后面都有then
单层if
格式
if condition
then
语句1
语句2
..
fi
单层if else
if condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
if-elif-elif-else
if condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
case
类似于c的switch
case $变量 in
值1)
语句1
语句2
...
;;
值2)
语句1
语句2
...
;;
*)
语句1
语句2
...
;;
esac
循环语句
for-in-do-done
for var in val1 val2 val3
do
语句1
语句2
...
done
举例
依次输出参数
for i in a 9 ss
do
echo $i
done
依次输出1-10
for i in $(seq 1 10) #seq序列
do
echo $i
done
依次输出a-z
for i in {a..z} #可以数字 可以倒序
do
echo $i
done
依次输出当前目录的文件名
for file in `ls`
do
echo $file
done
for((…;…;…))do…done
类似于c中的for
for ((expression; condition; expression))
do
语句1
语句2
done
这里的expression不需要转义
for ((i=1 ;i<=10 :i++))
do
echo $i
done
while…do…done循环
while condition
do
语句1
语句2
...
done
文件结束符为ctrl+D
,输入结束符后read指令返回false,循环停止。
until…do…done循环
当条件为真时结束。
until condition
do
语句1
语句2
..
done
break命令
跳出当前一层循环,但与c不同的是,如果break出现在case里的话,break不是跳出case语句,而是忽略case,跳出case外的一个循环语句。
while read name
do
for ((i=1;i<=10;i++))
do
case $i in
8)
break
;;
*)
echo $i
;;
esac
done
done
该实例里每次输入非EOF的字符串,会输出一遍1-7。
在for语句读到8的时候,break掉了for语句,进入下一次while。
continue命令
同c里的continue,跳过当前这次循环。
死循环处理
两种方法
- ctrl+c
- top命令找到进程PID,输入
kill -9 PID
函数
跟c里的函数基本一样,但是return值不同,是exitcode ,只能是0-255之间的数,0表示正常结束。
若要调用函数的输出结果,可通过echo输出到stdout中再通过$(function_name)
来获取
return值通过$?
获取。
调用函数的时候不需要写()。
格式
[function] func_name(){ #function 可不写
语句1
语句2
...
}
调用函数
func(){
name=Gx
echo $name
}
func
输出
Gx
若要获取return和stdout
func(){
name=Gx
echo $nane
return 111
}
output=${func} #or`func`
ret=$?
echo "output=$output"
echo "return=$ret"
输出
ouput=Gx
return=111
函数的输入参数
$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) #输出55
注:为什么只输出了55而没有输出其他echo的值呢,只要在echo后面有$(),它就会截获stdout里的值。递归往回调用的时候上一层的echo会被这一层的sum=$()截获而没有输出,而最后一层的echo被函数外的$捕获,函数外的echo输出55.
函数内的局部变量
变量未经声明都是全局,除非特殊命名。
格式local 变量名=变量值
exit命令
exit命令用来退出当前shell进程,并返回一个退出状态;使用$?可以接收这个退出状态。
exit命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
exit n
返回n并退出进程
文件重定向
每个进程默认打开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中
stdout和stdin可以同时重定向
test.sh<input.txt>output.txt
test.sh<output.txt>input.txt #这两个是等价的
引入外部脚本
类似于c的include,引入其他文件中的代码
两种格式
. filename
source filename
filename可以写绝对路径
相当于把这个外部脚本在当前文件中展开
举例
创建test1.sh
#! /bin/bash
name=Gx
创建test2.sh
#! /bin/bash
source test1,txt
echo My name is :$name
输出
My name is Gx
附录
关于文件权限的问题
如何查看文件权限呢
root@kali:~$ ls -l filename.sh
-rw-rw-r-- ... (省略)... # 脚本输出
脚本输出这里的123位rw-
是作者user权限,456位rw-
是同用户组group权限,789位r--
是其他用户other权限,而第0位若是-
则表示普通文件file,若是d
则表示目录directory。
r指read 可读权限(4),w指write 可写入权限(2),x指execute 可执行权限(1)。
顺便简单介绍一下chomd的用法
字母法公式
chomd [ u g o a ] [ + - = ] [ r w x ] filename
数字法公式chomd [ num1 ][ num2 ][ num3 ] filename
u-user,g-group,o-other,a-all
r-read-4,w-write-2,x-execute-1
num1-user的权限和,num2-group的权限和,num3-other的权限和。
+增加权限,-撤销权限,=赋予权限。
不写ugoa默认就是all。
举例1
chomd u+rwx, g+rw, o+rw filename.sh
等价于chomd 755 filename.sh