Shell是一个命令行解释器,它接收应用程序/用户命令,然后调用操作系统内核。
写一个hello.sh脚本:
1.mkdir scripts
2.cd scripts
3.touch hello.sh
4.vim hello.sh
bash
#!/bin/bash
echo "hello,world"
5.bash hello.sh(另外启用一个bash进程)
5.chmod +x scripts/hello.sh scripts/hello.sh(必须要用相对或绝对路径,不能进入scripts文件夹再执行) ./hello.sh(最常用)
5.(前两种都启用子shell进程)source hello.sh . hello.sh(不启用子shell进程,没有父子shell嵌套关系)
用ps -f可以查看bash进程数量,用exit可以退出子bash进程。
开子shell与不开子shell的区别就在于,环境变量的继承关系,如在子shell中设置的当前变量,父shell是不可见的。
|-------------|------------|
| echo $HOME | 查看系统变量 |
| env | less | 查看所有系统全局变量 |
| set | less | 查看定义的所有变量 |
自定义变量
在子shell更改变量,不影响父shell。
全局环境变量名字建议大写。
a=2(等号两边不能加空格)
echo $a
s="hello, world"(变量的值有空格,需要使用双引号括起来。)
echo $s
export s(升级成全局变量)
a=((1+5))(可以进行符号运算) a=1+5
echo $a
readonly b=5(定义一个只读变量)
b=10(报错,只读)
unset a(删除变量a)
unset b(删除不了,只读)
特殊变量
1.n (功能描述:n为数字,0代表该脚本名称,1-9代表第一到第九个参数,十以上的参数需要用大括号包含,如${10})
vim hello.sh
bash
#!/bin/bash
echo "hello, world"
echo "hello, $1"
./hello.sh xiaoming(将xiaoming填入$1)
vim parameter.sh
单引号就不会把里面的$认为是变量,而是原封不动输出。
bash
#!/bin/bash
echo '----------$n-----------'
echo script name:$0
echo 1st parameter:$1
echo 2nd parameter:$2
chmod +x parameter.sh
./parameter.sh abc def
2.$#(功能描述:获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性)。
vim parameter.sh
bash
#!/bin/bash
echo '----------$n-----------'
echo script name:$0
echo 1st parameter:$1
echo 2nd parameter:$2
echo '----------$#-----------'
echo parameter numbers:$#
./parameter.sh abc def
3.\*(功能描述:这个变量代表命令行中所有的参数,*把所有的参数看成一个整体)
@(功能描述:这个变量也代表命令行中所有的参数,不过@把每个参数区分对待)
vim parameter.sh
bash
#!/bin/bash
echo '----------$n-----------'
echo script name:$0
echo 1st parameter:$1
echo 2nd parameter:$2
echo '----------$#-----------'
echo parameter numbers:$#
echo '----------$*-----------'
echo $*
echo '----------$@-----------'
echo $@
./parameter.sh abc def
4.$?(功能描述:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行:如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。)
echo $?(接上面代码,结果为0)
parameter.sh(报错)
echo $?(非0)
运算符
echo $5 \* 2
s=$(2+3) \* 4
echo $s
cd /scripts
vim add.sh
bash
#!/bin/bash
sum=$[$1 + $2]
echo sum=$sum
chmod +x add.sh
./add.sh 25 25
条件判断
condition (注意condition前后要有空格)
注意:条件非空即为 true, happygame 返回true, 返回 false。
$a = Hello (等号两边需要空格,否则识别成一个值)
echo $?(如果a是Hello则结果为0)
$a != Hello
echo $?(a是Hello则结果为1)
|-----|---------------------|
| -eq | 等于(equal) |
| -ne | 不等于(not equal) |
| -lt | 小于(less than) |
| -le | 小于等于(less equal) |
| -gt | 大于(greater than) |
| -ge | 大于等于(greater equal) |
[两个整数之间比较]
2 -lt 8
echo $?(结果为0)
2 -gt 8
echo $?(结果为1)
|----|-----------------|
| -r | 有读的权限(read) |
| -w | 有写的权限(write) |
| -x | 有执行的权限(execute) |
[按照文件权限进行判断]
touch test
-r test
echo $?
-w test
echo $?
-x test
echo $?
|----|------------------------|
| -e | 文件存在(existence) |
| -f | 文件存在并且是一个常规的文件(file) |
| -d | 文件存在并且是一个目录(directory) |
[按照文件类型进行判断]
-e test
echo $?
-f test
echo $?
-d test
echo $?
a=15
$a -lt 20 && echo "a \< 20" \|\| echo "a > 20"(输出15 < 20)
a=27
$a -lt 20 && echo "a \< 20" \|\| echo "a > 20"(输出27 > 20)
if单分支
cd /scripts
vim if_test.sh
在条件判断里面加入双引号和x防止报错,当变量传入为空时就用上x。
bash
#!/bin/bash
if [ "$1"x = "happygame"x ]
then
echo "welcome, happygame"
fi
chmod +x if_test.sh
./if_test.sh happygame
a=25
if $a -gt 18 && $a -lt 35 ; then echo OK; fi(输出OK)
if多分支
vim if_test.sh
bash
#!/bin/bash
if [ "$1"x = "happygame"x ]
then
echo "welcome, happygame"
fi
# 输入第二个参数表示年龄,判断属于哪个年龄段
if [ $2 -lt 18 ]
then
echo "未成年人"
else
echo "成年人"
fi
./if_test.sh happygame 15(未成年人)
./if_test.sh happygame 25(成年人)
vim if_test.sh
bash
#!/bin/bash
if [ "$1"x = "happygame"x ]
then
echo "welcome, happygame"
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 happygame 25(青年人)
./if_test.sh happygame 45(中年人)
./if_test.sh happygame 65(老年人)
case
vim case_test.sh
bash
#!/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(two)
./case_test.sh 6(number else)
for
vim sum_to.sh
用取到sum和i的值,加号运算符要在和\[\]运算表达式中
bash
#!/bin/bash
for (( i=1; i <= $1; i++ ))
do
sum=$[ $sum + $i ]
done
echo $sum
chmod +x sum_to.sh
./sum_to.sh 100(输出5050)
以下是linux中常用的写法:
for os in linux windows macos; do echo $os; done(输出这三个操作系统名称)
for i in {1..100}; do sum=\[sum+i\]; done; echo sum(输出5050)
\*和@的区别:
vim parameter_for_test.sh
bash
#!/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 e
\*把它们作为一个整体,@仍然把他们作为独立个体。
结果如下:

while
vim sum_to.sh
bash
#!/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]
done
echo $sum2
./sum_to.sh 100(输出两个5050)
read
用于读取控制台输入。
|----|------------------------------|
| -p | 指定读取值时的提示。 |
| -t | 指定读取值时等待的时间(秒)如果不加 - t则一直等待。 |
vim read_test.sh
bash
#!/bin/bash
read -t 10 -p "请输入您的名字:" name
echo "welcome, $name"
chmod +x read_test.sh
./read_test.sh
系统函数
vim cmd_test.sh
(date +%s)相当于用()将date函数的值取出来。
bash
#!/bin/bash
filename="$1"_log_$(date +%s)
echo $filename
chmod +x cmd_test.sh
./cmd_test.sh happygame
basename /root/scripts/parameter.sh(结果输出parameter.sh)
basename /root/scripts/parameter.sh .sh(结果输出parameter,删除了后缀.sh)
vim parameter.sh
bash
#!/bin/bash
echo '----------$n-----------'
echo script name: $(basename $0 .sh)
echo 1st parameter:$1
echo 2nd parameter:$2
echo '----------$#-----------'
echo parameter numbers:$#
echo '----------$*-----------'
echo $*
echo '----------$@-----------'
echo $@
/root/scripts/parameter.sh a b
dirname /root/scripts/parameter.sh(结果输出/root/scripts)
vim parameter.sh
先进入路径,再得出绝对路径。
bash
#!/bin/bash
echo '----------$n-----------'
echo script name: $(basename $0 .sh)
echo script path: $(cd $(dirname $0); pwd)
echo 1st parameter:$1
echo 2nd parameter:$2
echo '----------$#-----------'
echo parameter numbers:$#
echo '----------$*-----------'
echo $*
echo '----------$@-----------'
echo $@
./parameter.sh a b
自定义函数
vim fun_test.sh
用sum变量得到echo的返回值,如果用$?返回只能返回0-255的数字。
bash
#!/bin/bash
function add(){
s=$[$1 + $2]
echo $s
}
read -p "请输入第一个整数: " a
read -p "请输入第二个整数: " b
sum=$(add $a $b)
echo "和: "$sum
chmod +x fun_test.sh
./fun_test.sh(接下来要手动输入两个数)
还可以计算和的平方:
bash
#!/bin/bash
function add(){
s=$[$1 + $2]
echo $s
}
read -p "请输入第一个整数: " a
read -p "请输入第二个整数: " b
sum=$(add $a $b)
echo "和的平方: "$[$sum * $sum]
练习一:归档文件
vim daily_archive.sh
bash
#!/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"
echo
else
echo "归档出现问题!"
echo
fi
exit
mkdir /root/archive
chmod u+x daily_archive.sh
./daily_archive.sh ../scripts
接下来将这个脚本加入到定时任务中:
crontab -e
bash
0 2 * * * /root/scripts/daily_archive.sh /root/scripts
常用特殊字符
|----------------------------------------|-----------------------------|
| cat /etc/passwd | grep ^a | 找到以a开头的内容 |
| cat /etc/passwd | grep bash | 找到以bash结尾的内容 |
| cat daily_archive.sh \| grep -n \^ | 找到空行的位置,-n显示行号 |
| cat daily_archive.sh | grep r..t | 找到r和t中间有两个任意字符的内容,如root |
| cat etc/passwd | grep ro*t | 找到o出现0次或多次的内容,如rt,rot,root等 |
| cat etc/passwd | grep ^a.*bash | 找到以a开头以bash结尾的内容 |
| cat etc/passwd \| grep \^a.\*var.\*in | 找到以a开头以in结尾,中间还有var的内容 |
|-------------|-------------------|
| 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 ra,b,c*t | 找到rt,rat,rbt,rabt等的内容 |
| cat daily_archive.sh | grep '\' | 用\\转义,找到含的内容 |
| echo "13812345678" | grep ^1345780-90-90-90-90-90-90-90-90-9 | 筛选手机号 |
| echo "13812345678" \| grep -E \^1\[34578\]\[0-9\]{9} | 用-E支持扩展的正则 |
cut
vim cut.txt
bash
dong shen
guan zhen
wo wo
lai lai
le le
cat cut.txt
|----|-------------------------|
| -f | 列号,提取第几列 |
| -d | 分隔符,按照指定分隔符分割列,默认制表符\t |
| -c | 按字符进行切割,加n表示取第几列 |
cut -d " " -f 1 cut.txt(截取出第一列的内容)
cut -d " " -f 2,3 cut.txt(截取第二、三列的内容)
cat /etc/passwd | grep bash$

cat /etc/passwd | grep bash$ | cut -d ":" -f 1,6,7
截取出以下内容:

|----------------------------------------------------|---------------------|
| cat /etc/passwd | grep bash \| cut -d ":" -f 1-4 | 截取1-4列 |
| cat /etc/passwd \| grep bash | cut -d ":" -f -4 | 截取1-4列 |
| cat /etc/passwd | grep bash$ | cut -d ":" -f 4- | 截取4到最后一列 |
| ifconfig ens33 | grep netmask | cut -d " " -f 10 | 截取到IP地址,inet前面有8个空格 |
| ifconfig | grep netmask | cut -d " " -f 10 | 截取出IP地址,本地环回地址,虚拟地址 |
awk
|----|--------------------|
| -F | 指定输入文件分隔符,空格为默认分隔符 |
| -v | 赋值一个用户定义变量 |
|----------------------------------------------------------------------------------------------------|------------------------------------|
| cat /etc/passwd | grep ^root | cut -d ":" -f 7 | 以root开头的第七列所有内容 |
| cat /etc/passwd | awk -F ":" '/^root/ {print 7}' | 等价于上面的式子 |
| cat /etc/passwd \| awk -F ":" '/\^root/ {print 1","7}' | 第一列和第七列,要给逗号打双引号拼接 |
| cat /etc/passwd \| awk -F ":" '/\^root/ {print 1","6","7}' | 第一列第六列和第七列 |
| cat /etc/passwd | awk -F ":" 'BEGIN{print "user, shell"}{print 1","7} END{print "end of file"}' | 在最开头加入user, shell,在结尾加入end of file |
|-----------------------------------------------------|-----------------|
| cat /etc/passwd | awk -F ":" '{print 3+1}' | 将第三列的数字全部加1 |
| cat /etc/passwd \| awk -v i=1 -F ":" '{print 3+i}' | 将第三列的数字全部加i,更灵活 |
|----------|--------------------|
| FILENAME | 文件名 |
| NR | 已读的记录数(行号) |
| NF | 浏览记录的域的个数(切割后列的个数) |
[awk的内置变量]
|--------------------------------------------------------------------|--------------------------------|
| awk -F ":" '{print "文件名:"FILENAME " 行号:"NR " 列数:"NF }' /etc/passwd | 显示文件名、行号、列数 |
| ifconfig | awk '/^/ {print "空行:"NR}' | 显示空行的行号 |
| ifconfig \| awk '/netmask/ {print 2}' | 显示IP地址,本地环回地址,虚拟地址,awk前八个空格不考虑 |
练习二:发送消息
mesg(查看是否打开消息功能)
who -T(+则打开了消息功能,-则关闭了消息功能)

vim send_msg.sh
bash
#!/bin/bash
# 查看用户是否登录(-i忽略大小写,-m最多拿几行,awk那第一列)
login_user=$(who | grep -i -m 1 $1 | awk '{print $1}')
# -z判断是否为空
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
# 从参数中获取要发送的消息
whole_msg=$(echo $* | cut -d " " -f 2-)
# 获取用户登录的终端
user_terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
# 写入要发送的消息(write写入要指定发送的用户和发送的终端)
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 happygame hi, happygame