--[SHELL编程]
Shell是一个命令行解释器,它接收应用程序/用户命令,然后调用操作系统内核.
cat /etc/shells
ls -l /bin/ | grep bash
echo $SHELL
文件名以.sh结尾
脚本以#!/bin/bash开头
less /bin/unix-lpr.sh
#!/bin/sh --开头,默认选择的命令行解释器
#注释
mkdir scripts
cd scripts/
touch hello.sh
vim hello.sh
#!/bin/hash
echo "hello,world"
执行方式:
(1)bash或sh+脚本--不需要赋予可执行权限
bash hello.sh
bash scripts/hello.sh
bash /root/scripts/hello.sh
sh scripts/hello.sh
(2)直接输入脚本的绝对/相对路径--需要具有可执行权限
/root/scripts/hello.sh
scripts/hello.sh
./hello.sh --在当前路径时
chmod +x scripts/hello.sh
(3)在脚本的路径前加"."或者source
source hello.sh
source /root/scripts/hello/sh
. hello.sh --.命令
type source --内置命令
ps -f
bash --进入子shell
ps -f
exit --退出子shell
ps -f
--在子shell设置的变量,父shell是不可见的
[变量]--全局/局部
常用系统变量:
$HOME --当前的主目录
$PWD --当前工作目录
$SHELL --当前Shell解释器
$USER --当前用户
echo $HOME
env | less
printenv | less --打印出系统全局变量
printenv USER
ls $HOME
set --当前定义的局部变量
set | less
用户自定义变量
基本语法:
定义变量:变量名=变量值 --"="号前后不能有空格
a=2
echo $a
echo $my_var --没有声明也不会报错
my_var=hello
echo $my_var
my_var=Hello
echo $my_var
my_var="Hello, world"
my_var='Hello, world'
env | grep my_var
set | grep my_var
bash --进入子shell
ps -f --多了个bash,代表已经进入子shell
echo $my_var
exit --退出子shell
echo $my_var
my_var=hello
export my_var --把局部变量导出变成全局变量
--子shell更改变量时不会影响到父shell的
[hello.sh]
#!/bin/bash
echo "hello,world"
echo $my_var
source hello.sh
./ hello.sh
echo $new_var
new_var="hello,linux"
[hello.sh]
#!/bin/bash
echo "hello,world"
echo $my_var
echo $new_var
./ hello.sh
. hello.sh
source hello.sh
export new_var
./ hello.sh
a=$((1+5)) --运算
a=$[5+9] --运算
readonly b=5 --只读变量
b=10 --报错
set | less
unset a --撤销a变量,只读变量不能撤销
set | less
--在bash里,变量类型默认是字符串类型
ls
hello.sh --无法执行
cd scripts/
hello.sh --未找到命令
cp hello.sh /bin/ --复制到/bin目录下
hello.sh
rm -f /bin/hello.sh
echo $PATH
[特殊变量]
--$n --n为数字,$0代表该脚本名称,$--9代表第一到第九个参数,10以上的参数需要大括号:${10}
echo $1
./ hello.sh xiaoming --$1输出即为xiaoming
vim parameter.sh
[parameter]
#!/bin/bash
echo '==================$n=================='
echo script name:$0
echo 1st paramater:$1
echo 2nd paramater:$2
chmod +x parameter.sh
./parameter.sh abc def
--$# --获取所有输入参数个数,常用于循环,判断参数的个数是否正确
vim parameter
[parameter]
#!/bin/bash
echo '==================$n=================='
echo script name:$0
echo 1st paramater:$1
echo 2nd paramater:$2
echo '==================$#=================='
echo parameter numbers:$#
./ parameter.sh
./ parameter.sh abc def
--$* $@
$* --获取当前命令行提供的所有参数(把所有参数看成一个整体)
$@ --获取当前命令行提供的所有参数(所有参数构成的一个集合),可以for循环遍历
[parameter]
#!/bin/bash
echo '==================$n=================='
echo script name:$0
echo 1st paramater:$1
echo 2nd paramater:$2
echo '==================$#=================='
echo parameter numbers:$#
echo '==================$*=================='
echo $*
echo '==================$@=================='
echo $@
./ parameter.sh abc def
--$? --最后一次执行命令的返回状态(0表示正常执行/其他则错误)
echo $?
[运算符]
基本语法:
$((运算符))
$[运算符]
expr 1 + 2 --要空格
expr 5 - 2
expr 5 * 2 --报错
expr 5 \* 2
echo $((5*2))
echo $[5*2]
a=$[6+8]
echo $a
a=$(expr 5 \* 2) --命令替换
echo $a
a=`expr 5 \* 2`
echo $a
s=$[(2+3)*4]
echo $s
cd scripts/
vim add.sh
[add.sh]
#!/bin/bash
sum=$[$1+$2]
echo sum=$sum
chmod +x add.sh
./ add.sh 25 89
[条件判断]
基本语法:
(1)test condition
(2)[condition] --condition前后要有空格
a=hello
echo $a
test $a = hello
echo $? --0,为真
test $a = Hello
echo $? --1,为假
[ $a = hello]
[ $a != hello]
echo $?
[ abc ]
echo $?
[ ]
echo $?
[ 2 = 8 ]
echo $?
[ 2 = 2 ]
echo $?
[ 2 < 8 ]
echo $?
> --输出,而不是大于
< --输入,而不是小于
常用判断条件:
(1)两个整数之间比较
-eq --equal
-ne --not equal
-lt --less than
-le --less qual
-gt --greater than
-ge --greater equal
(2)按照文件权限进行判断
-r --read
-w --write
-x --execute
(3)按照文件的类型进行判断
-e --文件存在(existence)
-f --文件存在并使一个常规的文件(file)
-d --文件存在并使一个目录(directory)
[ 2 -lt 8]
echo $?
touch test
[ -r hello.sh ]
echo $?
[ -w hello.sh ]
echo $?
[ -x test ]
echo $?
[ -e /home/atguigu/info ]
echo $?
[ -f /test]
echo $?
[ -d /etc ]
echo $?
多条件判断:
&& --前一条命令执行成功时,才执行一条命令
|| --上一条命令执行失败后,才执行下一条命令
a=15
[ $a -lt 20 ] && echo "$a<20" || echo "$a>=20"
a=27
[ $a -lt 20 ] && echo "$a<20" || echo "$a>=20"
[ atguigu ] && echo ok || echo notok
[ ] && echo ok || echo notok
[流程控制] --顺序/分支
if 判断
基本语法:
--(1)单分支
if [ 条件判断式 ];then
程序
fi
if [ 条件判断式 ]
then
程序
fi
cd /home/atguigu/;ls -l
a=25
if [ $a -gt 18 ];then echo OK;fi
cd scripts/
vim if_test.sh
[if_test.sh]
#!/bin/bash
if [ "$1"x = "atguigu"x ] --添加x,避免空值时报错
then
echo "welcome,atguigu"
fi
chmod +x if_test.sh
./if_test.sh
./if_test.sh xiaoming
./if_test.sh atguigu
a=25
if [ $a -gt 18 ] && [ $a -lt 35 ];then echo OK;fi
a=36
if [ $a -gt 18 -a $a -lt 35 ];then echo OK;fi --其中"-a"表示and/"-o"表示or
--(2)多分支
if [ 条件判断式 ];then
程序
elif [ 条件判断式 ];then
程序
else
程序
fi
vim if_test.sh
[if_test.sh]
#!/bin/bash
if [ "$1"x = "atguigu"x ]
then
echo "welcome,atguigu"
fi
# 输入第二个参数,表示年龄,判断属于哪个年龄段
if [$2 -lt 18 ]
then
echo "未成年人"
else
echo "成年人"
fi
./if_test.sh atguigu 15
./if_test.sh atguigu 25
vim if_test.sh
[if_test.sh]
#!/bin/bash
if [ "$1"x = "atguigu"x ]
then
echo "welcome,atguigu"
fi
# 输入第二个参数,表示年龄,判断属于哪个年龄段
if [ $2 -lt 18 ]
then
echo "未成年人"
elif [ $2 -lt 35 ]
then
echo "青年人"
elif [ $2 -lt 60 ]
then
echo "中年人"
else
echo "老年人"
fi
./if_test.sh atguigu 35
--case语句
基本语法:
case $变量名 in
"值1")
如果变量的值等于值1,则执行程序1
;; --相当于java中的break
"值2")
如果变量的值等于值2,则执行程序2
;;
...省略其他分支...
*) --相当于java中的default
如果变量的值都不是以上的值,则执行此程序
;;
esac
cd scripts/
vim case_test.sh
[case_test.sh]
#!/bin/bash
case $1 in
1)
echo "one"
;;
2)
echo "two"
;;
3)
echo "three"
;;
*)
echo "number else"
;;
esac
chmod +x case_test.sh
./case_test.sh 2
./case_test.sh 6
--(3)循环
--for循环
基本语法1:
for ((初始值;循环控制条件;变量变化))
do
程序
done
vim sum_to.sh
[sum_to.sh]
#!/bin/bash
for (( i=1; i<=$1;i++ )) --双小括号内可以用"<" ">" 运算符
do
sum=$[ $sum+$i ]
done
echo $sum
chmod +x sum_to.sh
./sum_to.sh 10
./sum_to.sh 100
a=1
if (( $a>2 ));then echo OK;else echo notOK;fi
基本语法2:
for 变量 in 值1 值2 值3 ...
do
程序
done
for os in linux windows macos ;do echo $os ; done
{} --表示序列
{ 1..100 } --1到100的序列
for i in {1..100};do sum=$[$sum+$i] $i;done;echo $sum --增强for循环
[比较$*和$@]
vim parameter_for_test.sh
[parameter_for_test.sh]
#!/bin/bash
echo '===============$*================'
for para in $*
do
echo $para
done
echo '===============$@================'
for para in $@
do
echo $para
done
chmod +x parameter_for_test.sh
./parameter_for_test.sh a b c d --不加""没区别
[parameter_for_test.sh]
#!/bin/bash
echo '===============$*================'
for para in "$*"
do
echo $para
done
echo '===============$@================'
for para in "$@"
do
echo $para
done
chmod +x parameter_for_test.sh
./parameter_for_test.sh a b c d --$@会依次显示,而$*会把"a b c d"当成整体输出
--while循环
基本语法:
while [ 条件判断式 ]
do
程序
done
vim sum_to.sh
[sum_to.sh ]
#!/bin/bash
# 用for进行实现
for (( i=1; i<=$1;i++ ))
do
sum=$[ $sum+$i ]
done
echo $sum
# 用while做一个实现
a=1
while [ $a -le $1 ]
do
# sum2=$[ $sum2+$a ]
# a=$[ $a+1 ]
let sum2+=a --不能有空格(let内置命令可以直接使用其他语言的写法)
let a++
done
echo $sum2
./sum_to.sh 100
--read读取控制台输入
基本语法:
read (选项) (参数)
选项:
-p --指定读取值时的提示符:
-t --指定读取值时等待的时间(秒)如果-t不加表示一直等待
参数:
变量:指定读取值的变量名
vim read_test.sh
[read_test.sh]
#!/bin/bash
read -t 10 -p "请输入您的名字:" name
echo "welcome,$name"
chmod +x read_test.sh
./read_test.sh
[函数] --方法;一段代码的集合;模块化
--系统函数
--basename
基本语法:
basename [string/pathname] [suffix] --basename命令会删掉所有的前缀包括最后一个('/')字符,然后将字符串显示出来.
--basename可以理解为取路径里的文件名称
选项:
suffix为后缀,如果suffix被指定了,basename会将pathname或string中的suffix去掉.
date
date +%s --时间戳
vim cmd_test.sh
[cmd_test.sh]
#!/bin/bash
filename="$1"_log_$(date +%s)
echo $filename
chmod +x cmd_test.sh
./cmd_test.sh atguigu --定义日志文件时可以用到
basename /root/scripts/parameter.sh --直接去掉前面的路径
basename /root/scripts/parameter.sh .sh --把后缀也去掉
vim parameter.sh
[parameter.sh]
#!/bin/bash
#!/bin/bash
echo '=========$n=========='
echo script name:$( basename $0 .sh )
echo 1st parameter:$1
echo 2nd parameter:$2
echo '=========$#=========='
echo parameter number:$#
echo '=========$*=========='
echo $*
echo '=========$@=========='
echo $@
./parameter.sh a b
--dirname
基本语法:
dirname 文件绝对路径 --从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下路径(目录的部分)
--dirname可以理解为取文件路径的绝对路径名称
dirname /root/scripts/parameter.sh
dirname ../scripts/parameter.sh
vim parameter.sh
[parameter.sh]
#!/bin/bash
echo '=========$n=========='
echo script name:$( basename $0 .sh )
cd $( dirname $0 )
echo script path:$( cd $( dirname $0 ); pwd)
echo 1st parameter:$1
echo 2nd parameter:$2
echo '=========$#=========='
echo parameter number:$#
echo '=========$*=========='
echo $*
echo '=========$@=========='
echo $@
./parameter.sh
--命令替换
$( 命令 )
--自定义函数
基本语法:
[function] funname[()]
{
Action
[return int;]
}
经验技巧:
(1)必须在调用函数之前先声明函数,shell脚本是逐行运行。
(2)函数返回值,只能通过$?系统变量获得,可以显示加:return 返回,如果不加,以最后一条命令运行结果作为返回值.return后跟数值n(0-255).
=========$@==========
[root@hadoop100 scripts]# vim fun_test.sh
vim fun_test.sh
[fun_test.sh]
#!/bin/bash
function add(){
s=$[ $1 + $2 ]
echo "和:" $s
}
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b
add $a $b
chmod +x fun_test.sh
vim fun_test.sh
[fun_test.sh]
#!/bin/bash
function add(){
s=$[ $1 + $2 ]
return $s
}
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b
add $a $b
echo "和:"$?
vim fun_test.sh
[fun_test.sh]
#!/bin/bash
function add(){
s=$[ $1 + $2 ]
echo $s
}
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b
sum=$(add $a $b)
echo "和:" $sum
echo "和的平方:"$[ $sum * $sum ]
./fun_test.sh
--归档文件案例
需求:实现一个每天对指定目录归档备份的脚本,输入一个目录名称(末尾不带/),将
目录下所有文件按天归档保存,并将归档日期附加在归档文件上,放在/root/archive下.
用到命令:tar
后面加上-c选项表示归档,加上-z选项表示同时进行压缩,得到的文件后缀名为.tar.gz.
vim daily_archive.sh
[daily_archive.sh]
#!/bin/bash
# 首先判断输入参数个数是否为1
if [ $# -ne 1 ]
then
echo "参数个数错误!应该输入一个参数,作为归档目录名"
exit
fi
# 从参数中获取目录名称
if [ -d $1 ]
then
echo
else
echo
echo "目录不存在!"
echo
exit
fi
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1);pwd)
# 获取当前日期
DATE=$(date +%y%m%d)
# 定义生成的归档文件名称
FILE=archive_${DIR_NAME}_$DATE.tar.gz
DEST=/root/archive/$FILE
# 开始归档目录文件
echo "开始归档..."
echo
tar -czf $DEST $DIR_PATH/$DIR_NAME
if [ $? -eq 0 ]
then
echo
echo "归档成功!"
echo "归档文件为:$DEST"
else
echo "归档出现问题!"
fi
exit
chmod u+x daily_archive.sh
mkdir /root/archive
./daily_archive.sh a b
./daily_archive.sh ../scripts
crontab -l --查看定时任务
crontab -e --编辑
0 2 * * * /root/scripts/daily_archive.sh /root/scripts --设置定时归档
--正则表达式入门
Linux中grep,sed,awk等文本处理工具都支持通过正则表达式进行模式匹配
cat /etc/passwd --获取所有用户信息
cat /etc/passwd | grep atguigu --常规匹配
常用特殊字符:
^ --匹配一行的开头
cat /etc/passwd | grep ^a --匹配以"a"开头的用户
$ --匹配一行的结束
cat /etc/passwd | grep bash$ --匹配以"bash"结尾的所有用户
cat daily_archive.sh | grep -n ^$ --"-n"显示行号
. --匹配一个任意的字符
cat /etc/passwd | grep r..t
* --不单独使用,它和上一个字符连用,表示匹配上一个字符0次或多次
cat /etc/passwd | grep ro*t
cat /etc/passwd | grep .*
cat /etc/passwd | grep ^a.*bash$ --指定以a开头,以bash结尾
cat /etc/passwd | grep ^a.*var.*bash$
[] --表示匹配某个范围内的一个字符
[6,8]--匹配6或者8
[0-9]--匹配0-9的数字
[0-9]*--匹配任意长度的数字串
[a-z]--匹配任意长度的字母字符串
[a-c,e-f]--匹配a-c或者e-f之间的任意字符
cat /etc/passwd | grep r[a,b]t
echo "rbtadfasf" | grep r[a,b]t
echo "23rbtadfasf" | grep r[a,b]t
"23rbtadfasf" | grep r[ab]t --可以不用,分割
echo "23rbtadfasf" | grep r[a-z]*t
\ --表示转义
cat daily_archive.sh | grep '\$' --搜索$符号
cat daily_archive.sh | grep '/\$' --匹配/$符号
a{2} --a出现两次
+ --一次或多次
? --0次或1次,不会多次
--匹配手机号
echo "13812345678" | grep ^1[34578][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$
echo "13812345678" | grep -E ^1[34578][0-9]{9}$ --grep加"-E"支持"{n}"的扩展写法
--[文本处理工具]
--cut
[cut 数据剪切]
基本语法:
cut [选项参数] filename
说明:默认分隔符是制表符
选项:
-f --列号,提取第几列
-d --分隔符,按照指定分隔符分割列,默认是制表符"\t"
-c --按字符进行切割,后加加n表示去第几列,比如 -c 1
vim cut.txt
[cut.txt]
dong shen
guan zhen
wo wo
lai lai
le le
cat cut.txt
cut -d " " -f 1 cut.txt --截取第一列
cut -d " " -f 2,3 cut.txt --截取第二三列
cat /ect/passwd | grep bash$
cat /etc/passwd | grep bash$ | cut -d ":" -f 1,6,7 --截取1,6,7列
cat /etc/passwd | grep bash$ | cut -d ":" -f 1,6- --“6-”截取到最后
cat /etc/passwd | grep bash$ | cut -d ":" -f -4 --截取到第4列
echo $PATH
echo $PATH | cut -d ":" -f 2-
ifconfig
ifconfig ens33
ifconfig ens33 | grep netmask
ifconfig ens33 | grep netmask | cut -d " " -f 10 --截取IP地址
--awk
{awk 文本分析工具,把文件逐行读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理}
基本语法:
awk [选项参数] '/patternl/{action1} /pattern2/{action2}...' filename
pattern --表示awk在数据中查找的内容,就是匹配模式
action --在找到匹配内容时所执行的一系列命令
选项参数:
-F --指定输入文件分隔符,默认是空格
-v --赋值一个用户定义变量
awk的内置变量:
FILENAME --文件名
NR --已读的记录数(行号)
NF --浏览记录的域的个数(切割后,列的个数)
which awk
ll /usr/bin |grep awk
cat /etc/passwd | grep ^root | cut -d ":" -f 7
cat /etc/passwd | awk -F ":" '/^root/ {print $7}'
cat /etc/passwd | awk -F ":" '/^root/ {print $1","$6","$7}'
BEGIN --在所有数据读取行之前执行
END --在所有数据执行之后执行
cat /etc/passwd | awk -F ":" 'BEGIN{print "user,shell"}{print$1","$7}END{print "end of file"}'
cat /etc/passwd | awk -v i=1 -F ":" ' {print $3+i}'
cat /etc/passwd | awk -F ":" '{print "文件名:"FILENAME " 行号: "NR " 列数: "NF}'
awk -F ":" '{print "文件名:"FILENAME " 行号: "NR " 列数: "NF}' /etc/passwd
ifconfig | grep -n ^$ --“-n”显示行号
ifconfig | awk '/^$/ {print NR}'
ifconfig | awk '/^$/ {print "空号:" NR}'
ifconfig ens33 | grep netmask
ifconfig ens33 | grep netmask | cut -d " " -f 10
ifconfig | grep netmask | cut -d " " -f 10
ifconfig | awk '/netmask/ {print $2}'
--[综合应用案例]
--发送消息
利用Linux自带的mesg和write工具,向其他用户发送消息.
需求:实现一个向某个用户快速发送消息的脚本,输入用户名作为第一个参数,后面直
接跟要发送的消息.脚本需要检测用户是否登录在系统中、是否打开消息功能,以及当前发
送消息是否为空.
who am i
who --查看当前所登录的所有用户
mesg --检查消息功能是否打开,"y"表示打开
who -T--检查消息功能是否打开,"+"表示打开,并且可以查看对于的控制台
mesg n--关闭消息功能
mesg y--打开消息功能
write atguigu pts/1 --给atguigu用户发送消息 pts为对应的控制台
ctrl+c --退出;退出后对方会显示"EOF",表示发送消息已经结束
vim send_msg.sh
[send_msg.sh]
#!/bin/bash
# 查看用户是否登录
login_user=$(who | grep -i -m 1 $1 | awk '{print $1}' ) -- -i忽略大小写;-m匹配多少行
if [ -z $login_user ] -- -z判断是否为空
then
echo "$1 不在线!"
echo "脚本退出.."
exit
fi
# 查看用户是否开启消息功能
is_allowed=$( who -T | grep -i -m 1 $1 | awk '{print $2}')
if [ $is_allowed != "+" ]
then
echo "$1 没有开启消息功能"
echo "脚本退出.."
exit
fi
# 确认是否有消息发送
if [ -z $2 ]
then
echo "没有消息发送"
echo "脚本退出.."
exit
fi
# 从参数中获取要发送的消息
whole_msg=$( echo $* | cut -d " " -f 2- )
# 获取用户登录的终端
user_terminal=$( who | grep -i -m 1 $1 | awk '{print $2}' )
# 写入要发送的消息
echo $whole_msg | write $login_user $user_terminal
if [ $? != 0 ]
then
echo "发送失败!"
else
echo "发送成功!"
fi
exit
chmod u+x send_msg.sh
./send_msg.sh atguigu hi,atguigu
./send_msg.sh atguigu boss is coming
Linux扩展(Shell)
于 2024-04-12 08:12:21 首次发布