文章目录
0. Shell学习笔记
shell
文件默认以.sh
后缀结尾(仅约定但不影响文件类型)
#!/bin/bash
文件首行指定解析器#
表示注释
1. Shell文件执行
- 手动执行
bash demo.sh # 相对路径或绝对路径 sh demo.sh # 部分linux系统中,sh命令是一个指向bash的软连接
手动执行shell文件,不要求文件具有可执行权限
- 自动执行
# 为demo.sh文件新增可执行权限 chmod +x demo.sh ./demo.sh # 直接执行,相对路径(只能.)或绝对路径
- 附加内容:
source demo.sh
可以使用source
命令执行脚本. demo.sh
使用.
命令,与上者等价
该执行操作的区别就是,使用“源”命令执行脚本时,是直接在当前的bash会话环境中去执行脚本的内容。
而其他的执行方式是,在当前的bash会话中,新起一个 子bash 环境去执行对应的脚本,执行环境与当前会话是隔离的,可以直接执行bash
打开新会话,exit
退出子会话。
如部分场景下当前配置的用户配置修改不生效,则可以使用:
source .profile
刷新当前环境的配置信息,即是在当前环境下直接修改环境配置
2. 变量
1. 系统预定义变量
- 常用系统变量
$HOME
、$PWD
、$SHELL
、$USER
、PATH
等
- 变量分全局变量和局部变量,顾名思义
- 输出环境变量
env
、printenv
可以打印出当前的全局变量echo $HOME
、printenv HOME
可以直接输出对应环境变量set
输出当前会话的所有变量,包含全局变量和局部变量
2. 自定义变量
- 基本语法
name=value
,=
等于号两边不能有空格unset name
,撤销变量readonly name=value
,声明一个静态只读变量,而且不能unset
my_var=Hello my_var="Hello, world" my_var='Hello, world' # 以上全部都是局部变量,父shell里面不能访问 echo $my_var # 打印看看 export my_var # 导出my_var,my_var被提升为全局变量,父shell可访问,且再修改的值不会影响全局变量 my_var='Hello, Linux' export my_var # 父shell依然是Hello, world ## 注意当前shell定义的局部变量,子shell也不可用访问,如: echo "echo $local_var" > demo.sh ./demo.sh ## 打印为空,因为会新起子shell,但无法访问父shell的局部变量
定义变量时,不支持表达式直接运算,需要使用命令表达式(var=$((1+2))
、var=$[1+2]
)
环境变量$PATH
:
> echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
3. 特殊变量
$0
在shell脚本中,表示脚本执行的第一个参数,一般为执行脚本的名称$1
在shell脚本中,表示脚本执行的第二个参数,为执行时的输入参数,以此类推$n
#!/bin/bash ## file demo.sh echo "==$n==" # "双引号内的$会被解析(变量名) echo '==$n==' # '单引号会被作为纯字面量值使用
$#
获取所有输入参数个数$*
获取命令行中所有(除$0
外)的参数(把所有参数作为一个整体值)$@
获取所有(除$0
外)的参数(作为一个集合,可以遍历参数)$?
表示上一次执行命令的返回状态,可用于判断命令的执行状态
3. 运算符
-
expr
可以直接支持运算> expr 1 + 2 # 注意使用空格,表示每一项参数(特殊字符如*需要转义\*表示乘号) > a=$(expr 5 \* 2) > echo $a # a=10 ## 可以使用反引号`替换计算结果值 > a=`expr 5 \* 2` > echo $a
-
$((运算表达式))
、$[运算表达式]
可以做表达式运算,并且看可以直接接受返回值> $((2*5)) > 10 > $((1+2)) > a=$[3 * 4] > echo $a
#!/bin/bash ## file add.sh sum=$[$1+$2] echo sum=$sum
> ./add.sh 10 20 sum=30
-
命令替换:使用反引号
``
包裹命令,可以直接执行并替换结果
4. 条件判断
-
基本语法
test condition_expression
,是一种断言型测试[ condition_expression ]
,简化语法,表达式前后必须要有空格
注意表达式各项需要有空格隔开
> a=hello > test $a = hello > echo $? # echo 0 判断成功 > [ $a = Hello ] > echo $? # echo 1 判断失败 > [ $a=123 ] > echo $? # echo 0 判断成功,因为"$a=123"被作为一个整体判断为真 > [ ] # 空表达式 > echo $? # echo 1 判断失败 > [ -z $zeroval ] # 判断变量是否为空
-
比较运算符
可以进行普通的=
和!=
,但是对于整数的比较大小的操作,不能使用<
等操作符,因为已经被占用为重定向操作整数比较操作符 作用 -eq
等于 -neq
不等于 -lt
小于 -gt
大于 -le
小于等于 -ge
大于等于 [ 1 -lt 2 ] # 1 < 2
对于字符串的比较,
=
表示相等,!=
表示不相等对文件进行判断:
文件判断操作符 作用 -r
文件可读权限 -w
文件可写权限 -x
文件可执行权限 -e
目标文件存在 -f
目标文件为文件类型 -d
目标文件为目录类型 [ -r demo.sh ] # 当前目录的demo.sh文件可读
-
逻辑与或(短路运算)
> a=10 > [ $a -eq 10 ] && echo ture || echo false true
5. 流程控制
1. if
判断
- 单分支
if [ $condition ];then # ;分号表示同一行内多个串行的命令 $dosomething fi if [ $condition ] then $dosomething fi
#!/bin/bash # demo.sh if [ $1 = world ];then # 如果参数为空,$1为空则会判断报错,需要一个一元运算符 echo hello, world fi # 健壮增强 if [ "$1" = "world" ];then # 使用字符串占位表达式,$1为空时结果为""空字符串,而不是空 echo hello, world fi
- 多分支
if [ $condition ];then $dosomething elif [ $condition ];then $other else $default fi
- 多条件判断
使用短路运算符&&
、||
连接多个判断语句if [ 20 -gt 18 ] && [ 30 -lt 35 ] then echo between 18 and 35 is ok fi
2. case
语句
var="var"
case $var in
"var1")
echo var=var1
;; ## 两个分号;类似break
"var2")
echo var=var2
;;
*) # 通配,类似default
echo var is unknown
;;
esac
3. for
循环
-
基本循环
for ((初始值;循环控制条件;循环递进)) do $todo done
示例:
#!/bin/bash # file calculation.sh for (( i=1;i <= $1; i++ )) ## 双小括号用法,里面可以按普通表达式判断和运算 do sum=$[$sum + $i] done echo $sum
-
in
循环快速遍历集合:
for v in v1 v2 ... do echo v done
for n in {1..100} do echo n # 输出1~100 done
回顾
$*
和$@
:#!/bin/bash echo '==========$*==========' for p in $* ## 此处$*作为一个参数列表整体替换到in后 do echo $p done echo '==========$@==========' for p in $@ ## 此处$@作为一个参数集合用在in后 do echo $p done
#!/bin/bash echo '==========$*==========' for p in "$*" ## 注意字符串解析表达式作用,in后面被作为单个字符串值整体使用 do echo $p done echo '==========$@==========' for p in "$@" ## 此处出现差别,虽然是解析表达式,但in后仍是一个参数的集合 do echo $p done
4. while
循环
while [ condition ]
do
$doloop
done
示例:
#!/bin/bash
# file calculation.sh
n=1
while [ $n -lt $1 ]
do
sum=$[ $sum + $n ]
let sum+=n
n=$[$n+1]
let n++
done
echo $sum
n=1
while [ $n -lt $1 ]
do
## 新语法
let sum+=n
let n++
done
echo $sum
6. 控制台输入
read (选项) (参数)
命令
-
选项:
-p
:指定读取值时的提示符-t
:指定读取值时等待的超时时间(秒),如果没有该选项则一直等待
-
参数:
- 变量:指定读取值后的变量名
#!/bin/bash read -t 10 -p "请输入参数:" var echo "您输入的为:$var"
7. 函数
shell
函数是一系列指令的集合,可以看作是一个工具,脚本也有类似的作用,类似重量化的函数
1. 系统函数
系统函数是 shell
已经提供的一些函数和工具
basename
basename [string / pathname] [suffix]
basename
或按路径分割获取参数最后一个末尾的字符串,suffix
后缀则是指定了路径后缀,获取时会自动去掉后缀
#! /bin/bash #filename demo.sh logfile="$1"_log_$(date +%s) # 使用$()表达式作命令替换(结果) echo 生成文件为:$logfile # 以时间戳为后缀的文件名 echo 脚本名称为:$(basename $0 .sh) echo 第一个参数:$1 echo 参数数量为:$#
- 获取时间信息:
> date # 默认格式日期 > date +%s # 返回当前时间戳 > date "+%Y-%m-%d %H:%M:%S" # 1970-01-01 00:00:00
dirname
dirname path
从给定的路径中,获取其所在的目录(父目录),即按路径分割截去末尾的字符串,所表示的目录路径。
> dirname ../a/b/c ../a/b > dirname /a/b/c/a.txt /a/b/c > cd /a/b > echo $(cd $(dirname c/a.txt);pwd) # 输出了目标绝对路径,但cd命令并不会干扰执行环境 /a/b/c
2. 自定义函数
[function] funcname[()] # []表示可选
{
args_1=$1; # 使用$n表示函数的参数,不需要声明参数列表
args_2=$2;
echo "todo something and return result";
[return byteint;] # 可选,表示函数执行结果状态码0~255
}
在调用函数之前,必须先声明函数定义
#! /bin/bash
# filename add.sh
function add()
{
sum=$[$1 + $2];
echo "两数之和:"$sum;
}
read -p "输入第一个数:" a
read -p "输入第二个数:" b
add $a $b
echo 加法运算状态:$? # 函数默认 return 0
function add()
{
sum=$[$1 + $2];
echo $sum;
}
sum=$(add $a $b) # 截获函数的输出,不会打印到控制台
echo 和的平方:$(($sum*$sum))
8. 正则表达式
- 使用
grep
命令> echo demo.txt | grep key # 匹配所有包含key的行 > echo demo.txt | grep ^key # 匹配所有以key为开头的行 > echo demo.txt | grep key$ # 匹配所有以key为结尾的行 > echo demo.txt | grep -n ^$ # 匹配所有空行,-n显示对应行号 > echo demo.txt | grep key. # .匹配任意一个字符 > echo demo.txt | grep a* # 匹配a字符任意多次(包含0次) > echo demo.txt | grep a[1,3,5] # []表示或区间,","逗号可以省略。匹配a1、a3或a5 > echo demo.txt | grep a[bcd]e > echo demo.txt | grep a[0-9a-z]e # 与其他正则匹配类似,可以组合不同的匹配方式,灵活处理 > echo demo.txt | grep ^ab?c+.*z$ # 匹配以 1个a + 0或1个b + 至少1个c 开头、z结尾的行 > echo demo.txt | grep '\$' # 匹配$字符,需要转义且使用单引号 > echo 13512349876 | grep -E ^1[35679][0-9]{9}$ # 匹配手机号,需要使用-E选项开启扩展正则表达式{}
9. 文本处理工具
-
cut [选项] filename
- 负责对数据进行裁剪、剪切使用。
选项参数 功能 -f
指定剪切第几列,可以类似正则表达式的或区间 []
内的用法-d
指定分隔符,按给定分隔符分列,默认 \t
,只能单字符-c
不按分割符操作,直接指定剪切的字符范围数字 # in file cut.txt: # a--b-c # 1--2-3 # A--B-C > cut -d " " -f 1,3 cut.txt a-b 1-2 A-B > cut -d " " -f 2- cut.txt -b-c -2-3 -B-C
-
awk
、gawk
awk [选项参数] '/pattern/{action} ...' filename
- pattern:表示
awk
在数据中查找的内容匹配 - action:在匹配到内容后,执行的一些操作
- pattern:表示
选项参数 功能 -F
指定输入的分隔符,默认空格 -v
自定义用户变量,可以用于action运算 > cat /etc/passwd | awk -F ":" '/^root/ {print $7}' /bin/bash # root用户指定的shell终端命令 > cat /etc/passwd | grep ^root | cut -d ":" -f 7 /bin/bash # 等效用法 > cat /etc/passwd | awk -F ":" '/^root/ {print $1" "$7}' root /bin/bash > cat /etc/passwd | awk -F ":" 'BEGIN{print "USER SHELL"}print{$1" "$7}END{print "END OF FILE"}' USER SHELL root /bin/bash ... ... END OF FILE # awk 复杂运算 > cat /etc/passwd | awk -F ":" '{print $3}' # 输出用户uid > cat /etc/passwd | awk -F ":" '{print $3+10000}' # 输出用户10uid > cat /etc/passwd | awk -F ":" -v i=10000 '{print $3+i}' # 输出用户10uid
BEGIN
、END
为内部模式,表示程序处理的开启和结束- 使用
-v
可以自定义变量灵活使用 awk
还有一些内部变量:
选项参数 功能 FILENAME
处理的文件名 NR
当前已读的行数(行号) NF
切割后列的个数 > awk '/^$/{print NR}' cut.txt # 输出文件空行的行号
10. 完结撒花(小练习)
Linux自带 mesg
和 write
命令,可用于给其他用户发送消息。
mesg [y/n]
查看当前是否开启了消息功能:is y/n
。传入参数可以控制开启、关闭- 使用
who -T
选项也可以查看用户是否开启了消息功能:[+/-] pts/0
#! /bin/bash
# filename notice.sh
# 查看指定用户,-i忽略大小写,-m指定最大匹配次数(只匹配1次)
login_user=$(who | grep -i -m 1 $1 | awk '{print $1}') # 模糊查找目标用户
if [ -z $login_user ]; 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
# 从第二个参数开始,获取所有的消息发送列表
msg_list=$(echo $* | cut -d ' ' -f 2-)
# 目标用户的第一个终端
user_terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
#向目标写入消息
echo $msg_list | write $login_user $user_terminal
if [ $? != 0 ];then
echo 发送失败!
else
echo 发送成功!
fi
> notice.sh user1 message1 message2