90%的Shell脚本,一篇搞定!

今天来复习下shell脚本的写法和常用语法。

Shell概述

​Shell位于Linux和外层应用程序之间,是一个命令行解释器,接收应用程序或用户的命令去调用操作系统的内核,同时Shell编程语言易编写、灵活性强。

Shell解析器:

[Robofly@node1 ~]$ cat /etc/shells
/bin/sh
/bin/bash    # CentOS默认解析器是bash
/usr/bin/sh  # sh -> bash
/usr/bin/bash
/bin/tcsh
/bin/csh

Shell脚本入门

脚本格式

脚本以#!/bin/bash开头。

# 直接用echo打印
[Robofly@node1 datas]$ touch helloworld.sh
[Robofly@node1 datas]$ vim helloworld.sh
#!/bin/bash
echo "Hello World!"
# 用echo追加内容到文件
[Robofly@node1 datas]$ touch batch.sh
[Robofly@node1 datas]$ vim batch.sh
#!/bin/bash
cd /home/datas
touch cls.txt
echo "I love cls" >>cls.txt

执行方式

  1. 采用bash或sh+脚本的相对或绝对路径执行,此方式不用赋予脚本执行权限。
# sh脚本的相对路径,前提是进入脚本所在的目录
[Robofly@node1 datas]$ sh helloworld.sh
# sh脚本的绝对路径
[Robofly@node1 ~]$ sh /home/Robofly/datas/helloworld.sh
# bash脚本的相对路径,前提是进入到脚本所在的目录
[Robofly@node1 datas]$ bash helloworld.sh
# bash脚本的绝对路径
[Robofly@node1 ~]$ bash /home/Robofly/datas/helloworld.sh
  1. 采用脚本的相对或绝对路径执行,此方式必须具有可执行权限。
# 先赋予脚本执行权限
[Robofly@node1 ~]$ chmod +x helloworld.sh
# 相对路径执行脚本
[Robofly@node1 datas]$ ./helloworld.sh
# 绝对路径执行脚本
[Robofly@node1 ~]$ /home/Robofly/datas/helloworld.sh

:第一种执行方法本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限,而第二种执行方法本质是脚本需要自己执行,所以需要执行权限。

变量和运算符

系统变量

# 常用的系统变量:$HOME $PWD $SHELL $USER等
[Robofly@node1 ~]$ echo $HOME # 查看系统变量的值
[Robofly@node1 ~]$ set # 显示当前Shell中所有的变量

自定义变量

[Robofly@node1 ~]$ A=5 # 定义变量A
[Robofly@node1 ~]$ A=8 # 给变量A重新赋值
[Robofly@node1 ~]$ unset A # 撤销变量A
[Robofly@node1 ~]$ readonly B=2 # 声明静态的变量B=2,不能unset
# bash中变量默认为字符串类型无法直接进行数值运算
[Robofly@node1 ~]$ C=1+2 
[Robofly@node1 ~]$ echo $C
1+2
# 变量的值如果有空格需要用双引号或单引号括起来
[Robofly@node1 ~]$ D="I love you"
# export+变量名可将变量提升为全局环境变量供其他shell程序使用
[Robofly@node1 ~]$ export B

特殊变量

# $n($0表示该脚本名称,$1-$9表示第一到第九个参数,10以上的参数需要用大括号包含${10})
[Robofly@node1 ~]$ touch para.sh
[Robofly@node1 ~]$ vim para.sh
#!/bin/bash 
echo "$0 $1 $2" # 双引号表示取变量的值,单引号不取变量的值,反引号(``)执行引号中的命令
[Robofly@node1 ~]$ chmod +x para.sh
[Robofly@node1 ~]$ ./para.sh cls xz
./para.sh cls xz
# $#(获取所有输入参数个数,常用于循环)
[Robofly@node1 ~]$ vim para2.sh
#!/bin/bash
echo "$0 $1 $2"
echo $# # 加不加双引号一样可输出参数个数,但加单引号依旧是输出字符
[Robofly@node1 ~]$ chmod +x para2.sh
[Robofly@node1 ~]$ ./para2.sh cls xz
./para2.sh cls xz 
2
# $*(命令行中所有的参数,把所有的参数看成一个整体)
# $@(也代表命令行中所有的参数,但是它把每个参数区分对待)
[Robofly@node1 ~]$ vim para3.sh
#!/bin/bash
echo "$0 $1 $2"
echo $#    echo $*    echo $@
[Robofly@node1 ~]$ bash para3.sh 1 2 3
./para3.sh 1 2
3    1 2 3    1 2 3
# $?(最后一次执行的命令的返回状态,如果这个变量的值为0表示上次命令正确执行,非0则表示上次命令执行不正确)
[Robofly@node1 datas]$ ./helloworld.sh
hello world
[Robofly@node1 datas]$ echo $?
0

运算符

# expr运算符间要有空格,没有空格作为字符串输出
[Robofly@node1 ~]$ expr 2 + 3 
# expr +,-,\*,/,% 加减乘除取余
[Robofly@node1 ~]$ expr `expr 2 + 3` \* 4
# $((运算式))或$[运算式]
[Robofly@node1 ~]$ s=$[(2+3)*4]
[Robofly@node1 ~]$ echo $s

条件判断

  1. 基本语法

[ 条件 ]:条件前后必须有空格。条件非空即为true,否则返回false。

常用判断条件:

-z:判断变量的值是否为空,空则返回0为true,非空返回1为false。
-n:判断变量的值是否为空,空则返回1为false,非空返回0为true。

  • 两个整数之间比较
    -lt:小于(less than)
    -le:小于等于(less equal)
    -eq:等于(equal)
    -gt:大于(greater than)
    -ge:大于等于(greater equal)
    -ne:不等于(not equal)
  • 按照文件权限进行判断
    -r:读权限(read)
    -w:写权限(write)
    -x:执行权限(execute)
  • 按照文件类型进行判断
    -f:文件存在且为常规文件(file)
    -e:文件存在(existence)
    -d:文件存在且为目录(directory)
  1. 举例
# 23是否大于等于22
[Robofly@node1 ~]$ [ 23 -ge 22 ]
[Robofly@node1 ~]$ echo $?
0
# helloworld.sh是否具有写权限
[Robofly@node1 ~]$ [ -w helloworld.sh ]
[Robofly@node1 ~]$ echo $?
0
# 目录中的文件是否存在
[Robofly@node1 ~]$ [ -e /home/Robofly/cls.txt ]
[Robofly@node1 ~]$ echo $?
1
# 多条件判断(&&表示前一条命令执行成功时才执行后一条命令,||表示前一条命令执行失败时才执行后一条命令)
[Robofly@node1 ~]$ [ condition ] && echo ok || echo notok
ok
[Robofly@node1 ~]$ [ condition ] && [  ] || echo notok
notok

流程控制

if判断

  1. 基本语法
# 语法1
if [ 条件判断 ];then
程序
fi
# 语法2
if [ 条件判断 ]
then 程序
fi

:中括号内的条件判断前后必须有空格,且if后必须要有空格。

  1. 举例
[Robofly@node1 ~]$ touch if.sh
[Robofly@node1 ~]$ vim if.sh
#!/bin/bash
if [ $1 -eq "1" ]
then
	echo "第一个参数为1"
elif [ $1 -eq "2" ]
then 
	echo "第一个参数为2"
fi
[Robofly@node1 ~]$ chmod +x if.sh
[Robofly@node1 ~]$ ./if.sh 1

case语句

  1. 基本语法
case $变量名 in
"值1")
​    程序1
;;
"值2")
​    程序2
;;
*)
​    都不是执行此程序
;;
esac

:case行尾必须为in,每一个模板必须以)结束,;;相当于break,*)相当于default。

  1. 举例
[Robofly@node1 ~]$ vim case.sh
#!/bin/bash
case $1 in
"1")
	echo "参数为1"
;;
"2")
	echo "参数为2"
;;
*)
	echo "参数即不为1也不为2"
esac
[Robofly@node1 ~]$ chmod +x case.sh
[Robofly@node1 ~]$ ./case.sh 1

for循环

  1. 基本语法
# 语法1
for((初始值;循环控制条件;变量变化))
	​do
		程序
​	done
# 语法2
for 变量 in 值1 值2 值3 ...
	do
		程序
	done
  1. 举例
# 从1加到100
[Robofly@node1 ~]$ vim for1.sh
#!/bin/bash
s=0
for((i=0;i<=100;i++))
	do
		s=$[$s+$i]
	done
echo $s
[Robofly@node1 ~]$ chmod +x for1.sh
[Robofly@node1 ~]$ ./for1.sh
# 打印所有输入参数
[Robofly@node1 ~]$ vim for2.sh
#!/bin/bash
# $*和$@都表示所有参数,不被""包含时都以$1$2...的形式输出所有参数
# 当它们被""包含时$*将参数作为一个整体以"$1$2..."输出,$@将参数分开以"$1""$2""..."形式输出
for i in $*
	do
		echo "第$i个参数"
	done
[Robofly@node1 ~]$ chmod +x for2.sh
[Robofly@node1 ~]$ ./for2.sh 1 2 3 4 5 6

while循环

  1. 基本语法
while [ 条件判断 ]
	do
		程序
	​done
  1. 举例
[Robofly@node1 ~]$ vim while.sh
# 从1加到100
#!/bin/bash
s=0
i=1
while [ $i -le 100 ]
	do
		s=$[$s+$i]
		i=$[$i+1]
	done
echo $s
[Robofly@node1 ~]$ chmod +x while.sh
[Robofly@node1 ~]$ ./while.sh

:凡是条件判断的都要在[ ]内的条件判断前后加空格。

函数

read读取控制台输入

  1. 基本语法
read (选项) (参数)
# 选项:-p指定读取值时的提示符;-t指定读取值时等待的时间(秒)
# 参数:变量-指定读取值的变量名
  1. 举例
[Robofly@node1 ~]$ vim read.sh
#!/bin/bash
read -t 7 -p "请在7秒内输入你的名字:" NAME
echo $NAME
[Robofly@node1 ~]$ chmod +x read.sh
[Robofly@node1 ~]$ ./read.sh

系统函数

# basename [string/pathname][suffix]截取路径的文件名称
# suffix为后缀,如果suffix被指定了则basename会将string或pathname中的suffix去掉
[Robofly@node1 ~]$ basename /home/Robofly/para.sh
para.sh
[Robofly@node1 ~]$ basename /home/Robofly/para.sh .sh
para
# dirname+文件绝对路径来获取文件的路径
[Robofly@node1 ~]$ dirname /home/Robofly/para.sh
/home/Robofly

自定义函数

  1. 基本语法
[ function ] funname[()]
{ 
action
[ return int; ]
}

:必须在调用函数之前声明函数,因shell脚本是逐行运行的。函数返回值只能通过$?系统变量获得,可以显式的加return 返回,如果不加将以最后一条命令运行结果作为返回值,return后跟数值n(0-255)。

  1. 举例
[Robofly@node1 ~]$ vim fun.sh
#计算两个输入参数之和
#!/bin/bash
function sum()
{
	s=0
	s=$[$1+$2]
	echo "$s"
}
# 下面的语句可不加分号,但如果写成单行需要用分号进行区分,如果像现在这样写成块则相当于用换行符替代了分号,故可不加分号
read -p "请输入第一个参数:" n1; 
read -p "请输入第二个参数:" n2;
sum $n1 $n2;
[Robofly@node1 ~]$ chmod +x fun.sh
[Robofly@node1 ~]$ ./fun.sh

Shell工具

cut工具

  1. 基本语法
# cut命令从文件的每一行剪切字节、字符和字段并将其输出
cut [ 选项参数 ] filename 
# 选项参数:-f:列号表提取第几列;-d:分隔符表按指定分隔符分割列;-c:指定具体的字符

:默认的分隔符是制表符。

  1. 举例
# 数据准备
[Robofly@node1 ~]$ vim cut.txt
gao hang
fei zhou
wo  wo
lai  lai
le  le
# 按空格切割并提取出第一列
[Robofly@node1 ~]$ cut -d " " -f 1 cut.txt # 按空格切可理解为去掉一个空格后取第n列
# 从文件中切割出fei(从前往后执行的)
[Robofly@node1 ~]$ cat cut.txt | grep "fei" | cut -d " " -f 1
# 切出PATH值中第二个:开始后的所有路径
[Robofly@node1 ~]$ echo $PATH | cut -d : -f 3-
# 切出IP地址
[Robofly@node1 ~]$ ifconfig ens33 | grep netmask | cut -d "n" -f 2 | cut -d " " -f 2

awk工具

  1. 基本语法
# awk将文件逐行的读入,以空格为默认的分隔符将每行切开
awk [ 选项参数 ] 'pattern1{action1} pattern2{action2}...' filename
# 选项参数:-F:指定输入文件的分隔符;-v:赋值一个用户定义变量
# pattern:在数据中要查找的内容
# action:在找到匹配内容时所执行的一系列命令
# awk的内置变量:FILENAME-文件名,NR-已读的记录数,NF-浏览记录的域的个数(切割后的列个数)
  1. 举例
# 数据准备
[Robofly@node1 ~]$ sudo cp /etc/passwd ./
# 对passwd文件按:进行分割,找到以root开头的行并打印该行的第7列
[Robofly@node1 ~]$ awk -F : '/^root/{print $7}' passwd
# 对passwd文件按:进行分割,找到以root开头的行并打印该行的第1列和第7列且以','分割
[Robofly@node1 ~]$ awk -F : '/^root/{print $1","$7}' passwd # 只有匹配了pattern的行才会执行action
# 对passwd文件按:切割,找到第1列和第7列以","隔开,且在所有行前加user,shell在最后一行加end,/bin/hello
[Robofly@node1 ~]$ awk -F : 'BEGIN{print "user,shell"} {print $1","$7} END{print "end,/bin/hello"}' passwd # BEGIN在所有数据读取行之前执行,END在所有数据执行之后执行
# 将passwd文件中的用户id增加数值1并输出
[Robofly@node1 ~]$ awk -v i=1 -F : '{print $3+i}' passwd
# 统计passwd的文件名,每行的行号,每行的列数
[Robofly@node1 ~]$ awk -F : '{print "filename:"FILENAME ",linenumber:"NR ",columns:"NF}' passwd

sort工具

  1. 基本语法
# sort命令在Linux里非常有用,它将文件进行排序并将排序结果标准输出。
sort (选项) (参数)
# 选项:-n:依照数值的大小排序;-r:以相反的顺序来排序;-t:设置排序时所用的分隔字符;-k:指定需要排序的列
# 参数:指定待排序的文件列表
  1. 举例
# 数据准备
[Robofly@node1 datas]$ vim sort.sh
aa:23:5.4
bb:67:4.3
cc:89:2.6
dd:12:3.8
ee:90:1.5
# 按照":"分割后的第三列倒序排序
[Robofly@node1 datas]$ sort -t : -nrk 3 sort.sh
aa:23:5.4
bb:67:4.3
dd:12:3.8
cc:89:2.6
ee:90:1.5

正则表达式

常规匹配

# 匹配所有包含root的行
[Robofly@node1 ~]$ cat passwd | grep root

常用特殊字符

[Robofly@node1 ~]$ cat passwd | grep ^a # 匹配出所有以a开头的行
[Robofly@node1 ~]$ cat passwd | grep t$ # 匹配出所有以t结尾的行
[Robofly@node1 ~]$ cat passwd | grep r..t # .匹配一个任意的字符
[Robofly@node1 ~]$ cat passwd | grep ro*t # 和上一个字符连用表示匹配上一个字符0次或多次
[Robofly@node1 ~]$ cat passwd | grep r[a,b,c]*t # 匹配[]中字符0次或多次
[Robofly@node1 ~]$ cat passwd | grep a\$b # 匹配所有包含a$b的行

其他特殊字符

太多不便列出,需要的公众号后台回复“正则表达式”获取。

以上就是今天的内容分享,下篇见。

posted @ 2023-03-10 11:46  码农高飞  阅读(0)  评论(0编辑  收藏  举报