当你进入Linux世界的大门时,就会遇到一个强大而又神奇的工具——Shell。Shell是一种命令行解释器,为你在Linux系统中与计算机进行互动提供了无限的可能性。
学习Shell可以让你获得强大的自动化和脚本编程能力,让你更高效地处理文件和目录、管理进程、配置系统以及执行各种任务。不仅如此,Shell还是与服务器和云平台交互的主要方式,掌握这个技能将助你在开发和运维领域中更上一层楼。
通过学习Shell,你将:
- 掌握命令行界面,成为Linux系统的主人,极大提升工作效率;
- 理解Linux系统的运行方式和机制,探索其背后的原理与逻辑;
- 高效地处理和管理文件、目录以及系统资源;
- 通过编写简单而又强大的Shell脚本,实现自动化的工作流程和任务;
- 成为一名优秀的开发者或运维工程师,拥有更广阔的就业和发展机会。Shell学习曲线不可否认会有一些陡峭,但一旦你理解其基本概念和语法,你会发现它是如此有趣和有用。无论你是初学者还是有一定经验的用户,Shell都能够为你提供强大的工具和技能,让你成为Linux世界中的骄傲。
所以,准备好挑战和探索吧!迈出第一步,掌握Shell,它将是你在Linux旅程中最重要的伙伴之一。开始学习Shell,让命令行的魔力展现在你的指尖吧!
概述
Shell
简介
Shell是一个用C语言编写的程序,它是用户使用Linux的桥梁
Shell既是一种命令语言,又是一种程序设计语言
Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务
优点
Shell属于内置的脚本,程序开发的效率非常高,依赖于功能强大的命令可以迅速地完成开发任务
语法简单,代码写起来比较轻松,简单易学
Shell分类
分类
在linux中有很多类型的shell,不同的shell具备不同的功能,shell还决定了脚本中函数的语法
/bin/sh
/bin/bash【默认】
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
查看流行shell
cat /etc/shells
当前系统使用的Shell
echo $SHELL
基础内容
风格
文件命名规范
文件名.sh .sh是linux下bash shell 的默认后缀
Shell解析器
指定告知系统当前这个脚本要使用的shell解释器【shell文件中的第一句】
#!/bin/bash
编写脚本
创建一个scripts文件夹,用来存储文件
mkdir scripts
cd scripts
编写脚本
vim hello.sh
#!/bin/bash
echo "Hello World"
执行脚本
第一种
bash ./hello.sh 和 sh ./hello.sh
第二种
source hello.sh 和 . hello.sh
第三种
./hello.sh
注意需要权限
chmod +x hello.sh
区别
第一种是在子bash环境下运行,而第二种是在当前bash环境下运行
我们通过运行type source可以看到source is a shell builtin(source是shell的一个内嵌)
我们执行ps -f可以查看当前bash环境,创建一个子bash,继续通过ps -f查看,在当前子bash环境下,你依然可以运行shell文件
从结果上来看,两者好像没有什么区别,但是如果引入更多知识,例如变量:如果子shell【bash】中设置的当前变量,父shell【bash】是可不见的
bash环境
查看当前bash环境
ps -f
创建一个新的bash环境【子bash】
当输入bash命令并按下回车键时,系统将启动一个新的Bash Shell环境,并将当前终端切换到该环境下
返回上级bash
exit
变量
全局变量和局部变量
全局变量
层层嵌套的子bash依然可以访问
局部变量
只在当前的bash中可以访问,子bash和父bash都不能访问呢
查看变量是全局还是局部
全局:env | grep 变量名
局部:set | grep 变量名
系统预定义变量
简介
系统定义好的变量,可以直接拿来用
常用系统变量
查看当前所有的全局系统变量:env
查看当前所有的变量(包含全局和局部的,系统的,用户的):set
$HOME 、$PWD 、$SHELL 、$USER
测试
[root@localhost scripts]# echo $HOME
用户自定义变量
简介
用户自己定义的变量
规则
变量名=变量值
1.等号前后不能有空格
2.在声明变量的时候是不需要添加$符号,但是使用时候需要添加
3.如果定义的是一个字符串,需要将值添加双引号或者单引号
规范
变量命名规范:字母、数字和下划线组成,其中数字不能开头
自定义变量一般都是小写的
在shell中,变量是没有类型的,或者我们理解为全部都是字符串类型
如果变量的值需要做数值运算,可以使用$((1+1))或者$[1+1]的形式
定义局部变量
[root@localhost scripts]# a=10
[root@localhost scripts]# str1="test thi var"
[root@localhost scripts]# echo $a
定义全局变量
[root@localhost scripts]# a=10
[root@localhost scripts]# export a
[root@localhost scripts]# bash
[root@localhost scripts]# echo $a
10
定义一个全局变量需要先声明一个局部变量,然后再通过export导出为一个全局变量
注意:在子bash中修改全局变量,只会再当前环境中生效,不会影响父bash环境,哪怕是你增加export也依然不会影响到父bash环境
Shell脚本中使用变量
我们可以在hello.sh的脚本中去调用全局和局部变量
注意:在shell脚本中使用变量,同样遵循全局和局部变量的规则
#!/bin/bash
echo $txt
只读变量和撤销变量
只读变量
[root@localhost scripts]# readonly a1=10
readonly 变量名=值
在shell中,只读变量相当于是常量,定义之后不允许修改
撤销变量
[root@localhost scripts]# unset a
变量定义之后是可以撤销的,使用unset 变量名就可以撤销了
特殊变量
简介
在Shell中,存在一些特殊变量,他们具有特殊的意义
$n
$n代表接受参数,n是数字,代表在执行脚本时候传递的参数数量
例如$1-$9代表第一个到第九个参数,十以上的数字,可以使用大括号包裹,例如${10}
比较特殊的是$0,代表当前脚本名称
$#
$#获取输入参数的个数,一般用于循环中,判断参数的个数是否正确,加强脚本的健壮性
$*和$@
$*和$@非常相似,都代表命令行所有的参数
但是$*把参数看成是一个整体,例如123 456
而$@把每个参数区分对待,例如[123,456]
注意:在没有循环遍历时候,两者效果一致
$?
$?最后一次执行命令的状态,如果是结果是0,证明上面执行的命令都是正确的
如果结果不是0(具体是哪个数字,有命令自己决定),则证明上面命令不正确了
运算符
简介
Shell中存在expr表达式用于做运算
运算需要使用$((a+b))或者$[a+b]的形式
表达式输出
[root@localhost scripts]# expr $[10+10]
[root@localhost scripts]# expr $((10+5))
表达式赋值给变量
[root@localhost scripts]# a=$(expr 10 + 5)
脚本中的应用
创建add.sh,在执行的时候,通过传递参数的形式实现加法效果
#!/bin/bash
sum=$[$1 + $2]
echo sum=$sum
条件判断
相等判断
基本语法
test 表达式
[ 表达式 ] 注意:中括号前后需要有空格
示例1
注意:=前后需要有空格,否则会认为是一个整体,判断为正确,永远0
[root@localhost ~]# test $a = 10
[root@localhost ~]# echo $?
0
[root@localhost ~]# test $a = 11
[root@localhost ~]# echo $?
1
示例2
注意:记得加空格
[root@localhost ~]# [ $a = 10 ]
[root@localhost ~]# echo $?
0
[root@localhost ~]# [ $a = 11 ]
[root@localhost ~]# echo $?
1
[root@localhost scripts]# [ $a != 11 ]
[root@localhost scripts]# echo $?
0
两个值比较
简介
-eq 等于
-ne 不等于
-lt 小于
-le 小于等于
-gt 大于
-ge 大于等于
示例
[root@localhost ~]# [ $a -lt 11 ]
[root@localhost ~]# echo $?
0
文件权限判断
简介
-r 有读的权限
-w 有写的权限
-x 有执行的权限
示例
[root@localhost scripts]# [ -r hello.sh ]
[root@localhost scripts]# echo $?
0
文件类型判断
简介
-e 文件存在
-f 文件存在并且是一个文件类型
-d 文件存在并且是一个目录类型
示例
[root@localhost scripts]# [ -e hello.sh ]
[root@localhost scripts]# echo $?
0
多条件判断
简介
&& 与的关系,两者都成立
|| 或的关系,两者有一个成立
三元运算符
&& 表示前一个条命令执行成功之后,在执行第二个条件
|| 表示前一个条命令执行失败之后,再执行第二个条件
由此,我们可以衍生出来,类似三元运算符的形式
示例 [ $a -eq $b ] && echo "$a=$b" || echo "$a!=$b"
示例
[root@localhost scripts]# a=10
[root@localhost scripts]# b=10
[root@localhost scripts]# [ $a -eq $b ] && [ $b -gt 5 ]
[root@localhost scripts]# echo $?
0
流程控制
If
命令行
if [ $a = 10 ];then echo 'OK'; fi
if [ $a = 10 ] && [ $b = 11 ];then echo 'ok'; fi
命令文件
#!/bin/bash
if [ $a = 10]
then
echo "ok"
fi
命令文件
#!/bin/bash
if [ $1 -lt 18 ]
then
echo "未成年"
else
echo "成年人"
fi
命令文件
#!/bin/bash
if [ $1 -lt 18 ]
then
echo "未成年"
elif [ $1 -lt 35 ]
then
echo "青年人"
elif [ $1 -lt 60 ]
then
echo "壮年人"
else
echo "老年人"
fi
Case
注意事项
case行结尾必须为单词"in",每一个模式匹配必须以右括号")"结束
双分号 ";;" 表示命令序列结束,相当于跳出当前判断语句
最后 "*)" 表示默认模式结尾,不符合最终的出口
命令文件
#!/bin/bash
case $1 in
1)
echo "值为1"
;;
2)
echo "值为2"
;;
3)
echo "值为3"
;;
*)
echo "其他数字"
;;
esac
For
基本语法1
#!/bin/bash
for (( i=0;i<=100;i++ ))
do
sum=$[$sum+$i]
done
echo $sum
基本语法2
#!/bin/bash
for os in linux windows macos
do
echo $os
done
内部运算符
在shell中,{}是内部运算符,{}表示一个序列,例如,从1写到100: {1..100}
#!/bin/bash
for i in {1..100}
do
sum=$[$sum+$i]
done
echo $sum
$*和$@
$*和$@非常相似,都代表命令行所有的参数,但是$*把参数看成是一个整体,例如123 456。
而$@把每个参数区分对待,例如换行显示 注意:在没有循环遍历时候,两者效果一致
#!/bin/bash
echo '=====$*====='
for param in "$*"
do
echo $param
done
echo '=====$@====='
for param in "$@"
do
echo $param
done
While
示例1
#!/bin/bash
a=1
while [ $a -le $1 ]
do
sum=$[$sum+$a]
a=$[$a+1]
done
echo $sum
示例2
#!/bin/bash
a=1
while [ $a -le $1 ]
do
# sum=$[$sum+$a]
# a=$[$a+1]
let sum+=a
let a++
done
echo $sum
读取控制台输入
基本语法
read 选项 参数
-p:指定读取值时候的提示符
-t:指定读取值时候的等待时间(秒) 如果不添加 -t 表示一直等待
示例
read -t 10 -p "请输入您的名字:" name
echo "welcome,$name"
函数
Shell函数
简介
在Shell中,函数细分为系统函数和自定义函数
系统函数
获取时间戳【date +%s】
注意:这里我们使用的系统命令(或者系统函数)date +%s要进行命令替换,也就是添加$(date +%s)
#!/bin/bash
filename="$1_log_$(date +%s)"
echo $filename
basename
basename [string/pathname][suffix]
basename的作用是获取文件名称,它会删除所有的前缀包括最后一个"/"字符,然后将字符串显示出来
suffix为后缀,如果suffix被指定了,basename将会pathname或string中的suffix去掉
basename /scripts/cmd_test.sh
basename /scripts/cmd_test.sh .sh
basename /abc/def/cmd_test.sh
/abc/def/cmd_test.sh .sh
$0是获取当前名字,但是带有路径,我们只希望获取名字,所以可以通过basename 去掉路径,甚至去掉后缀
#!/bin/bash
echo '=====$n====='
echo script name:$0
#!/bin/bash
echo '=====$n====='
echo script name:$(basename $0 .sh)
dirname
dirname获取文件路径的绝对路径,从给定的包含绝对路径的文件名中去除文件名,然后返回剩余的路径
dirname /scripts/cmd_test.sh
dirname /abc/def/cmd_test.sh
我们可以获取某个文件的绝对路径
#!/bin/bash
echo '=====$n====='
echo script path:$(cd $(dirname $0); pwd)
echo script name:$(basename $0 .sh)
自定义函数
基本语法
function 函数名(){
// 函数体
return 返回值
}
注意
必须在调用函数之前,先声明函数
函数返回值可以通过$?获取,但注意,$?的值范围是0~255
实现一个两个数值相加的函数
#!/bin/bash
function add(){
s=$[$1 + $2]
echo $s
}
read -p "请输入第一个参数:" a
read -p "请输入第二个参数:" b
sum=$(add $a $b)
echo "和:" $sum
echo "和的平方:"$[$sum * $sum]
正则表达式
常规匹配
一串不包含特殊字符的正则表达式匹配它自己
cat /etc/passwd | grep root
特殊字符:^
^匹配一行的开头
cat /etc/passwd | grep ^a
特殊字符:$
$匹配一行的结束
cat /etc/passwd | grep h$
特殊字符:.
.匹配一个任意字符
cat /etc/passwd | grep r.t
cat /etc/passwd | grep r..t
特殊字符:*
* 不单独使用,他和上一个字符连用,表示匹配上一个字符 0 次或多次
cat /etc/passwd | grep ro*t
cat /etc/passwd | grep r.*t
字符区间(中括号):[]
[] 表示匹配某个范围内的一个字符
[6,8]------匹配6或者8
[0-9]------匹配一个0-9 的数字
[0-9]*------匹配任意长度的数字字符串
[a-z]------匹配一个 a-z 之间的字符
[a-z]*-----匹配任意长度的字母字符串
[a-c,e-f]---匹配 a-c 或者 e-f之间的任意字符
cat /etc/passwd | grep r[a,b]t
echo "23fsdfrat23f5y2t23sdf" | grep r[a,b]t
echo "23fsdfrat23f5y2t23sdf" | grep r[ab]t
echo "23fsdfraabat23f5y2t23sdf" | grep r[ab]*t
cat /etc/passwd | grep r[a-z]t
cat /etc/passwd | grep r[a-z]*t
特殊字符:\
\ 表示转义,并不会单独使用。由于所有特殊字符都有其特定匹配模式,
当我们想匹配某一特殊字符本身时(例如,我想找出所有包含"$"的行),
就会碰到困难。此时我们就要将转义字符和特殊字符连用,来表示特殊字符本身
cat /scripts/daily_archive.sh | grep '\$'
匹配手机号
echo "14747696666" | grep ^1[345789][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$
echo "14747696666" | grep -E ^1[345789][0-9]{9}$
文本处理工具
Cut
简介
cut 的工作就是“剪”,具体的说就是在文件中负责剪切数据用的
cut 命令从文件的每行剪切字节、字符和字段并将这些字节、字符和字段输出
基本用法
cut [选项参数] filename
说明:默认分隔符是制表符
-f 列号,提取第几列
-d 分隔符,按照指定分隔符分割列,默认是制表符"\t"
-c 按字符进行切割 后加n表示取第几列 比如 -c 1
cut_test.txt
高 我
级 来
程 学
序 习
员 了
简介
cut -d " " -f 1 cut_test.txt
cut -d " " -f 2,3 cut_test.txt
简介
cat /etc/passwd | grep bash$
cat /etc/passwd | grep bash$ | cut -d ":" -f 1,7
cat /etc/passwd | grep bash$ | cut -d ":" -f 5-
cat /etc/passwd | grep bash$ | cut -d ":" -f -5
cat /etc/passwd | grep bash$ | cut -d ":" -f 5-7
简介
ifconfig # 获取当前的IP
ifconfig ens33 # 获取ens33的信息
ifconfig ens33 | grep netmask # 获取具有IP的一行
ifconfig ens33 | grep netmask | cut -d " " -f 10 # 切分ens33的IP
ifconfig | grep netmask | cut -d " " -f 10 # 切分所有IP
Awk
简介
一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理
基本用法
awk [选项参数] '/pattern1/{action1} /pattern2/{action2}...' filename
pattern:表示awk 在数据中查找的内容,就是匹配模式
action:在找到匹配内容时所执行的一系列命令
-F 指定输入文件分隔符
-v 赋值一个用户定义变量
查看是否存在
which awk # 查看是否存在awk
示例
1. 搜索passwd文件以root关键字开头的所有行,并输出该行的第7列
cat /etc/passwd | grep ^root | cut -d ":" -f 7
cat /etc/passwd | awk -F ":" '/^root/ {print $7}'
2. 搜索passwd文件以root关键字开头的所有行,并输入该行的第1列和第7列,中间以","分割
cat /etc/passwd | awk -F ":" '/^root/ {print $1","$7}'
3. 只显示/etc/passwd 的第一列和第七列,以逗号分割,且在所有行前面添加列名"start"在最后一行添加"over"
cat /etc/passwd | awk -F ":" 'BEGIN{print "start"}{print $1","$7} END{print "over"}'
4. 将passwd文件中的用户id增加数值1并输出
cat /etc/passwd | awk -F ":" '{print $3+1}'
将添加的数值变成变量
cat /etc/passwd | awk -v i=2 -F ":" '{print $3+i}'
awk内置变量
简介
FILENAME 文件名
NR 已读的记录数(行号)
NF 浏览记录的域的个数(切割后,列的个数)
示例
1. 统计passwd文件名,每行的行号,每列的列数
cat /etc/passwd | awk -F ":" '{print "文件名:"FILENAME " 行号:"NR " 列数:" NF }'
awk -F ":" '{print "文件名:"FILENAME " 行号:"NR " .数" NF }' /etc/passwd
2. 查询ifconfig命令输出结果中的空行所在行号
ifconfig | grep -n ^$
ifconfig | awk '/^$/ {print NR}'
ifconfig | awk '/^$/ {print "空行:"NR}'
3. 切割IP
ifconfig | awk '/netmask/ {print $2}'