Shell就是一个命令行解释器,它的作用就是遵循一定的语法将输入的命令加以解释并传给系统。它为用户提供了一个向Linux发送请求以便运行程序的接口系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。 Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。作为命令语言,它互动式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高阶语言中才具有的控制结构,包括循环和分支。它虽然不是 Linux系统内核的一部分,但它调用了系统内核的大部分功能来执行程序、创建文档并以并行的方式协调各个程序的运行。
Shell语言
使用Shell编译静态库,Shell种类非常多,Bash是其中的一个使用最多的种类(免费,易用)
我使用的是mac本,自带Shell脚本开发
Hello world!
1、首先创建一个脚本文件:touch hello.sh
2、编写Shell程序
#定义文件声明 !表示约定标记, 它会高斯系统这个脚本需要什么样的解释器来执行,即一种脚本体现
#!/bin/bash
echo "Hello world!"
3、修改文件权限
首先查看文件权限:ls -l hello.sh
修改权限:chmod +x ./hello.sh
4、执行脚本文件
命令:./hello.sh
语法
1、注释
# 表示注释
在Shell脚本中没有多行注释,只有单行注释
2、变量
变量定义注意事项:
- 定义变量时候,变量名不需要加$符号
- 变量名和等号不能够有空格
- 变量名首字母必须是是字母或者下划线(a_z,A_Z)
- 变量名中间不允许有空格
2.1只读变量:关键字readonly
name="小生生"
readonly name
_age=18
name="小白"
echo $name
echo $_age
这段代码会有警告:#readonly variable
2.2删除变量
语法:unset 变量名
只读变量 无法删除
2.3变量类型
- 本地变量,作用域:整个bash进程可以使用 语法: name="xiaoming"
- 局部变量,作用域:当前代码段(修饰符local) 语法:local name="xiaohong"
- 环境变量,作用域:当前shell进程以及子进程 语法:export name="xiaohei"
- 位置变量,给我们脚本文件传递参数 使用${数字} 0表示文件名称,从1开始 执行的时候可以传参,/hello.sh xiaohong 150 男
#给我们的脚本文件传递参数
name=${1}
age=${2}
sex=${3}
echo "姓名:${name}, 年龄 ${age}, 性别 ${sex}"
- 特殊变量:
1)${0}文件名称
2)${?}表示返回上一个命令执行状态返回值:0表示成功,1表示执行结果,2表示程序状态返回码(0-255)系统预留错误(1,2,127)- $# 表示参数的个数
4)$* 或者 $@ 表示参数列表(区别:前者将所有参数组成一个字符串,后者分开的字符串)
5)$$ 获取当前shell进程ID
6) $! 表示执行上一个指令的PID(进程ID)
- $# 表示参数的个数
3、字符串
3.1 单引号
name='Andy'
3.2 双引号
name="Andy"
3.3 字符串拼接
方式一
name='Andy'
age=18
sex=2
info=${name}${age}${sex}
echo $info
方式二
name='Andy'
age=18
sex=2
info="${name},${age},${sex}"
echo $info
3.4 获取字符串长度
语法结构:${#变量名}
name="Andy"
echo ${#name}
3.5 字符串截取
语法:${变量名:开始位置:截取位置}
name="I am Andy"
length=${#name}
result=${name:5:length-1}
echo $result
3.6字符串删除
语法一:${变量名#删除字符串 正则表达式}
作用:从字符串开头开始匹配要删除的字符串
#删除头字符 只能删除从头开始的
name="I am Andy"
result=${name#I}
echo $result 结果am Andy
#查找制定字符,并删除前面所有字符(包含自己)
name="I am Andy"
result=${name#*A}
echo $result 结果ndy
#删除制定范围(只能从第一个开始删除,删除到那里)
name="I am Andy"
result=${name#I*A}
echo $result 结果ndy
语法二:${变量名##删除字符串 正则表达式}
作用:从字符串的结尾开始匹配 删除字符串
name="I am Andy"
result=${name##*A}
echo $result 结果ndy
语法三:${变量名%删除字符串 正则表达式}
作用:从字符串结尾开始匹配,删除匹配的字符串
name="I am Andy"
result=${name%Andy}
echo $result
#查找自定字符第一个,并删除前面所有字符(包含自己)
name="I am Andy"
result=${name%A*}
echo $result 结果 I am
4、布尔运算符
“!”:非运算,
a=100
b=200
if [ $a != $b ]
then
echo "a不等于b"
fi
“-o”:或运算
a=100
b=200
if [ $a -lt 200 -o $b -gt 200 ]
then
echo "成立"
fi
“-a”: 与运算
a=100
b=200
if [ $a -lt 200 -a $b -gt 200]
then
echo "成立"
else
echo "不成立"
fi
5、逻辑运算符
"&&":逻辑且
a=100
b=00
if [ $a -lt 200 ] && [ $b -gt 200 ]
then
echo "成立"
else
echo "不成立"
fi
“||”:逻辑或
a=100
b=200
if [ $a -lt 200 ] || [$b -gt 200 ]
then
echo "成立"
else
echo "不成立"
fi
6、字符串运算
6、1
“=”:检测两个字符串是否相等,如果相同则返回true
a="Andy"
b="你好"
if [ $a = $b ]
then
echo "字符串相等"
else
echo "字符串不相等"
fi
6、2
“!=”:检测两个字符串是否相等,如果不相等返回true
a="Andy"
b="你好"
if [ $a != $b ]
then
echo "字符串a不等于字符串b"
else
echo "字符串a等于字符串b"
fi
“-z”:检测字符串的长度是否为0,如果是返回true
a="Andy"
if [ -z $a ]
then
echo "a不存在"
else
echo "a存在"
fi
“-n”:检测字符串长度是否为0,如果不是0,返回true
a="Andy"
if [ -n "$a" ]
then
echo "a存在"
else
echo "a不存在"
fi
7、文件测试运算符
file="/Users/zhaoruisheng/Desktop"
"-d file":检测一个文件是不是一个目录,如果是返回true
file="/Users/zhaoruisheng/Desktop"
if [ -d $file ]
then
echo "是一个目录"
else
echo "不是一个目录"
fi
"-r file":检测文件是否可读,如果是返回true
file="/Users/zhaoruisheng/Desktop/Shell/hello.sh"
if [ -r $file ]
then
echo "可读"
else
echo "不可读"
fi
"-w file:检测文件是否可写,如果是返回true
file="/Users/zhaoruisheng/Desktop/Shell/hello.sh"
if [ -w $file ]
then
echo "可写"
else
echo "不可写"
fi
"-x file":检测文件是否是可执行文件,如果是,那么返回true
file="/Users/zhaoruisheng/Desktop/Shell/hello.sh"
if [ -w $file ]
then
echo "可执行"
else
echo "不可执行"
fi
"-f file"检测一个文件是否是普通文件(既不是目录,也不是设备文件),如果是返回true
file="/Users/zhaoruisheng/Desktop/Shell/hello.sh"
if [ -f $file ]
then
echo "普通文件"
else
echo "系统文件"
fi
"-s file":检测文件是否为空(文件有内容),如果是,那么返回true
file="/Users/zhaoruisheng/Desktop/Shell/hello.sh"
if [ -s $file ]
then
echo "文件有内容"
else
echo "文件没有内容"
fi
"-e file":检测文件是否存在(包含了:目录和文件),如果是,返回true
file="/Users/zhaoruisheng/Desktop/Shell/hello.sh"
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
控制流程
1、if语句
语法结构:
if [条件]
then
代码
fi
if else 语句
if [条件]
then
代码
else
代码
fi
if-else-if-else
语法结构
if [条件]
then
代码
elif [条件]
then
代码
else
代码
fi
2、switch
语法结构
case 值 in
模式1)
代码
;;
模式2)
代码
;;
模式3)
代码
;;
esac
number=1
case $number in
1) echo "等于1";;
2) echo "等于2";;
3) echo "等于3";;
esac
3、for循环
语法结构
for 变量名 in item1 item2 ...
do
代码
done
for name in "Andy" "小黑" "小吧"
do
echo $name
done
#读取文件通配符
file="/Users/zhaoruisheng/Desktop/Shell/*"
for name in ${file}
do
echo $name
done
#嵌套循环
for ((a = 1; a < 5; a++ ))
do
echo $a
for ((b = 1; b < 5; b++ ))
do
echo $b
done
done
4、while循环
语法结构
while(条件)
do
代码
done
a=1
while(($a<10))
do echo $a
a=`expr $a + 1`
done
5、until循环
语法结构
until ((条件))
do
代码
done
i=1
until (( $i > 1 ))
do
echo "i小于1"
done
6、break
1、跳出单个循环
for ((i=0; i < 10; i ++))
do
echo "当前i的值是$i"
if [ $i == 5 ]
then
break
fi
done
2、跳出内部循环
for ((i = 0; i< 10; i++))
do
echo "外层循环i的值$i"
for ((j =0; j<10;j++))
do
echo "内层循环j的值$j"
if [ $j == 5 ]
then
echo "退出内层循环"
break
fi
done
done
3、跳出外部循环(break 数字:表示退出几层循环)
for ((i = 0; i< 10; i++))
do
echo "外层循环i的值$i"
for ((j =0; j<10;j++))
do
echo "内层循环j的值$j"
if [ $j == 5 ]
then
echo "退出内层循环"
break 2
fi
done
done
7、continue
for ((i=0;i < 5; i++))
do
echo "外层循环i的值$i"
if [ $i == 2]
then
echo "跳出"
continue
fi
done
文件包含
语法一: ./filename
文件A -> fileA.sh
脚本内容
#!/bin/bash
echo "我是文件A"
文件B -> fileB.sh
脚本内容
#!/bin/bash
#文件B包含文件A
source ./fileA.sh
echo "我是文件B"
cat命令
作用
- 查看文件内容,
- 连接文件,
- 创建一个或者多个文件,
- 重定向输出到终端
- 重定向到文件
语法:
- 查看文件内容 cat test.sh
- 查看内容带行号 cat -n test.sh
- 查看内容(有内容的行) cat -b test.sh
- 在每一行的后面增加一个美元符($) cat -e test.sh 作用于多行内容转换为一行内容
- 单独输入一个cat时候,接收输入内容,并输出内容 退出control+c
获取用户输入
read命令
1、接收一个输入内容
#写法一:
echo "请输入你的名字"
read name
echo "你的名字是${name}"
#写法二:
read -p "请输入你的名字" name
echo "你的名字是${name}"
2、超时->输入的时候,过了多少时间没有输入,就过期
if read -t 6 -p "请输入你的名字" name
then
echo "你的名字是${name}"
else
echo "超时"
fi
3、隐藏输入内容,相当于输入密码 -s
read -s -p "请输入你的密码" name
echo "你的密码是${name}"
4、从文件中读取内容
cat testA.sh | while read line
do
echo "内容${line}"
done
printf命令使用
1、prindf命令和echo命令的区别
- printf不换行,echo自动换行
- print用于格式打印,echo用于标准输出
2、printf语法结构
printf format-string 参数列表
#打印输出表头
printf "%-10s %-8s %-4s \n" 姓名 性别 体重kg
#打印输出内容
printf "%-10s %-8s %-4s \n" Andy 男 64kg
printf "%-10s %-8s %-4s \n" jar 男 65kg
printf "%-10s %-8s %-4s \n" 帅牛哥 男 100kg
"%-10"指的是一个宽度为10的字符,"-"表示左对齐
"s"表示字符串字符
"%-4.sf"指的是一个宽度为4,小数点2位,"f"表示小数
3、printf也可以用单引号或者不用
多个参数输出需要用符号
函数
1、语法结构
function testFunction() {
echo 第一个参数
}
testFunction
输入/输出重定向
1、输入输出重定向->控制台/文件
1.1、将file中的内容读取到控制台 wc 文件名称
- 第一个参数:文本行数
- 第二个参数:文本词数
- 第三个参数:文本字节数
使用cat 来使用输入重定向
1.2、输出重定向 “>”:我们把方向指向一份文件,那么将文件中的内容删除,写入新的内容
">>"追加
脚本文件fileA.sh代码
#!/bin/bash
echo "我是文件A"
脚本文件fileB.sh代码
#!/bin/bash
echo "我是文件B"
echo "Hello Dream" > fileA.sh
echo "追加" >> fileA.sh
执行脚本代码
./fileB.sh
总结:替换内容->将fileB.sh输出内容替换了fileA.sh中内容
2、“expr”命令
- 方式一
a=100
b=200
val=`expr $a + $b`
echo "val的值${val}"
- 方式二
a=100
b=200
val=$(expr $a + $b)
echo "val的值是${val}"
- 方式3
a=100
b=200
val=$[ $a + $b ]
echo "val的值是${val}"
3、浮点数
3.1、bash的基本用法
"bc"命令:是在我们的shell脚本语言中中提示符,通过bc命令访问bash计算器
- 第一步:输入bc命令打开计算器
- 第二步:开始计算
- 第三步:直接输入
- quit 退出
将bash计算器应用到shell脚本中
语法结构:val=$(echo "options;expression" | bc)
options:表示选项,可以设置精度
expression:表达式
#案例一:基本语法
val=$(echo "sacal=1;1.56 * 100" | bc)
echo "val的值为:$val"
总结:1.56*100->称为表达式
sacal=1表示选项
#案例二:
val1=1.314
val2=0.618
val3=$(echo "$val1 * $val2" | bc)
val4=$(echo "$val3 * $val2" | bc)
echo "val4的值是:$val4"
#总结:简单计算可以,复杂计算麻烦
#案例三:bc命令+输入重定向
语法格式:
val=$(bc<<FG
iptions
statements
expression
FG
)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
val1=1.314
val2=0.618
val3=$(bc << FG
a = ($val1 * $val2)
b = ($a * $val2)
a + b
FG
)
echo "val3的值是:$val3"
管道
使用场景:有时候我们需要一个命令的输出作为另一个命令的输入
语法结构: command1 | command2
4、重定向->外部文件操作
4.1、理解输入和输出、错误概念:shell脚本语言中将输入、输出、错误分为 了三种描述符(3中状态),分别对应的值(0,1,2)
- 第一种:STDIN->标准输入->0->键盘输入
第一点:直接输入cat命令->接收键盘输入,然后打印到控制台
第二点:采用cat接收一个非STDIN文件输入,说白了就是读取文件内容,输出到控制台 cat fileA.sh - 第二种:STDOUT->标准输出->1->对应终端显示
第一点:重定向->输出文件中,原来输出到控制到,现在重定向到文件
#ls-l:显示文件时间信息()
#who:打印当前登录系统的用户信息
who >> testB.sh
cat testB.sh
#ni hao
我从不装逼
zhaoruisheng console Mar 6 13:20
zhaoruisheng ttys000 Mar 6 13:20
第二点:重定向错误->输入到文件中
ls -al a.sh > testB.sh
总结一:到我们的命令发生错误时候,shell脚本语言并不会将错误信息重定向到输出重定向文件,shell及时创建了输出重定向文件,当时也不会写入内容
总结二:虽然创建了文件,但是文件内容是空的
总结三:shell脚本语言对于错误信息处理和普通信息输出是分开的,那么如果我们希望报错错误信息,那么需要采用STDERR实现
- 第三种:STUERR->标准错误-> 2
ls -al a.sh 2> testB.sh
5、重定向错误信息
5.1、只定向错误
- 将STDERR文件描述改为2 ls -al a.sh 2> testB.sh
- 将STDOUT和STDERROR结合使用
第一步:执行命令
ls -al testA.sh fileA.sh 2> testB.sh
第二步:查看结果
-rwxr-xr-x@ 1 zhaoruisheng staff 74 3 5 16:53 testA.sh
5.2、重定向错误和重定向数据 - STDOUT和STDERROR结合使用(1和2),保存错误信息和数据,即:在后面追加就行了,可以追加到别的文件
第一步:执行命令ls -al testA.sh fileA.sh 2> testB.sh 1>> testB.sh
第二步:查看数据和错误
ls: fileA.sh: No such file or directory
-rwxr-xr-x@ 1 zhaoruisheng staff 74 3 5 16:53 testA.sh - 同时将数据和错误信息输入到同一个文件 “&>”
ls -al testA.sh fileA.sh 2> testB.sh 1&> testB.sh
输入和输出
6、重定向输出->深入一点
6.1、临时重定向 “&”
案例一:
echo "Hello world" >&2
echo "你好"
案例二:
当执行 ./hello.sh 2> testB.sh后 Hello world 被重定向到testB中了,使用>&2后就会别被标记为错误信息, 1位输出信息
6.2、永久重定向 exec命令
案例一:保存数据
exec 1> testB.sh
echo "Hello world"
echo "你好"
echo "挂卡很快就"
echo "kjhlakjhflha"
案例二:保存错误信息和数据
exec 1> fileContent.sh
echo "Hello world"
echo "你好"
echo "JAR童鞋"
exec 2> error.sh
echo "我报错了" >&2
echo "不好意思,我拒绝你,你不配"
7、重定向输入
从文件A中读取内容到文件B中
exec 0< fileContent.sh
count=1
while read line
do
echo "当前行数:$count 读取内容:$line"
count=$[ $count + 1 ]
done
8、重建自己的重定向
0、1、2是系统提供的,我们可以自定义
- 创建自己的输出文件描述(创建新的文件或者替换内容)
案例一:创建新的文件或者替换内容
exec 3> fileContent.sh
echo "Hello world"
echo "你好" >&3
echo "JAR童鞋"
exec 2> error.sh
echo "我报错了" >&2
echo "不好意思,我拒绝你,你不配"
案例二:追加内容
exec 3>> fileContent.sh
echo "Hello world"
echo "你好" >&3
echo "JAR童鞋" >&3
exec 2> error.sh
echo "我报错了" >&2
echo "不好意思,我拒绝你,你不配"
- 恢复文件原始的描述符
#最开始是3重定向到1
exec 3>&1
exec > fileContent.sh
echo "Hello world"
exec 2> error.sh
echo "我报错了" >&2
echo "不好意思,我拒绝你,你不配"
exec 1>&3
echo "搞完了"
- 创建文件输入的描述符
exec 6<&0
exec 0< fileContent.sh
count=1
while read line
do
echo "当前行数:$count 读取内容:$line"
count=$[ $count + 1 ]
done
#恢复之前文件描述符
exec 0<&6
echo "输出"
read -p "你是不是一个男人?" isMan
case $isMan in
Y|y) echo "男人" ;;
N|n) echo "女人" ;;
*) echo "人妖" ;;
- 创建读写的描述符
exec 3<>fileContent.sh
#从fileContent中读取文件
read line <&3
echo "Read: $line"
#写入文件
echo "I have a deam" >&3
注意:替换了第二行?因为我们文件指针读取完了第一行,文件指针指向了第二行,所以第二行之后所有的内容被替换
- 关闭文件描述符:关闭后,在当前脚本中读写都不允许,读取和写入都会报错
语法结构:exec 3>&-
exec 3>fileContent.sh
echo "我是天才">&3
exec 3>&-
echo "写入锁定">&3
read line <&3
echo "数据读取:$line"
- 打开文件描述
lsof命令:非管理员用户也可以采用这个命令插件系统信息
命令路径:/uset/sbin/lsof
/usr/sbin/lsof -a -p $$ -d 0,1,2
~~~~~输出
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 519 zhaoruisheng 0u CHR 16,0 0t4515 665 /dev/ttys000
bash 519 zhaoruisheng 1u CHR 16,0 0t4515 665 /dev/ttys000
bash 519 zhaoruisheng 2u CHR 16,0 0t4515 665 /dev/ttys0000
分析:/usr/sbin/lsof->表示lsof命令
"-a"->表示将两个选项结果进行(AND操作)拼接(格式化输出)
"-p"->进程ID
"$$"->表示环境变量
"-d"->表示文件描述符(例如:0、1、2)
分析结果
COMMAND->表示正在运行的命令名称(取出名字前9个字符)
PID->进程ID
USER->进程所属登录名
FD->文件描述符号以及访问类型(r读、w写、u读写)
TYPE->表示文件类型(CHR:字符型,BLK块类型,DIR目录、REG文件)
DEVICE->设备号
SIZE/OFF->如果存在,那么表示文件大小
NODE->表示本地文件节点号
NAME->表示文件名称(路径)
exec 3> fileContent.sh
exec 6> fileB.sh
exec 7< fileErr.sh
/usr/sbin/lsof -a -p $$ -d 0,1,2,3,6,7
~~~~~~执行结果
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 2268 zhaoruisheng 0u CHR 16,0 0t5834 665 /dev/ttys000
bash 2268 zhaoruisheng 1u CHR 16,0 0t5834 665 /dev/ttys000
bash 2268 zhaoruisheng 2u CHR 16,0 0t5834 665 /dev/ttys000
bash 2268 zhaoruisheng 3w REG 1,8 0 55364172 /Users/zhaoruisheng/Desktop/Shell/fileContent.sh
bash 2268 zhaoruisheng 6w REG 1,8 0 55378786 /Users/zhaoruisheng/Desktop/Shell/fileB.sh
- 阻止命令输出 > /dev/null
案例一:
打印命令+ > /dev/null
案例二:清空文件
cat /dev/null > fileContent.sh
- 创建临时文件
创建本地的临时文件
案例一:保证当前文件目录唯一
mktemp nihao.sh
#在脚本中创建临时文件
#创建临时文件
tempfile=$(mktemp tempfile.xxxxxxx)
#重定向文件
exec 3> $tempfile
#打印文件名称
echo "文件名称 $tempfile"
#给文件输入内容
echo "我是个临时文件" >&3
echo "我是个临时文件1" >&3
echo "我是个临时文件2" >&3
#关闭文件
exec 3>&-
#打印文件内容
cat $tempfile
#删除临时文件
rm -f $tempfile 2> /dev/null
在/temp目录下创建临时文件 "-t"
案例一:
mktemp -t testfile.xxxxx
~~~~~执行结果
/var/folders/5c/f1wlx5wn2nv1zl04kg_35prm0000gn/T/testfile.xxxxx.8wF10COI(临时目录)
注意:不同系统临时目录不一样
案列二:
#创建临时目录
tempfile=$(mktemp -t tempfile.xxxx)
echo "我是一个临时目录" > $tempfile
#追加内容
echo "追加内容" >> $tempfile
#打印文件路径
echo "文件路径:$tempfile"
cat $tempfile
#删除文件
rm -f $tempfile 2> /dev/null
创建临时目录 "-d"
#当前目录下创建临时目录
tempfile=$(mktemp -d testfile.xxxx)
#进入临时文件
cd $tempfile
#在临时目录中创建临时文件
tempfile1=$(mktemp testfile.xxxx)
tempfile2=$(mktemp testfile.xxxxx)
#自定义文件描述->重定向输出
exec 7>$tempfile1
exec 8>$tempfile2
#打印临时目录路径
echo "临时目录路径:$tempfile"
#向临时文件写入内容
echo "我是临时文件1" >&7
echo "我是临时文件2" >&8
cat $tempfile1
cat $tempfile2
- 记录消息
作用:将输出同事发送显示器和日志文件,重定向的话需要两次,采用tee命令一次完成
#采用管道,一个命令的输出作为另一个命令的输入
案例一:重定向->输出->覆盖
date | tee testB.sh
案例二:追加
date | tee -a testB.sh
案例三:应用到脚本中
操作数据库
导出SQL文件
场景:有一个excel文件,导出.csv文件转成.sql文件
脚本实现
#!/bin/bash
#定义数据库文件(.sql)文件
outfile='test.sql'
#定义域分隔符->分割字符串
IFS=','
while read name sex age mv yd phone
do
cat >> $outfile << EOF
INSERT INTO t_test (name, sex, age, mv, yd, phone) VALUES ('$name', '$sex', '$age', '$mv', '$yd', '$phone');
EOF
done < ${1}
执行操作./hello.sh csvtest.csv
分析:
三个重定向操作
- 第一个:输入重定向done < ${1},{1}第一个参数恰好是一个文件,read通过循环一行行读取,{2}是第二个参数,同事read还会通过IFS进行字符串分割,分割之后自动给参数赋值
- 第二个:输出重定向
cat >> test.sql:等到输入内容,敲回车后输出到制定重定向文件 - 第三个:输入重定向'<<'追加
数据库操作
- 链接数据库->登录数据库