一.shell
1.1 shell
shell的本质是一种命令行解释器,它问用户提供与操作系统交互的界面。
1.命令解释:接受用户输入的命令,并将其解释为操作系统可以理解的指令来执行。
2.脚本执行:可以运行 Shell 脚本,这些脚本包含一系列命令,可实现自动化任务。
3.环境配置:允许用户设置环境变量,来定制工作环境。
1.环境变量就是保存系统启动相关以及系统运行相关的一些值的变量。
linux系统中可以使用命令 env 来查看系统中已有的环境变量
可以使用 echo $环境变量名 来查看环境变量的值
2.常见的环境变量
HOME 保存当前使用的的用户家目录
PWD 保存当前所在的路径
OLDPWD 保存上一次所在的路径
SHELL 保存当前用户使用的默认的命令解析器
PS1 保存命令行提示符显示的内容及配色方案
PATH 保存可执行文件的路径
3.声明环境变量的方式
export 环境变量名=环境变量值 覆盖写
export 环境变量名=$环境变量的名:新的值 追加写
只对当前终端生效方式:直接在终端执行命令 export 环境变量名=环境变量值,立即生
效,多用于测试。
对当前用户生效方式:将声明环境变量的语句添加到~/.bashrc这个脚本文件中,重新打开
终端或执行命令 source ~/.bashrc 立即生效,常用于实际开发。
对系统中的所有用户生效:将声明环境变量的语句添加到/etc/profile这个脚本文件中,重启
或执行命令 source ~/.bashrc 立即生效。
4.管道和重定向:支持将一个命令的输出作为另一个命令的输入(管道),以及将命令的输入和输出重定向到文件。
管道符的作用将前一个命令的标准输出作为后一个命令的标准输入,符号是 |
例:head -n 100 file | tail -n 10 查看文件file第91-100行的内容
入输出重定向符
以输出重定向符为例: >是覆盖 >>追加
输出重定向符: > >>
命令(或可执行程序)执行的结果输出到指定的文件。
ls / > test.txt
#会将根目录下的文件列表输出到test.txt
文件中,如果test.txt
不存在则创建,如果存在则覆盖其原有内容
echo "hello dog" >> test.txt
#会将 "hello dog" 追加到test.txt
文件的末尾输入重定向符:< <<
让命令(或可执行程序)从指定文件中取得输入数据。
1.2 shell命令
终端上使用的命令
1.3shell脚本
shell脚本的本身是一个 .sh 结尾的文件,里面都是命令的集合以及一些复杂的逻辑。
作用:批量的执行命令。
二. shell的基本用法
2.1 shell的变量
shell中的变量是没有类型,默认是字符串
shell中的变量无需提前定义,直接使用
shell中每条指令结束,结尾无需加分号
shell中的标量也要符合命名规范,与c语言相同,hell中变量名一般采用大写字母
2.2 shell的注释
# 单行注释
:<<EOF
多行
注释
EOF
这种方式相当于利用了输入重定向符的功能。-如果注释的内容里面有输入的语句 就可能出问题 所以这种方式不建议使用。
2.3 shell的字符串处理
2.3.1 计算字符串长度--strlen
#!/bin/bash
STR="hello world"
len=${#STR}
echo $len #11
2.3.2 字符串拷贝--strcpy
#!/bin/bsah
STR1="hello world"
STR2="beijing"
STR1=${STR2}
echo $STR1 #beijing
STR1="123" #123
#通过直接赋值
2.3.3字符串拼接--strcat
#!/bin/bash
STR1="hello world"
STR2="beijing"
STR1="${STR1}$STR2"
echo $STR1 #hello worldbeijing
STR1="AA${STR1}BB" #AABhello worldB
2.3.4字符串截取--strstr
常用方式如下
#!/bin/bash
STR1="www.baidu.com" #字符串的字符从0开始计数
# 从第4个字符开始截取 截取到字符串结束
STR2=${STR1:4}
echo $STR2 # baidu.com
# 从第4个字符开始截取 截取2个字符
STR2 ${STR1:4:2}
echo $STR2 # ba
# 从倒数第6个字符开始截取 截取到字符串结束
STR2 ${STR1:0-6:2}
echo $STR2 # du
不常用方式:
#!/bin/bash
STR1="www.baidu.com.www.baidu.com"
# 从左向右截取第一次出现ba后面的内容
STR2=${STR1#*ba}
echo $STR2 # du.com.www.baidu.com
# 从左向右截取第一次出现ba前面的内容
STR2=${STR1##*ba}
echo $STR2 # www.
# 从右向左截取第一次出现ba前面的内容
STR2=${STR1%ba*}
echo $STR2 # www.baidu.com.www.
# 从右向左截取最后一次出现ba前面的内容
STR2=${STR1%%ba*}
echo $STR2 # www.
2.4 shell的输入输出
2.4.1 输出--echo
类似C语言的printf
echo hello world # echo输出自带换行符
echo -n hello world # 不想加换行符,-n 选项可以取消
age=23
name="zhangsan"
echo 我是${name},今年${age}了 # echo输出可以自己组装格式
echo -e "nihao\nbeijing" #echo输出转义字符\n, 加 -e 选项
2.4.2 输入--read
类似C语言的scanf
# read 允许一行以空格分隔输入多个变量的值,但给某个变量输入带空格的字符串是不允许
# 建议一行只读取一个变量的值
read V1 V2
echo V1=${V1} echo V2=${V2}
read -p "请输入您的姓名:" NAME # -p "描述信息" ,可以用来提示
echo $NAME
read -t 5 VALUE # -t 5,可以指定等待输入的时间,最多等待5秒
echo $VALUE
read -n 5 VALUE # -n 5,可以指定读取5个字符,如果够5个自动读取内容,不需敲回车
echo $VALUE
read -s PASSWD # -s 选项可以隐藏输入的回显,一般用于读取密码
echo 你输入的密码是[$PASSWD]
#可以多个选项一起使用,需注意选项和参数要匹配
read -p "请输入密码:" -t 5 -n 6 -s PASSWD
echo 您输入的密码是[$PASSWD]
2.5shell的算数运算
shell本身是用来批量执行命令的,并不是用来做复杂的算数运算的。如果需要做复杂的算数运算,可以用C语言写,在shell中调用C程序来处理。shell的场景会涉及一些简单的算数运算,如 计数变量的自增,需要用到shell中的算数运算的方式。
shell如果不特殊处理,所有的运算都会将变量的值当做字符串处理
shell不支持浮点型运算
shell中做算数运算的方式有4种:
(()) $[] let expr
上述四种运算方式,执行的效率是依次递减的。
2.5.1 (())
格式:
((表达式))
((表达式1,表达式2,...,表达式n)) #当有多个表达式时 每个表达式都会参与运算
# 最后一个表达式的结果 就是整个(())运算符的结果
注意事项:
1.(())里面引用变量值的时候 $ 可加可不加
2.(())里面运算符前后的 空格 可加可不加
3.如果想引用运算的结果 需要在(())前加 $ 如 C=$((A+B))
4.(())可以做较复杂的运算 如 ++ -- 如 for 循环 等
for((i=0;i
do
done
#!/bin/bash
v1=10
v2=20
RET=$((V1+V2))
echo $RET
echo $((V1+V2))
echo #((V1 + V2))
echo #(($V1+$V2))
echo #(($V1 + $V2))
#结果都是30
#如果自增自减运算时 不能加$ (($V1++))是错误的写法
((v1++))
echo $v1 # 11
# 多个表达式 每个表达式都会参与运算
RET=$((++V1, ++V2))
echo $v1 $v2 $RET # 12 21 21
# (()) 支持for等写法
for((i=0;i<5;i++))
do
echo "hello world"
done
三. 控制语句
3.1 if..else语句
3.1.1格式
if [ 表达式1 ] # 注意 if 和 [ ]之间,[ ] 和 表达式 之间都要加至少一个空格
then
分支1
elif [ 表达式2 ]
then
分支2
else #else分支没有then
其他分支
fi
3.1.2 判断字符串
在if的表达式中,建议使用双引号把字符串引起来。因为字符串中有空格时,表达式会报错。
-z 判断字符串是否为空
-n 判断字符串是否非空
==或= 判断字符串是否相等
!= 判断字符串是否不相等
\> 判断字符串是否大于
\< 判断字符串是否小于
3.1.3 字符串按整形方式比较
-gt 大于 (great than)
-lt 小于 (less than)
-eq 等于 (equal)
-ge 大于等于 (great equal)
-le 小于等于 (less equal)
-ne 不等于 (not equal)
逻辑运算:
-a 逻辑与 (and)
-o 逻辑或 (or)
注意事项:
1.在一个[ ]里面要使用 -a -o
2.在两个[ ]之间要使用 && ||
#输入学生的成绩 [90,100]为A [80,90)为B [70,80)为C [607)为D [0,60)为不及格
#!/bin/bash
read -p "输入学生的成绩:" SCORE
if [ &SCORE -lt 0 -o &SCORE -gt 100 ]
then
echo "范围不合理"
exit
fi
if [ $SCORE -ge 90 -a $SCORE -le 100 ]
then
echo "A"
elif [ $SCORE -ge 80 ] && [ $SCORE -lt 90 ]
then
echo "B"
elif [ $SCORE -ge 70 -a $SCORE -lt 80 ]
then
echo "C"
elif [ $SCORE -ge 60 ] && [ $SCORE -lt 70 ]
then
echo "D"
else
echo "不及格"
fi
3.1.4 判断文件的类型
-e 判断文件是否存在,存在为真,不存在为假
-s 判断文件是否存在,且判断文件是否为非空
-c 判断文件是否存在,且判断文件是否为字符设备文件
-d 判断文件是否存在,且判断文件是否为目录文件
-f 判断文件是否存在,且判断文件是否为普通文件
-L 判断文件是否存在,且判断文件是否为链接文件
-S 判断文件是否存在,且判断文件是否为套接字文件
-p 判断文件是否存在,且判断文件是否为管道文件
1.判断类型时,先-e判断文件是否存在,再判断其他类型
2.对于判断链接文件,即能匹配链接文件,又能匹配被链接文件的类型。所以先判断是否是链接文件,因为其他文件不会匹配链接文件。
# 输入一个文件,输出这个文件的类型
#!/bim/bsah
read -p "请输入一个文件:" FILE
if [ -e "FILE" ]
than
ehco "文件存在"
if [ -L $FILE ]
than
echo "链接文件"
elif [ -d $FILE ]
echo "目录文件"
elif [ -f $FILE ]
echo "普通文件"
fi:
else
echo "文件不存在"
fi
3.1.5 判断文件的权限
-r 判断文件是否存在 且判断文件是否有读权限
-w 判断文件是否存在 且判断文件是否有写权限
-x 判断文件是否存在 且判断文件是否有执行权限
3.1.6 判断文件的时间戳
文件的时间戳:文件最后一次被修改的时间
-nt 判断两个文件的时间戳,前面的文件是否比后面的新(new than)
-ot 判断两个文件的时间戳,前面的文件是否比后面的新(old than)
#!/bin/bash
read -p "请输入两个文件" FILE1 FILE2
if [ $FILE1 -nt $FILE2 ]
than
echo "yas"
else
echo "no"
fi
3.1.7 判断文件的inode号
-ef 两个文件inode号相同为真,不相同为假
同一组硬链接文件inode号相同(查看inod号 estat 文件名 或ls -i 文件名)
#!/bin/bash
read FILE1 FILE2
if [ $FILE1 -ef $FILE2 ]
then
echo "yes"
else
echo "no"
fi
3.2 for循环
3.2.1 类似C语言的for循环
for((i=0;i<5;i++))
do
循环体
done
3.2.2 shell中特有的for循环
for 变量 in 单词列表 do 循环体 done
1.单词列表的单词之间用空格分隔
2.如果单词列表是连续的,可以用{start..end},如{1..100}
也可使用`seq 1 10`,如果有间隔可以`seq 1 2 10` 表示取1 3 5 7 9(2为步长)
3.单词列表也可是命令的结果,如`ls` $(ls) ---最常用的用法
4.可省略in和单词列表,单词列表来自于命令行的传参
#!/bin/bash
#批量处理文件
for i in `seq 1 10`
do
echo file_$i
#处理文件的命令
done
# 给当前路径下所有文件的所属用户项 添加执行权限
for i in `ls` # 或者$(ls)
do
chmod u+x $i
done
#省略in和单词列表输出文件
for i
do
echo $i
done
3.3while循环
格式:
while [ 表达式 ] # 注意 while 和 [] 之间 ,[]和表达式之间都要有空格
do
循环体
done
# 表达式的用法和 if..else 语句的表达式用法相同
# 1到100累加求和
#!/bin/bash
sum=0
i=0
while [ $i -le 100 ]
do
sum=$((sum + i))
((i++))
done
echo &sum
shell中一般使用while循环都用作死循环使用
死循环的写法: 比如 使用死循环 每秒打印一次系统时间
方式一
whlie [ 1 ]
do
date
sleep 1
done
方式二
while ((1))
do
date
sleep 1
done
方式三
while true
do
date
sleep 1
done
3.4 case..in语句
格式:
case $变量 in
选项)
分支1
;;
选项)
分支2
;;
*)
其他分支
;;
esac
case..in语句和C语言的 switch..case 语句很像,* 的作用类似于C语言中 default,表示如果前面的分支都不满足 就进 * 分支。* 分支如果不需要 也可以不写。
选项的写法支持通配符,常用写法如下:
aa|bb|cc 通配aa或者bb或者cc
file[abc] 通配filea fileb filec
file[a-f] 通配filea - filef
file[1-4] 通配file1 file2 file3 file4
file[^abc] 通配不包含abc中的任意一个字符 语法允许 但是逻辑是反的 不建议用
#!/bin/bsah
read -p "请输入你的选择[Y/n]" CHOOSE
case $CHOOSE in
[Yy][Ee][Ss]|[Yy])
echo "yes"
;;
*)
echo "no"
;;
esac
3.5select..in语句
select..in语句会将单词列表生成一个可供用户选择的列表
用户只能通过编号来选择单词 不能通过输入单词来选择
一般都配合着case..in语句一起使用,可以根据用户的选择不同走不同的分支
格式:
select 变量 in 单词列表
do
代码块
done
#!/bin/bash select i in beijing shanghai guangzhou do echo $i done
执行结果:
1) beijing
2) shanghai
3) guangzhou
#?
这个#?是提示符 可以通过修改环境变量PS3来修改 如 export PS3="请输入你的选择>>"
3.6 braek和continue的作用
break 立即结束本层的循环
break n n是一个数字 表示立即结束n层循环
cotinue 理解结束本层的本次循环
cotinue n n是一个数字 表示立即结束n层的本次循环
#!/bin/bash for((i=1;i<=5;i++)) do for((j=1;j<=5;j++)) do if [ $j -eq 3 ] then #break #continue #break 2 continue 2 fi echo "$i--$j" done done
四. 函数
4.1 定义函数的格式
function 函数名(){
函数体
}
1.shell中的函数是没有返回值类型的
2.shell中的函数是没有参数列表的
3.function是声明函数的关键字可以不写(为了方便阅读代码可以写),但是声明函数的 () 必须写
4.shell中的函数名也需要符合命名规范
5.shell中函数如果不被调用 是不会执行的
4.2 函数的调用
函数定义之后,可多次调用
没有参数:函数名
有参数: 函数名 参数1 参数2 ... 参数n
#!/bin/bash # 函数的定义 user_func1(){ echo $(($1+$2)) } # 有参数的函数调用 hqyj_func2 10 20 hqyj_func2 100 200
4.3 函数内部参数
在函数内部可以通过环境变量来使用参数
$0 脚本名
$1-$9 调用函数时传递的第1-第9个参数
${10} 第10个参数
封装函数 给参数指定的路径下的所有文件的 所属用户添加执行权限
#!/bin/bash user_add_x(){ LIST=`ls $1` for i in $LIST do chmod u+x $1/$i done } #执行时如下 # user_add_x 指定的路径
如果在函数内部想使用命令行的参数,需要调用函数时通过传参传递给函数
$@ $* "$@" 将命令行的参数传递给函数
"$*" 将命令行的参数作为一个整体传递给函数
例:执行脚本 时 bash xxx.sh hello world beijing nihao
#!/bin/bash function user_func1(){ echo $1 # world echo $2 # hello echo $3 # nihao echo $4 # beijing } user_func1 $2 $1 $4 $3 user_func2(){ echo $1 echo $2 echo $3 echo $4 } user_func2 $@ user_func2 $* user_func2 "$@" #输出 hello # world # beijing # nihao user_func2 "$*" #输出 hello world beijing nihao 下面空三行 # 这种写法 函数中的 $1 就表示命令行的所有参数
4.4 函数的返回值
1. 因为shell中的变量默认就是全局的 所以函数中定义的变量 在调用函数结束后还可以使用。只要变量没有加local关键字修饰 就可以在函数调用后继续访问 可以用这种方式返回结果。
#!/bin/bash
user_add_1(){
RET=$(($1+$2))
}
user_add_1 10 20
echo $RET # 30
2.shell中也可以使用return来返回函数运行的结果 在调用处使用 $? 就表示使用函数返回的结果
但是注意,这种方式只能返回 [0,255] 范围的值
#!/bin/bash
user_add_2(){
RET=$(($1+$2))
return $RET
}
user_add_2 10 20
echo $? # 30
hqyj_add_2 100 200
echo $? # 44 只能返回 [0,255] 的结果
3.使用echo的方式返回函数运行的结果 在调用处使用 命令置换符 $() 或者 `` 来处理
但是这种用法 函数内部就没法将信息打印到终端了 函数内部所有的echo都会打印到调用处
#!/bin/bash
user_add_3(){
RET=$(($1+$2))
echo $RE #不会输出到终端
XXX=`hqyj_add_3 10 20`
echo $XXX # 30
XXX=$(hqyj_add_3 10000 20000)
echo $XXX # 30000
shell脚本的综合练习
1.判断当前用户的家目录下是否有 file_dir 和 dir_dir 这两个目录文件
如果有 询问用户是否清空?
如果用户输入的是Y 则清空 (删除后新建)
如果用户输入的是其他字符 则不清空 退出脚本(exit)
如果没有 则新建这两个目录文件
2. 在终端读取用户指定的路径 (最好是绝对路径)
将该目录下的所有普通文件拷贝到 file_dir 中
将该目录下的所有目录文件拷贝到 dir_dir 中
并统计拷贝的普通文件和目录文件的个数
3.拷贝完成后,输出拷贝了多少个普通文件和目录文件
并分别输出拷贝了哪些普通文件和哪些目录文件
#!/bin/bash FILE_DIR="$HOME/file_dir" DIR_DIR="$HOME/dir_dir" COUNT_FILE=0 COUNT_DIR=0 if[ -e FILE_DIR ] then read -p "目录文件$FILE_DIR,是否清空[Y/n]" CHOOSE if [$CHOOSE == "Y" ] then rm -r $FILE_DIR else echo "目录未清空 脚本结束" exit fi fi mkdir $FILE_DIR if[ -e DIR_DIR ] then read -p "目录文件$FILE_DIR,是否清空[Y/n]" CHOOSE if [$CHOOSE == "Y" ] then rm -r $DIR_DIR else echo "目录未清空 脚本结束" exit fi fi mkdir $DIR_DIR read -p "请输入要进行分类的路径(绝对路径):" MY_PATH if [ -e $MY_PATH ] then if [ -d $MY_PATH ] then for i in `$MY_PATH` do if [ -f $MY_PATH$i ] then cp $MY_PATH$i $FILE_DIR ((COUNT_FILE++)) elif [ -d $MY_PATH$i ] then cp -r $MY_PATH/$i $DIR_DIR ((COUNT_DIR++)) fi done echo "----------------------------------------" echo "---------------分类完成-----------------" echo "----------------------------------------" echo "-----------共计操作了 $COUNT_FILE 个普通文件-------------" ls $FILE_DIR echo "-----------共计操作了 $COUNT_DIR 个目录文件-------------" ls $DIR_DIR echo "----------------------------------------" else echo "路径 $MY_PATH 不是目录文件 脚本退出" exit fi else echo "路径 $MY_PATH 不存在 脚本退出" exit fi