shell学习笔记 yxc的linux
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可以替换为任意字符串
定义字符串,以下三种都可以
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 表达式
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
标准输入中读取单行数据。当读到文件结束符时,exit code
为1,即返回假,否则为0 ,循环语句中会用到
参数
-p
接提示信息-t
跟秒数,超过等待时间则自动忽略此条命令read -p "input u name:" -t 30 name
用于输出字符串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
用于格式化输出,类似于c中的printf函数
默认不添加换行符
printf format-string [arguments...]
举例
printf "%d * %d =%d\n" 5 6 `expr 5 \* 6`
就相当于是printf函数去掉了括号和逗号分隔符,shell里的分隔符是空格
&&
与,||
或& |
则是在expr里面进行逻辑关系运算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 condition
then
语句1
语句2
..
fi
if condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
if condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
else
语句1
语句2
...
fi
类似于c的switch
case $变量 in
值1)
语句1
语句2
...
;;
值2)
语句1
语句2
...
;;
*)
语句1
语句2
...
;;
esac
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
类似于c中的for
for ((expression; condition; expression))
do
语句1
语句2
done
这里的expression不需要转义
for ((i=1 ;i<=10 :i++))
do
echo $i
done
while condition
do
语句1
语句2
...
done
文件结束符为ctrl+D
,输入结束符后read指令返回false,循环停止。
当条件为真时结束。
until condition
do
语句1
语句2
..
done
跳出当前一层循环,但与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。
同c里的continue,跳过当前这次循环。
两种方法
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命令用来退出当前shell进程,并返回一个退出状态;使用$?可以接收这个退出状态。
exit命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
exit n
返回n并退出进程
每个进程默认打开3个文件描述符:
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