文章目录
- 一、前言
- 二、Linux脚本编写基础
- 2.1 文件开头
- 2.2 注释
- 2.3 变量
- 2.3.1 系统变量
- 2.3.2 环境变量
- 2.3.3 用户环境变量
- 2.4 注意事项
- 三、shell脚本中常用的三类命令
- 3.1 Linux命令
- 3.2 管道、重定向和命令置换
- 3.2.1 管道
- 3.2.2 重定向
- 3.2.3 命令置换
- 四、流程控制
- 4.1 说明性语句
- 4.2 功能性语句
- 4.3 结构性语句
- 4.3.1 条件语句
- 4.3.1.1 if语句
- **4.3.1.2 ****case多路分支语句**
- 4.3.1.3 for循环语句
- 4.3.1.4 while循环语句
- 4.3.1.5 select选择语句
- 五、函数
- 5.1 函数语法
- 5.2 函数变量作用域
- 5.3 函数的传参
- 5.4 函数的返回值
- 5.4.1 使用echo或者其他命令输出
- 5.4.2 第二种方式
- 六、数组
- 七、其他
- :命令
- eval命令
- exit命令
- export命令
- set命令
- unset命令
- 八、正则表达式
一、前言
本文章主要写的是shell脚本的相关内容,用于自用和新手入门。
二、Linux脚本编写基础
2.1 文件开头
不支持 Bash 特有的扩展功能(如数组、算数运算等)。
#!/bin/sh
需要在脚本中使用 Bash 特有的功能或扩展
#!/bin/bash
2.2 注释
使用#开头
#这是注释
2.3 变量
无需定义,输出需要使用$(xx)来防止错误。任何数据都解释成一串字符。
变量=value
count=1
echo $count
DATE=$(date)
echo $DATE
有三种变量:用户自定义变量、系统变量、环境变量。
用户自定义变量通常用大写变量来区别,COUNT=1
unset命令删除变量的赋值。
2.3.1 系统变量
用法:主要是用于对参数判断和命令返回值判断时使用。
$0 与键入的命令行一样,包含脚本文件名
$1,$2,……$9 分别包含第一个到第九个命令行参数
$* 包含所有命令行参数
$# 包含命令行参数的个数
$? 前一个命令的退出状态,返回0表示执行成功
$$ 包含正在执行进程的ID号
2.3.2 环境变量
用法:主要是在程序运行时需要设置。
PATH shell搜索路径,以冒号(:)分割。
HOME /etc/passwd文件中列出的用户主目录。
SHELL 显示当前的shell类型。
USER 打印当前用户名。
ID 打印当前用户id信息。
PWD 显示当前所在路径。
TERM 打印当前终端类型。常用的有vt100,ansi,vt200,xterm等
HOSTNAME 显示当前主机名。
PS1,PS2: 默认提示符($)及换行提示符(>)
HISTSIZE 历史命令大小,可通过HISTTIMEFORMAT变量设置命令执行时间。
PANDOM 随机生成一个0到32767的整数。
IFS:Internal Field Separator, 默认为空格,tab及换行符。
$PATH:取PATH变量里面的值。
2.3.3 用户环境变量
用法:又称局部变量,主要是用在shell脚本内部或者临时局部使用。
2.4 注意事项
1.shell命名:一般为英文、大写、小写、后缀为.sh结尾。不能用特殊符号、空格。
2.shell脚本变量:不能以数字、特殊符号开头,可以使用下划线不能用破折号。
3.";"在bash脚本中是命令分隔符,用于在同一行中分隔多个命令。
4.||:or操作符。当||左边的命令执行失败(返回非零值)时,||右边的命令才会被执行。
5.&& || 这两个符号组合起来可以实现类似三目运算符的功能。
6.单引号:
不会进行变量或命令的替换:在单引号内的所有内容都会被当作普通字符处理,即使其中包含变量或命令,Shell也不会尝试去替换它们。
不支持转义字符:在单引号内的字符都是字面意义上的字符,包括反斜杠 ()。
7.双引号:
会进行变量或命令的替换:在双引号内的变量或命令会被Shell替换为相应的值或输出。
支持部分转义字符:双引号内支持一些转义字符,例如 \n 代表换行,\t 代表制表符等。但是,有些转义字符(如 KaTeX parse error: Undefined control sequence: \) at position 6: 、` 和 \̲)̲在双引号中有特殊的意义。在双引…()的方式执行命令等操作。
三、shell脚本中常用的三类命令
3.1 Linux命令
看我之前的Linux下常用的系统命令
3.2 管道、重定向和命令置换
3.2.1 管道
将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l
在file.txt中搜索包含有”hello”的行并计算其行数。
在这里grep命令的输出作为wc命令的输入。wc -l用来统计行数
3.2.2 重定向
将命令的结果输出到文件,而不是标准输出(屏幕)。
是写入文件并覆盖旧文件。
加到文件的尾部,保留旧文件内容。
<输入重定向,将文件内容作为输入传递给命令。
ls -l > lsoutput.txt
通过>操作符把标准输出重定向到一个文件。默认情况下,如果该文件已经存在,其内容将被覆盖。
ps >> lsoutput.txt
>>操作符将输出内容附加到一个文件中。
more < killout.txt
<代表重定向输入 more命令与cat类似,只不过可以一页一页输出
需要注意的是,如果有一系列的命令需要执行,相应的输出文件是在这一组命令被创建的同时立刻被创建或写入的,所以不要在命令流中重复使用相同的文件名。
3.2.3 命令置换
方法一:
command1 command2
echo `wc -l < /etc/passwd`
方法二:
command1 $(command2)
echo $(wc -l < /etc/passwd)
四、流程控制
在 Shell 脚本中,通常将语句分为说明性语句、功能性语句和结构性语句这三类。
4.1 说明性语句
#! /bin/sh
4.2 功能性语句
read:从标准输入读入一行,并赋值给后面的变量。
read var
read username
read year month day
4.3 结构性语句
4.3.1 条件语句
4.3.1.1 if语句
配合test或者[]使用。使用[]时,必须要在[符号和被检查的条件之间留出空格,可以把[符号看作和test一样,test和后面的条件之间总是有一个空格。如果要把then放在和if同一行上,则必须要用一个分号把if和then分开。
格式:
if(表达式)
then
语句1
elif(表达式)
then
语句2
else
语句3
fi
#!/bin/sh
#从键盘输入一个代表天气的字符串,如果是晴天,就出去玩
read str
if test ${str} = "晴天";then
echo "出去玩"
fi
#!/bin/sh
#从键盘输入一个代表天气的字符串,如果是晴天,就出去玩
read str
if [ ${str} = "晴天" ];then
echo "出去玩"
fi
字符串测试 | 结果 |
---|---|
s1 = s2 | 测试两个字符串的内容是否完全一样 |
s1 != s2 | 测试两个字符串的内容是否有差异 |
-z s1 | 测试s1 字符串的长度是否为0 |
-n s1 | 测试s1 字符串的长度是否不为0 |
整数测试 | |
---|---|
a -eq b | 测试a与b是否相等 |
a -ne b | 测试a与b是否不相等 |
a -gt b | 测试a是否大于b |
a -ge b | 测试a是否大于等于b |
a -lt b | 测试a 是否小于b |
a -le b | 测试a是否小于等于b |
文件测试 | |
---|---|
-d name | 测试name 是否为一个目录,判断目录是否存在。 |
-e name | 测试一个文件是否存在。 |
-f name | 测试name 是否为普通文件,判断文件是否存在。 |
-L name | 测试name 是否为符号链接。 |
-r name | 测试name 文件是否存在且为可读。 |
-w name | 测试name 文件是否存在且为可写。 |
-x name | 测试name 文件是否存在且为可执行。 |
-s name | 测试name 文件是否存在且其长度不为0。 |
f1 -nt f2 | 测试文件f1 是否比文件f2 更新。 |
f1 -ot f2 | 测试文件f1 是否比文件f2 更旧。 |
**4.3.1.2 **case多路分支语句
主要用于对多个选择条件进行匹配输出,与if elif语句结构类似,通常用于脚本传递输入参数,打印出输出结果及内容。
格式:
case 字符串变量(模式名) in
模式1)
命令表1
;;
模式2)
命令表2
;;
模式n)
命令表n
;;
*)
不符合以上模式执行的命令
esac
注:
- case语句只能检测字符串变量
- 各模式中可用文件名元字符,以右括号结束
- 一次可以匹配多个模式用“|”分开
- 命令表以单独的双分号行结束,退出case语句
- 模式 n常写为字符* 表示所有其它模式
- 最后一个双分号行可以省略
#!/bin/sh
echo “Is it morning?Please answer yes or no”
read timeofday
case “$timeofday” in
yes|y) echo "Good Morning";;
no|n) echo "Good Afternoon";;
*) echo "answer not recognized";;
esac
exit 0
4.3.1.3 for循环语句
格式:
#格式:for name [ [ in [ word ... ] ] ; ] do list ; done
for 变量名 in 取值列表
do
语句 1
done
例:
for a in 1 2 3 4 5
do
echo $a
done
( s e q 1254 ) :这是一个 < / f o n t > 命令替换 < f o n t s t y l e = " c o l o r : r g b ( 51 , 51 , 51 ) ; " > 的结构。 (seq 1 254):这是一个</font>命令替换<font style="color:rgb(51, 51, 51);">的结构。 (seq1254):这是一个</font>命令替换<fontstyle="color:rgb(51,51,51);">的结构。(…) 表示执行其中的命令,并将其标准输出替换到当前位置。
seq 1 254 是一个 Unix 命令,用于生成从 1 到 254 的整数序列。
&&和||是条件操作符。如果ping命令成功执行(即主机在线),则result被设置为0;否则,result被设置为1。
#!/bin/bash
#check hosts is on/Off
#功能:检查多台主机存活情况。
Network=$1
for Host in $(seq 1 254)
do
ping -c 1 $Network.$Host > /dev/null && result=0 || result=1
if [ "$result" == 0 ];then
echo -e "\033[32;1m$Network.$Host is up \033[0m"
echo "$Network.$Host" >> /tmp/up.txt
else
echo -e "\033[;31m$Network.$Host is down \033[0m"
echo "$Network.$Host" >> /tmp/down.txt
fi
done
4.3.1.4 while循环语句
格式:
while (表达式)
do
语句1
done
例1:
let是一个Bash内置命令,用于执行算术操作。这里N++将变量N的值增加1。如果N之前没有定义或赋值,那么它会被初始化为0,并且其值在每次循环时递增1。
while true
do
let N++
if [ $N -eq 5 ]
then
break
fi
echo $N
done
例2:
i=0
while ((i<=100))
do
echo $i
i=`expr $i + 1`
done
在Bash脚本中,while ((i<=100)) 和 while [ i<=100 ] 的条件判断方式存在显著区别:
- while ((i<=100)) 使用了Bash的算术扩展语法,允许直接在条件中进行算术运算和比较。这种写法下,i会被解释为变量名,并与100进行数值比较,无需额外的引号或转义处理。
- while [ i<=100 ] 则使用了基本的括号测试结构。在这一结构中,i<=100会被当作字符串整体处理,而不是进行数值比较,这会导致测试失败,无法实现预期的循环控制功能。
建议在需要进行数值比较时使用((…))结构,以确保条件判断的准确性。
while [ $i -le 100 ]
do
echo $i
i=$((i + 1))
done
i=expr i + 1 等价于 i = i + 1等价于i= i+1等价于i=((i + 1)):expr的作用是告诉程序后面是进行算数运算。
j=expr $i + j 等价于 j = j等价于j= j等价于j=((i+j))。
例3:
[root@web-server01~/script]# vim login.sh
#!/bin/bash
#Check File to change.
#By author rivers 2021-9-27
USERS="hbs"
while true
do
echo "The Time is `date +%F-%T`"
sleep 10
NUM=`who|grep "$USERS"|wc -l`
if [[ $NUM -ge 1 ]];then
echo "The $USERS is login in system."
fi
done
date +%F-%T --> 2024-04-21-15:42:04
4.3.1.5 select选择语句
格式:
#select 是一个类似于 for 循环的语句
#Select语句一般用于选择,常用于选择菜单的创建,可以配合PS3来做打印菜单的输出信息,其语法格式以select…in do开头,done结尾:
#先设置ps3的值,我们输入的数据会保存到i中,表达式会显示选项
PS3=""
select i in (表达式)
do
语句
done
例1:
选择mysql版本
#!/bin/bash
#by author rivers on 2021-9-27
PS3="Select a number: "
while true
do
select mysql_version in 5.1 5.6 quit
do
case $mysql_version in
5.1)
echo "mysql 5.1"
break
;;
5.6)
echo "mysql 5.6"
break
;;
quit)
exit
;;
*)
echo "Input error, Please enter again!"
break
esac
done
done
五、函数
Shell编程函数默认不能将参数传入()内部,Shell函数参数传递在调用函数名称传递,例如name args1 args2。
5.1 函数语法
定义一个函数,只需简单的写出它的名字,然后是一对空括号,再把有关的语句放在一对花括号中
#!/bin/sh
foo()
{
echo “this is a example”
}
echo “script starting”
foo #直接调用
echo “script ended”
exit 0
5.2 函数变量作用域
如果要在shell函数中声明局部变量,则要使用local关键字,局部变量的作用域则局限在函数范围
内。此外,函数还可访问全局作用范围内的其他shell变量。如果全局变量和局部变量出现了同
名,则局部变量会覆盖全局变量,但仅限于函数范围内。
声明局部变量的格式:Local variable_name =value
例一:
#!/bin/sh
sample_text="global variable"
foo()
{
local sample_text="local variable"
echo "function is executing"
echo $sample_text
}
echo "script starting"
foo
echo $sample_text
echo "script ending"
exit 0
5.3 函数的传参
当一个函数被调用时,脚本程序的位置参数$*, $@, $#, $1, $2等会被替换为函数的参数,当函数执行完毕后,这些参数会恢复为他们先前的值。
#!/bin/sh
foo()
{
echo $0
echo $1
echo aa
}
foo aa bb cc
echo $0 $1
#运行如下
#./test ee rr
./test
aa
aa
./test ee
5.4 函数的返回值
5.4.1 使用echo或者其他命令输出
- 在 Shell 脚本中,函数的返回值并不是通过 return 语句来传递的,而是通过函数的标准输出(stdout)。当你在函数中使用 echo 或其他命令输出内容时,这些内容会被捕获并赋值给变量。
- result=$(foo) 使用了命令替换(Command Substitution)语法 $(…)。它的作用是执行括号中的命令,并将命令的输出作为变量的值。如果函数 foo 中有多个 echo 语句,命令替换 $(foo) 会捕获函数中所有 echo 的输出,并将它们作为一个整体赋值给变量 result。换言之,所有输出会按顺序拼接在一起,并存储到变量中。
#!/bin/sh
foo()
{
echo "JAY"
}
result=$(foo)
echo "aaa".$result."bb"
#运行如下
#./aa
aaa.JAY.bb
备注:
$* 和 $@ 都表示传递给函数或脚本的所有参数,当 $* 和 $@ 不被双引号包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。
但当他们加上双引号后就有区别了:
“$*” 会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
“$@” 仍然将每个参数都看作一份数据,彼此之间是独立的。
5.4.2 第二种方式
格式:
function_name [arg1 arg2 … ]
echo $?
获取函数的返回的状态
在 Shell 脚本中,return 命令用于从函数中返回一个退出状态码(exit status),但它并不限于只能返回 0 或 1。实际上,return 可以返回 0 到 255 之间的任何整数值。
check_user( )
{
#查找已登录的指定用户
user=`who | grep $1 | wc -l`
if [ $user –eq 0 ]
then
return 0 #未找到指定用户
else
return 1 #找到指定用户
fi
}
while true # MAIN, Main, main: program begin here
do
echo "Input username: \c"
read uname
check_user $uname # 调用函数, 并传递参数uname
if [ $? –eq 1 ] # $?为函数返回值
then
echo "user $uname online"
else
echo "user $uname offline"
fi
done
六、数组
格式:
array=(元素1 元素2 元素3 ...)
用小括号初始化数组,元素之间用空格分隔。
定义方法 1:初始化数组 array=(a b c)
定义方法 2:新建数组并添加元素 array[下标]=元素
定义方法 3:将命令输出作为数组元素array=($(command))
#方法 1:
#!/bin/bash
IP=(10.0.0.1 10.0.0.2 10.0.0.3)
for ((i=0;i<${#IP[*]};i++))
do
echo ${IP[$i]}
done
eg:
10.0.0.1
10.0.0.2
10.0.0.3
#方法 2:
#!/bin/bash
IP=(10.0.0.1 10.0.0.2 10.0.0.3)
for IP in ${IP[*]}
do
echo $IP
done
${IP[*]} 用于表示数组 IP 的所有元素。具体地说,它会将数组 IP 中的所有元素作为独立的字符串参数展开。
I P [ @ ] 、 {IP[@]}、 IP[@]、{IP[*]}的区别:
在 Bash shell 脚本中,${IP[@]} 和 ${IP[*]} 都用于访问数组 IP 的所有元素,但是它们在处理数组元素时的行为略有不同,特别是在涉及引号、空格和通配符时。
1.引号处理:
${IP[@]} 会保留数组元素中的引号,这意味着如果数组元素包含空格或特殊字符,并且这些字符被引号括起来,那么 ${IP[@]} 将确保这些引号在展开时仍然存在。
${IP[*]} 则不会保留引号,它简单地将所有元素连接成一个由空格分隔的字符串。如果数组元素中有空格或特殊字符,并且它们没有被引号括起来,那么这些元素可能会被错误地解释。
2.通配符扩展:
${IP[@]} 在展开时不会执行通配符扩展(即文件名扩展)。如果数组元素包含通配符(如 *),那么这些通配符将保持原样,不会被扩展为匹配的文件名。
${IP[]} 在某些情况下会执行通配符扩展。如果数组元素包含通配符,并且这些通配符在当前上下文中可以匹配到文件名,那么 ${IP[]} 会将这些通配符扩展为匹配的文件名列表。
3.安全性:
由于 ${IP[@]} 保留了引号并且不会执行通配符扩展,它通常更安全,特别是在处理包含空格、特殊字符或通配符的数组元素时。
${IP[*]} 可能会在处理这些元素时引入意外的行为,因此在使用时需要更加小心。
七、其他
:命令
冒号(:)是一个空命令,它偶尔会被用于逻辑简化,相当于true的一个别名,由于它是内置命
令,所以它的运行效率要比true高。
eval命令
eval命令允许对参数进行求值。它是shell的内置命令,通常不会以单独命令的形式存在。
foo=10
x=foo
y='$'$x 相当于y=$foo
echo $y
打印出的结果是$foo
而
foo=10
x=foo
eval y='$'$x
echo $y
输出是10
当然你也可以用另外一种方法对变量求值
y=$(($x)) //相当于求值再求值
exit命令
exit n命令以退出码n结束运行。
如果你在退出时没有指定一个退出状态,那么最后一条被执行的命令的状态将被用作返回值。
在shell脚本编程中,0表示成功,1-125是脚本程序使用的错误代码
export命令
export命令将作为它参数的变量导出到子shell中,并使之在子shell中有效。
export2:
#!/bin/sh
echo "$foo"
echo "$bar"
export1:
#!/bin/sh
foo="the first meta-syntactic variable"
export bar="the second meta-syntactic variable"
./export2
运行export1,输入如下:
the second meta-syntactic variable
第一个空行的出现是因为变量foo在export2中不可用,所以$foo被赋值为空,echo一个空变
量将输出一个空行。
注意:当变量被一个shell导出后,它就可以被该shell调用的任何脚本使用,也可以被后续
调用的任何shell使用。如果export2调用了另一个脚本,bar的值对新脚本来说仍然有效。
set命令
set命令的作用是为shell设置参数变量(实际上就是给$0、$1等赋值)。
#!/bin/sh
echo the date is $(date)
set $(date)
echo the month is $2
#输出如下
the date is 2019年 09月 12日 星期四 15:06:23 CST
the month is 09月
unset命令
unset命令的作用是从环境变量中删除变量或函数。这个命令不能删除shell本身定义的只读变
量。
#!/bin/sh
foo="hello world"
echo $foo
unset foo
echo $foo
#输出如下,第二行为空
hello world
八、正则表达式
使用shell时,从一个文件中抽取多于一个字符串将会很麻烦。例如,在一个文本中抽取一个词,它的头
两个字符是大写的,后面紧跟四个数字。如果不使用某种正则表达式,在shell中将很难实现这个操作。
^ | 锚定行的开始,如:'^grep’匹配所有以grep开头的行。 |
---|---|
$ | 锚定行的结束,如:'grep$'匹配所有以grep结尾的行。 |
. | 匹配一个非换行符的字符,如:'gr.p’匹配gr后接一个任意字符,然后是p。 |
* | 匹配零个或多个先前字符,如:*grep 匹配所有一个或多个字符后紧跟grep的行。. 一起用代表任意字符。 |
[] | 匹配一个指定范围内的字符,如’[Gg]rep’匹配Grep和grep。 |
[^] | 匹配一个不在指定范围内的字符,如:'[^ A-FH-Z]rep’匹配不包含A-F和H-Z的一个字母开头,紧跟rep的行。 |
< | 锚定单词的开始,如:'<grep’匹配包含以grep开头的单词的行。 |
> | 锚定单词的结束,如’grep>'匹配包含以grep结尾的单词的行。 |
x{m} | 含有重复字符x,m次的字符串,如:‘0{5}’匹配包含5个0的行。实际上是 重复字符x,m至少m次 |
x{m,} | 重复字符x,至少m次 |
x{m,n} | 重复字符x,至少m次,不多于n次,如:‘o{5,10}’匹配5–10个o的行。(多于N次好像也OK) |
\w | 匹配字母和数字字符,也就是[A-Za-z0-9],如:'G\w*p’匹配以G后跟零个或多个文字或数字字符,然后是p。 |
\W | \w的反置形式,匹配一个或多个非单词字符,如点号句号等。 |
使用grep命令
#!/bin/sh
text="Hello, World!"
pattern="^Hello.*World!$"
if echo "$text" | grep -qP "$pattern"; then
echo "Match found!"
else
echo "No match."
fi
#输出
Match found!
使用sed命令
#!/bin/sh
text="Hello, World!"
pattern="Hello"
# 替换 "Hello" 为 "Hi"
result=$(echo "$text" | sed "s/$pattern/Hi/")
echo "$result"
#输出
Hi, World!
使用awk命令
#!/bin/sh
text="Hello, World!"
pattern="Hello"
# 检查是否包含 "Hello"
if echo "$text" | awk "/$pattern/ {print 'Match found!'}"; then
echo "Match found!"
else
echo "No match."
fi
#输出
Match found!