shell
1. shell概述
1.1 引入
- 完成以下任务:
- 判断用户家目录下(~)下面有没有一个叫 test 的文件夹
- 如果没有,提示按 y 创建并进入此文件夹,按 n 退出
- 如果有,直接进入,提示请输入一个字符串,并按此字符串创建一个文件,如果此文件已存在,提示重新输入,重复三次自动退出,不存在创建完毕,退出
- 简单的进行命令堆积无法完成以上任务,这就需要学习相应的 shell脚本语法规则了(本文件最后用shell脚本实现)
1.2 简介
- shell既是一种应用程序,也是一种设计语言
- 作为应用程序:交互式地解释、执行用户输入的命令,将用户的操作翻译成机器可以识别的语言,完成相应功。
- 系统分为:
硬件层
、Liunx系统层
、Shell
、应用程序层
,如图1。 - 此时Shell的作用是:沟通应用程序层与Liunx系统层,即 Shell解析器
- Shell解析器常用的有三种,分别是:
- sh
- ash
- bash,一般情况下使用bash
- 可以使用echo $SHELL查看使用的那种解析器,如图2
- Shell解析器常用的有三种,分别是:
- 作为设计语言
- 概念:shell脚本语言,不需要编译,
直接解析执行
(批处理) - 优点:简化我们对系统的管理与应用程序的部署过程
- 名词:
- 批处理:大量的循环的数据处理,如扫描当前根目录下是否存在a.txt,此时需要在当前根目录所有文件夹中一一寻找
- 脚本语言:我们只需使用任意文本编辑器,按照语法编写相应程序,增加可执行权限,即可在安装 shell命令解释器的环境下执行的代码语言,如python、html、css等。
- 概念:shell脚本语言,不需要编译,
图1:
图2:
1.3 shell脚本的分类
- 系统调用
- 这类脚本无需用户调用,系统会在合适的时候调用,如:/etc/profile、~/.bashrc 等
/etc/profile
:- 此文件为系统的每个用户设置环境信息,当
用户第一次登录时,该文件被执行
, - 系统的公共环境变量在这里设置
- 开机自启动的程序,一般也在这里设置
- 注意:如果在该文件中配置,需要从新登录用户才可生效
- 此文件为系统的每个用户设置环境信息,当
~/.bashrc
:- 用户自己的家目录中的
.bashrc
(隐藏文件,使用 ls -a 命令查看) - 登录时会自动调用,打开任意终端时也会自动调用
- 这个文件一般设置与个人用户有关的环境变量,如交叉编译器的路径等等
- 用户自己的家目录中的
- /etc/profile与~/.bashrc的关系,如下图
style=“zoom:50%;” />
- 这类脚本无需用户调用,系统会在合适的时候调用,如:/etc/profile、~/.bashrc 等
- 用户编写
- 需要手动调用
注意:
- 无论是系统调用的还是需要我们自己调用的,其
语法规则都一样
2. 第一个shell脚本
2.1 步骤
1、创建文件,shell脚本一般以
.sh
结尾,也可以不是或者没有
2、编写代码
3、执行脚本
2.2 示例
#!/bin/bash
echo "Hello Shall"
echo 今天不冷
说明:
#!
用来声明脚本由什么 shell解释,否则使用默认 shell(bash)#
表示注释本行
注意:
创建文件时:
- 如果是在windows中创建编写的Shell脚本文件,拖拽到Liunx下无法运行,因为Windows与Liunx下
\表示的含义不同
,所以导致程序无法被识别- 解决方案1:
- 在Liunx中使用
vim编辑器
,在最后一行模式(命令行模式) 下输入set ff=unix
- 解决方案2:
- 在Linux中安装dos2unix,使用dos2unix转换
- 步骤:
- 1、命令行输入:sudo apt-age install dos2unix
- 2、命令行输入:dos2unix 文件地址
编写代码:
#
:注释
#!
:用来声明脚本由什么 shell 解释
echo
:否则使用默认 shell 输出执行脚本方式:
方式1:使用
sh
或bash
- sh 文件名
- bash 文件名
方式2:
./
文件名
- 需要可执行权限,添加权限命令:chmod +x 文件名
- +增加权限
- -减少权限
- x:可执行
- 方式1、方式2 会开启 子Shell
方式3:
source
文件名 或. 文件名
- source 文件路径 借鉴cshell
- . 文件路径 借鉴bash
- 方式3 不会开启子Shell
开启子Shell与不开启的区别:
- 开启子Shell后,子Shell中的变量父Shell不可见
- 区别:
./
和bash
执行过程基本一致,
bash
明确指定 bash 解释器去执行脚本,脚本中#!指定的解释器不起作用./
首先检测#!,使用#!指定的 shell,如果没有使用默认的 shell- 用./和 bash 去执行会在后台启动一个新的 shell去执行脚本
- 用. 或source 去执行脚本不会启动新的 shell,直接由当前的 shell去解释执行脚本
3. 变量
3.1 分类
- 按是否为系统提供分类
- 系统变量
- 自定义变量
- 按作用范围分类
- 全局变量:当前Shell中的所有子Shell都可使用(自己与其中的子Shell)
- 局部变量:当前Shell中使用(自己使用)
3.2 系统预定义变量
3.2.1 常用系统变量
HOME、PWD、SHELL、USER
#!/bin/bash
#家目录的路径
echo $HOME
#当前路径
echo $PWD
#Shell解析器的路径
echo $SHELL
#当前用户名
echo $USER
# #系统提供的所有全局变量可以使用env命令查询
# env
# echo -----------------------------
# NAME=aaa
# #当前Shell中的所有变量
# set
3.2.2 查看系统变量的值
语法:
echo 变量名
如:
echo $HOME
3.2.3 显示系统中的全局变量
语法:
env或printenv
如:
env
printenv
3.2.4 显示当前Shell中的所有变量
语法:
set
如:
set
3.3 自定义变量
3.3.1 定义变量
变量名=变量值
注意:
- =号前后不能有空格
- 声明的变量为局部变量
3.3.2 撤销变量
unset 变量名
3.3.3 声明只读变量
readonly 变量
示例:
#!/bin/bash
readonly num=10
#unset num
num=100
echo $num
3.3.4 引用变量
$变量名
注意:如果变量不存在返回空
3.3.5 导出变量
-
作用:
- 将当前变量导出到父shell中
- bash执行,导出到父shell中,当前程序关闭导出变量将被销毁
- source执行,导出到系统变量中
-
语法:
export 变量名
-
注意:
- 使用./ 或 bash 执行,只在当前终端有效,当前程序关闭导出变量将被销毁;
- 所以需要使用source 或 . 执行,会导出至系统变量,直至Linux关机重启该变量才会被销毁
-
示例:
#!/bin/bash export DATA=10
-
bash执行脚本:
-
source执行脚本
3.3.6 将变量强制转换为数字
declare -i 变量
示例:
#!/bin/bash
declare -i num=10
num=$num+1
echo $num
3.3.7 定义规则
- 变量名由字母、数字、下划线组成,不能使用数字开头,环境变量名不建议大写
- =号两侧不能有空格
- 在bash中,
变量默认
类型为都是字符
,无法直接
进行数值运算 - 变量值如果有空格,需要使用双引号或单引号包裹
注意:“” 与 ''的区别
- ""会解析变量值
- ''不会解析变量值
示例:
#!/bin/bash
num=1000
echo "num=$num"
echo 'num=$num'
3.4 预设变量
$#: 传给 shell 脚本参数的数量
$*: 传给 shell 脚本参数的内容
$1、$2、$3、...、$9:运行脚本时传递给其的参数,用空格隔开
$?: 命令执行后返回的状态
"$?": 用于检查上一个命令执行是否正确(在 Linux 中,命令退出状态为 0 表示该命令正确执行,任何非 0 值表示命令出错)。
$0: 当前执行的进程名
$$: 当前进程的进程号
"$$": 最常见的用途是用作临时文件的名字以保证临时文件不会重复
示例1:
#!/bin/bash
#获取执行当前shell脚本传入的参数个数
echo $#
#获取执行当前shell脚本传入的参数
echo $*
#获取执行当前shell脚本传入的第1个参数
echo $1
#获取执行当前shell脚本传入的第2个参数
echo $2
#获取执行当前shell脚本传入的第3个参数
echo $3
ls
#$?获取上一条命令执行状态,0表示成功,非0失败
echo $?
#test.c不存在时,返回1
cat test.c
echo $?
#创建后,返回0
touch test.c
echo $?
echo $0
echo $$
echo "$$"
touch "$0$$.txt"
3.5 脚本变量的特殊用法
"":包含的变量会被解释
'':包含的变量会作为字符串处理
``:反引号中的内容作为系统命令,并执行其内容,可以替换输出为一个变量
\:同c语言\n \t \r \a等echo命令需加-e转义(bash解析器需求)
( 命令 ):由子shell来完成,不影响当前shell中的变量,命令前后必须有空格
{ 命令 }:在当前shell中执行,会影响当前变量,命令前后必须有空格
示例1:转义字符\
#!/bin/bash
#以sh运行会将-e作为字符串,但是可以解析其中的转意字符
#以bash运行-e说明将打印的内容中的转意字符成为转意字符,否则当前普通字符串处理
#./使用的是#!后执行的shell解析器,如果没有#!,使用默认的shell解析器
echo -e "123\n456"
echo -e '#\n#'
示例2:
#!/bin/bash
echo date
echo "date"
echo 'date'
echo `date`
num=1
(
num=10
echo "()中的num=$num"
)
echo "()外的num=$num"
data=1
{
data=10
echo "{}中的data=$data"
}
echo "{}外的data=$data"
4. 键盘录入
语法:
read 变量名
或
read -p 提示内容 变量名
示例:
#!/bin/bash
#read
#语法1:read 变量名
#语法2:read -p 提示的内容 变量名
#输入人名
#语法1
# echo 请输入姓名
# name=""
# read name
# echo "您输入的姓名为:$name"
name=""
read -p 请输入姓名: name
echo "您输入的姓名为:$name"
# 结果:
# 请输入姓名:张三
# 您输入的姓名为:张三
5. 条件测试语句
5.1 语法
语法1:
test 条件
语法2:
[ 条件 ]
5.2 文件测试
文件测试: 测试文件状态的条件表达式
-e 是否存在
-d 是否为目录
-f 是否为文件
-r 是否可读
-w 是否可写
-x 是否可执行
-L 是否连接
-c 是否字符设备
-b 是否块设备
-s 文件非空
示例:
#!/bin/bash
#输入文件名判断文件是否存在
#test 条件
#[ 条件 ]
filename=""
read -p 请输入文件名 filename
test -e $filename
echo "文件是否存在:$?"
test -f $filename
echo "是文件吗:$?"
test -d $filename
echo "是文件夹吗:$?"
test -r $filename
echo "是否可读:$?"
test -w $filename
echo "是否可写:$?"
test -x $filename
echo "是否可执行:$?"
[ -s $filename ]
echo "是否非空:$?"
5.3 字符串测试
= 两个字符串相等
!= 两个字符串不相等
-z 空串
-n 非空串
注意:
= !=前后要有空格
示例:
#!/bin/bash
str01="hello"
str02="world"
str03=$str01
echo "str01=$str01"
echo "str02=$str02"
echo "str03=$str03"
[ $str01 = $str02 ]
echo -e "str01与str02内容是否相同?\t$?"
[ $str01 = $str03 ]
echo -e "str01与str03内容是否相同?\t$?"
[ $str01 != $str03 ]
echo -e "str01与str03内容是否不相同?\t$?"
str04="123"
test -z $str04
echo -e "str04是否为空字符串?\t$?"
# str01=hello
# str02=world
# str03=hello
# str01与str02内容是否相同? 1
# str01与str03内容是否相同? 0
# str01与str03内容是否不相同? 1
# str04是否为空字符串? 1
5.4 数值测试
-eq 数值相等
-ne 数值不相等
-gt 数 1 大于 数 2
-ge 数 1 大于等于 数 2
-lt 数 1 小于 数 2
-le 数 1 小于等于 数 2
示例:
#!/bin/bash
# -eq 数值相等
num01=1
num02=10
[ $num01 -eq $num02 ]
echo $?
# -ne 数值不相等
[ $num01 -ne $num02 ]
echo $?
# -gt 数 1 大于数 2
[ $num01 -gt $num02 ]
echo $?
# -ge 数 1 大于等于数 2
[ $num01 -ge $num02 ]
echo $?
# -le 数 1 小于等于数 2
[ $num01 -le $num02 ]
echo $?
# -lt 数 1 小于数 2
[ $num01 -lt $num02 ]
echo $?
# num=1
#当num存在取num的值,如果num不存在取10
# echo ${num:-10}
# echo $num
echo ${num:=10}
echo $num
# 1
# 0
# 1
# 1
# 0
# 0
# 10
# 10
5.5 复合测试
&&:
command1 && command2
&&左边命令(command1)执行成功(即返回 0) shell 才执行&&右边的命令(command2)
||:
command1 || command2
||左边的命令(command1)未执行成功(即返回非 0) shell 才执行||右边的命令(command2)
多重条件:
-a:与
-o:或
!:非
示例1:
#!/bin/bash
#输入一个数判断是否在0~100之间
read -p "请输入一个数" num
test $num -gt 0 && test $num -lt 100
echo "$num 是否在0~100之间:$?"
# 结果:
# 请输入一个数12
# 12 是否在0~100之间:0
示例2:
#!/bin/bash
#输入文件名称,判断文件是否可读可写可执行
read -p "请输入文件名称" fileName
test -r $fileName -a -w $fileName -a -x $fileName
echo "文件可读可写可执行:$?"
test -r $fileName -o -w $fileName
echo "文件可读或可写:$?"
test ! -x $fileName
echo "文件是否不可执行:$?"
# 请输入文件名称test.c
# 文件可读可写可执行:1
# 文件可读或可写:0
# 文件是否不可执行:0
6. 控制语句
6.1 if
语法1:
if [ 条件 1 ];then
执行第一段程序
else
执行第二段程序
fi
示例:
#!/bin/bash
# if [ 条件 1 ];then
# 执行第一段程序
# else
# 执行第二段程序
# fi
#判断文件是否存在,如果存在则不管,如果不存在则创建
if [ -e "a.txt" ];then
echo "不管"
else
touch "a.txt"
fi
语法2:
if [ 条件1 ];then
执行第1段程序
elif [ 条件2 ];then
执行第2段程序
elif [ 条件3 ];then
执行第3段程序
...
else
执行第n段程序
fi
示例:
#!/bin/bash
# if [ 条件1 ];then
# 执行第1段程序
# elif [ 条件2 ];then
# 执行第2段程序
# elif [ 条件3 ];then
# 执行第3段程序
# ...
# else
# 执行第n段程序
# fi
#判断成绩对应的区间
read -p "请输入考试成绩:" score
if [ $score -lt 0 -o $score -gt 100 ];then
echo "成绩有误"
elif [ $score -lt 60 ];then
echo "D"
elif [ $score -lt 70 ];then
echo "C"
elif [ $score -lt 85 ];then
echo "B"
else
echo "A"
fi
# 请输入考试成绩:99
# A
6.1 case语句
语法:
case $变量名 in
"值1")
语句1
;;
"值2")
语句2
;;
*)
语句3
;;
esac
扩展:
exit 1 #退出shell
示例:
#!/bin/bash
# case $变量名 in
# "值1")
# 语句1
# ;;
# "值2")
# 语句2
# ;;
# *)
# 语句3
# ;;
# esac
# |
#输入月份输出季节
read -p "请输入月份" month
case $month in
"3" | "4" | "5")
echo "春季"
;;
"6" | "7" | "8")
echo "夏季"
;;
"9" | "10" | "11")
echo "秋季"
;;
"12" | "1" | "2")
echo "冬季"
;;
*)
echo "输入有误"
;;
esac
# 请输入月份9
# 秋季
6.3 for语句
语法:
for (( 初始值; 限制值; 执行步阶 ))
do
程序段
done
示例:
#!/bin/bash
# for (( 初始值; 限制值; 执行步阶 ))
# do
# 程序段
# done
# 计算100以内数之和
declare -i sum=0
declare -i i=0
for(( i=0;i<101;i++ ))
do
sum=$sum+$i
done
echo $sum
#5050
6.4 while语句
语法:
while [ 条件 ]
do
程序段
done
示例:
#!/bin/bash
# while [ 条件 ]
# do
# 程序段
# done
#打印100以内的数
declare -i i=0
while [ $i -le 100 ]
do
num=$(( i % 2 ))
if [ $num -eq 0 ];then
echo $i
fi
i=$i+1
done
6.5 扩展
变量名=$(( 值1 + 值2 ))
变量名=$(( 值1 - 值2 ))
变量名=$(( 值1 * 值2 ))
变量名=$(( 值1 / 值2 ))
变量名=$(( 值1 % 值2 ))
变量名=$(( 变量名-- ))
变量名=$(( --变量名 ))
变量名=$(( 变量名++ ))
变量名=$(( ++变量名 ))
变量名=$(( -10 > 1 ? 10 : 1 ))
示例:
echo $(( 10 + 2))
echo $(( 10 - 2))
echo $(( 10 * 2))
echo $(( 10 / 2))
echo $(( 10 % 2))
num=1
echo $(( num-- ))
echo $(( -10 > 1 ? 10 : 1 ))
# 12
# 8
# 20
# 5
# 0
# 1
# 1
6.6 until语句
语法:
until [ condition ]
do
程序段
done
注意:这种方式与 while 恰恰相反,当 condition 成立的时候退出循环,否则继续循环。
示例:
#!/bin/bash
# until [ condition ]
# do
# 程序段
# done
#打印100以内的数
declare -i num=0
until [ $num -eq 100 ]
do
echo $num
num=$num+1
done
7. 函数
7.1 步骤
- 定义
- 调用
7.1.1 定义
语法1:
函数名(){
程序段
}
语法2:
function 函数名(){
程序段
}
7.1.2 调用
语法
函数名 参数1 参数2
7.2 示例
#!/bin/bash
# function add()
# {
# declare -i sum=0
# sum=$1+$2
# return $sum
# }
add()
{
declare -i sum=0
sum=$1+$2
return $sum
}
add 1 9
echo $?
//10
注意:
- 当函数定义与函数调用不在一个文件中需要在函数调用所在的文件中使用
source引用函数定义的文件
#19_01shell.sh文件
#!/bin/bash
add()
{
declare -i sum=0
sum=$1+$2
return $sum
}
#19_02shell.sh文件
#!/bin/bash
source 19_01shell.sh
add 1 10
echo $?
8. 引入问题 答案
#!/bin/bash
# 判断用户家目录下(~)下面有没有一个叫 test 的文件夹
# 如果没有,提示按 y 创建并进入此文件夹,按 n 退出
# 如果有,直接进入,提示请输入一个字符串,并按此字符串创建一个文件,
# 如果此文件已存在,提示重新输入,重复三次自动退出,不存在创建完毕,退出
#进入家目录
cd ~
#封装函数
mkdirtest()
{
if [ -e "test" ];then
echo "文件夹存在"
cd test
return 0
else
echo "文件夹不存在, 是否创建?y|n"
read tag
case $tag in
"Y" | "y")
mkdir "test"
cd test
return 0
;;
*)
echo "退出";
return 1
;;
esac
fi
}
mkdirtest
if [ $? -eq 0 ];then
for (( i=0;i<3;i++ ))
{
read -p "请输入一个文件名" filename
if [ -e $filename ];then
echo "文件已存在,请重新输入文件名"
else
echo "文件不存在"
touch $filename
echo "每天中午吃饭都不知道吃啥" >> filename
echo "但是已创建"
break
fi
}
fi
8. 引入问题 答案
#!/bin/bash
# 判断用户家目录下(~)下面有没有一个叫 test 的文件夹
# 如果没有,提示按 y 创建并进入此文件夹,按 n 退出
# 如果有,直接进入,提示请输入一个字符串,并按此字符串创建一个文件,
# 如果此文件已存在,提示重新输入,重复三次自动退出,不存在创建完毕,退出
#进入家目录
cd ~
#封装函数
mkdirtest()
{
if [ -e "test" ];then
echo "文件夹存在"
cd test
return 0
else
echo "文件夹不存在, 是否创建?y|n"
read tag
case $tag in
"Y" | "y")
mkdir "test"
cd test
return 0
;;
*)
echo "退出";
return 1
;;
esac
fi
}
mkdirtest
if [ $? -eq 0 ];then
for (( i=0;i<3;i++ ))
{
read -p "请输入一个文件名" filename
if [ -e $filename ];then
echo "文件已存在,请重新输入文件名"
else
echo "文件不存在"
touch $filename
echo "每天中午吃饭都不知道吃啥" >> filename
echo "但是已创建"
break
fi
}
fi