Shell

2024-06-12

Shell

[TOC]

Shell

Shell 脚本是什么?

一个 Shell 脚本是一个文本文件,包含一个或多个命令。作为系统管理员,我们经常需要使用多个命令来完成一项任务,我们可以添加这些所有命令在一个文本文件(Shell 脚本)来完成这些日常工作任务。

什么是默认登录 Shell ?

在 Linux 操作系统,"/bin/bash" 是默认登录 Shell,是在创建用户时分配的。

使用 chsh 命令可以改变默认的 Shell 。示例如下所示:

1
2
## chsh <用户名> -s <新shell>
## chsh ThinkWon -s /bin/sh

在 Shell 脚本中,如何写入注释?

注释可以用来描述一个脚本可以做什么和它是如何工作的。每一行注释以 # 开头。例子如下:

1
2
3
#!/bin/bash
## This is a command
echo “I am logged in as $USER

语法级

可以在 Shell 脚本中使用哪些类型的变量?

在 Shell 脚本,我们可以使用两种类型的变量:

  • 系统定义变量

    系统变量是由系统系统自己创建的。这些变量通常由大写字母组成,可以通过 set 命令查看。

  • 用户定义变量

    用户变量由系统用户来生成和定义,变量的值可以通过命令 "echo $<变量名>" 查看。

Shell脚本中 $? 标记的用途是什么?

在写一个 Shell 脚本时,如果你想要检查前一命令是否执行成功,在 if 条件中使用 $? 可以来检查前一命令的结束状态。

  • 如果结束状态是 0 ,说明前一个命令执行成功。例如:

    1
    2
    3
    4
    root@localhost:~## ls /usr/bin/shar
    /usr/bin/shar
    root@localhost:~## echo $?
    0
  • 如果结束状态不是0,说明命令执行失败。例如:

    1
    2
    3
    4
    root@localhost:~## ls /usr/bin/share
    ls: cannot access /usr/bin/share: No such file or directory
    root@localhost:~## echo $?
    2

Bourne Shell(bash) 中有哪些特殊的变量?

下面的表列出了 Bourne Shell 为命令行设置的特殊变量。

1
2
3
4
5
6
7
8
内建变量    解释
$0 命令行中的脚本名字
$1 第一个命令行参数
$2 第二个命令行参数
….. …….
$9 第九个命令行参数
$## 命令行参数的数量
$* 所有命令行参数,以空格隔开

如何取消变量或取消变量赋值?

unset 命令用于取消变量或取消变量赋值。语法如下所示:

1
## unset <变量名>

Shell 脚本中 if 语法如何嵌套?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if [ 条件 ]
then
命令1
命令2
…..
else
if [ 条件 ]
then
命令1
命令2
….
else
命令1
命令2
…..
fi
fi

在 Shell 脚本中如何比较两个数字?

if-then 中使用测试命令( -gt 等)来比较两个数字。例如:

1
2
3
4
5
6
7
8
9
#!/bin/bash
x=10
y=20
if [ $x -gt $y ]
then
echo “x is greater than y”
else
echo “y is greater than x”
fi

Shell 脚本中 case 语句的语法?

基础语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case 变量 in
值1)
命令1
命令2
…..
最后命令
!!
值2)
命令1
命令2
……
最后命令
;;
esac

Shell 脚本中 for 循环语法?

基础语法如下:

1
2
3
4
5
6
7
for 变量 in 循环列表
do
命令1
命令2
….
最后命令
done

Shell 脚本中 while 循环语法?

如同 for 循环,while 循环只要条件成立就重复它的命令块。
不同于 for循环,while 循环会不断迭代,直到它的条件不为真。

基础语法:

1
2
3
4
while [ 条件 ]
do
命令…
done

do-while 语句的基本格式?

do-while 语句类似于 while 语句,但检查条件语句之前先执行命令(LCTT 译注:意即至少执行一次。)。下面是用 do-while 语句的语法:

1
2
3
4
do
{
命令
} while (条件)

Shell 脚本中 break 命令的作用?

break 命令一个简单的用途是退出执行中的循环。我们可以在 whileuntil 循环中使用 break 命令跳出循环。

Shell 脚本中 continue 命令的作用?

continue 命令不同于 break 命令,它只跳出当前循环的迭代,而不是整个循环。continue 命令很多时候是很有用的,例如错误发生,但我们依然希望继续执行大循环的时候。

如何使脚本可执行?

使用 chmod 命令来使脚本可执行。例子如下:chmod a+x myscript.sh

#!/bin/bash 的作用?

#!/bin/bash 是 Shell 脚本的第一行,称为释伴(shebang)行。

  • 这里 # 符号叫做 hash ,而 ! 叫做 bang。
  • 它的意思是命令通过 /bin/bash 来执行。

如何调试 Shell脚本?

  • 使用 -x' 数(sh -x myscript.sh)可以调试 Shell脚本。
  • 另一个种方法是使用 -nv 参数(sh -nv myscript.sh)。

如何将标准输出和错误输出同时重定向到同一位置?

  • 方法一:2>&1 (如## ls /usr/share/doc > out.txt 2>&1 )
  • 方法二:&> (如## ls /usr/share/doc &> out.txt )

在 Shell 脚本中,如何测试文件?

test 命令可以用来测试文件。基础用法如下表格:

1
2
3
4
5
6
7
8
Test         用法
-d 文件名 如果文件存在并且是目录,返回true
-e 文件名 如果文件存在,返回true
-f 文件名 如果文件存在并且是普通文件,返回true
-r 文件名 如果文件存在并可读,返回true
-s 文件名 如果文件存在并且不为空,返回true
-w 文件名 如果文件存在并可写,返回true
-x 文件名 如果文件存在并可执行,返回true

在 Shell 脚本如何定义函数呢?

函数是拥有名字的代码块。当我们定义代码块,我们就可以在我们的脚本调用函数名字,该块就会被执行。示例如下所示:

1
2
3
4
5
6
7
$ diskusage () { df -h ; }
译注:下面是我给的shell函数语法,原文没有
[ function ] 函数名 [()]
{
命令;
[return int;]
}

如何让 Shell 就脚本得到来自终端的输入?

read 命令可以读取来自终端(使用键盘)的数据。read 命令得到用户的输入并置于你给出的变量中。例子如下:

1
2
3
4
5
6
7
8
9
## vi /tmp/test.sh
#!/bin/bash
echo ‘Please enter your name’
read name
echo “My Name is $name
## ./test.sh
Please enter your name
ThinkWon
My Name is ThinkWon

如何执行算术运算?

有两种方法来执行算术运算:

  • 1、使用 expr 命令:## expr 5 + 2
  • 2、用一个美元符号和方括号($[ 表达式 ]):test=$[16 + 4] ; test=$[16 + 4]

编程题

判断一文件是不是字符设备文件,如果是将其拷贝到 /dev 目录下?

1
2
3
4
5
#!/bin/bash
read -p "Input file name: " FILENAME
if [ -c "$FILENAME" ];then
  cp $FILENAME /dev
fi

添加一个新组为 class1 ,然后添加属于这个组的 30 个用户,用户名的形式为 stdxx ,其中 xx 从 01 到 30 ?

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
groupadd class1
for((i=1;i<31;i++))
do
if [ $i -le 10 ];then
useradd -g class1 std0$i
else
useradd -g class1 std$i
fi
done

编写 Shell 程序,实现自动删除 50 个账号的功能,账号名为stud1 至 stud50 ?

1
2
3
4
5
#!/bin/bash
for((i=1;i<51;i++))
do
userdel -r stud$i
done

写一个 sed 命令,修改 /tmp/input.txt 文件的内容?

要求:

  • 删除所有空行。
  • 一行中,如果包含 “11111”,则在 “11111” 前面插入 “AAA”,在 “11111” 后面插入 “BBB” 。比如:将内容为 0000111112222 的一行改为 0000AAA11111BBB2222 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@~]## cat -n /tmp/input.txt
1 000011111222
2
3 000011111222222
4 11111000000222
5
6
7 111111111111122222222222
8 2211111111
9 112222222
10 1122
11

## 删除所有空行命令
[root@~]## sed '/^$/d' /tmp/input.txt
000011111222
000011111222222
11111000000222
111111111111122222222222
2211111111
112222222
1122

## 插入指定的字符
[root@~]## sed 's#\(11111\)#AAA\1BBB#g' /tmp/input.txt
0000AAA11111BBB222
0000AAA11111BBB222222
AAA11111BBB000000222
AAA11111BBBAAA11111BBB11122222222222
22AAA11111BBB111
112222222
1122

一、工作中你都写过什么脚本?

1、监控脚本(监控系统、监控服务、监控硬件信息、监控性能、安全监控等)

2、系统初始化脚本(创建目录,创建账户,安装软件包,设置权限,修改内核参数等)

一键部署(源码安装脚本)

3、备份脚本(自动备份数据库,备份网站数据,备份日志,备份配置文件等)

4、日志分析脚本(分析日志数据,汇总并统计相关信息,如 PV、UV 等)

二、如何获取一个文件每一行的第三个元素?

# awk ‘{print $3}’ 文件名

备注:awk 支持按列输出,通过内置变量$1,$2,$3…可以单独显示任意列,默认列是以

空格或 Tab 缩进为分隔符,也可以使用-F 选项指定其他分隔符。

三、 shell 函数能解决什么实际问题?

定义函数的格式:

function 函数名{

代码块

}

函数名(){

代码块

}

使用函数可以避免代码重复

使用函数可以将大的工程分割为若干小的功能模块,代码的可读性更强

四、使用 awk 统计 httpd 访问日志中每个客户端 IP 的出现次数?

# awk ‘{ip[$1]++}END{for(i in ip){print ip[i],i}}’ /var/log/httpd/access_log

备注:定义数组,数组名称为 ip,数字的下标为日志文件的第 1 列(也就是客户端的 I

P 地址)

,++的目的在于对客户端进行统计计数,客户端 IP 出现一次计数器就加 1。END

中的指令在读取完文件后执行,通过循环将所有统计信息输出。

4、哪些方式可以将标准输出和错误输出重定向到文件?

答案:

# 命令 &> 文件名

# 命令 > 文件名 1 2> 文件名 2

# 命令 > 文件名 2>&1

# 命令 &>> 文件名

# 命令 >> 文件名 1 2>> 文件名 2

# 命令 >> 文件名 2>&1

五、正则表达式符号: *、+、?、[]、[^]、{n}分别代表什么含义?

*表示匹配前面的字符出现了任意次(包括 0 次)

+表示匹配前面的字符出现了至少 1 次(1 次或多次)

?表示匹配前面的字符出现了 0 次或 1 次

[]表示集合,匹配集合中的任意单个字符

[^]表示对集合取反

{n}表示精确匹配前面的字符出现了 n 次

六、shell 中对变量字串进行截取的方式有哪些?

# echo ${变量名:开始位置:长度} #注意,起始位置从 0 开始

# expr substr $变量名 #注意,起始位置从 1 开始

# echo $变量名 |

开始位置 长度

cut -b 开始位置-结束位置 #注意,起始位置从 1 开始

\7. 使用 sed 命令如何将文件中所有的大写字母 Q 转换为小写字母 q?

答案:

# sed -i ‘s/Q/q/g’ 文件名

七、编写脚本,用户输入密码,脚本判断密码是否正确,输入正确则提示正确,连续输入错误密码 3 次,则报警?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vim test.sh

#!/bin/bash

init=123456

for I in {1..3}

do

read -p "请输入密码:" pass

if [ $pass == $init ];then

echo "密码正确"

break

fi

done

echo "警告:密码错误"

八、编写脚本,自动生成一个 8 位随机密码?

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vim test.sh

#!/bin/bash

Str="abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678

9"

pass=""

for i in {1..8}

do

num=$[RANDOM%${#Str}]tmp=${Str:num:1}

pass+=$tmp

done

echo $pass

九、递归函数,遍历目录

递归遍历目录

通过定义递归函数 files来实现

Shell也可以实现递归函数,就是可以调用自己本身的函数。在Linux系统上编写Shel脚本的时候,经常需要递归遍历系统的目录,列出目录下的文件和目录,逐层递归列出,并对这些层级关系进行展示。具体的实现过程如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function list files()

{

for f in `ls $1`;

do

if [-d"$1/Sf"]: then

echo"$2$f"

list_files "$1/$2" " $2"

else

echo "$2$"

fi

done

}

list_files "/var/log" "

十、shell实现nginx日志自动切割

脚本思路【按天分割日志】

a、获取昨天的日期(date -d yesterday +%Y%m%d),用来作为分割后日志的名称

b、将源日志文件移动到新的nohuplogs文件夹里,并按时间重命名

c、在源日志文件夹(logs)里新建默认日志文件(access.log)

d、给nginx一个信号量,重新打开日志

f、设置一个定时任务,定时执行日志切割的脚本

操作步骤

a、新建日志分割的文件夹nohuplogs(mkdir /jboss/nginx/nohuplogs)

b、编写脚本,暂且命名为:splitlogs.sh吧,脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
LOGPATH=/jboss/nginx/logs/access.log

BASEPATH=/jboss/nginx/nohuplogs

LOGBAK=$BASEPATH/$(date -d yesterday +%Y%m%d).log

#echo $LOGBAK

mv $LOGPATH $LOGBAK

touch $LOGPATH

kill -USR1 `cat /jboss/nginx/logs/nginx.pid

`

c、配置定时任务 crontab -e,如下图,增加日志分割的脚本,每天晚上23点59分切割

59 23 * * * sh /jboss/nginx/splitlogs.sh

十一、冒泡算法

请结合使用冒泡排序方法把 123.txt 文件中的数字按照降序排序输出在一行当中,并要求没有重复数字。(20分)

cat 123.txt

1 4 7 9

2 5 8 3

3 6 9 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

myarray() {

array=(`echo $@`)

\#echo ${array[*]}

a=${#array[*]}

for ((i=1; i<$a; i++))

do

for ((j=0; j<$a-i; j++))

do

if [ ${array[$j]} -lt ${array[$[$j + 1]]} ];then

temp=${array[$j]}

array[$j]=${array[$[$j+1]]}

array[$[$j+1]]=$temp

fi



done

done

echo ${array[*]}

}

###主体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
list=`for i in $(cat 123.txt); do echo $i; done | sort -n | uniq`

a=0



for value in $list

do

arr[$a]=$value

let a++

done



result=`myarray ${arr[*]}`

echo “排序后数组为:$result”

监控网站状态是否正常,异常发邮件

题目要求:

写一个shell脚本,通过curl -I 返回状态码来判定所访问的网站是否正常,比如当代码状态200,才算正常

写一个发邮件的脚本

习题分析:

1、关键问题,截取出代码状态

2、在写出该shell脚本时,应该先在命令下面使用curl -I http://www.51xit.top/命令测试,然后通过awk截取到状态码

3、写发邮件的脚本,用的是sendEmail。生产环境有配套的模板

4、判断和发邮件关联

curl -I http://www.51xit.top/

我们抓包会有交互信息 200

###创建触发器及邮件报警测试##

【安装邮件组件】

[root@tang ~]# wget http://caspian.dotconf.net/menu/Software/SendEmail/sendEmail-v1.56.tar.gz

[root@tang ~]# tar -zxvf sendEmail-v1.56.tar.gz

[root@tang ~]# cp sendEmail-v1.56/sendEmail /usr/local/bin/

[root@tang ~]# chmod 755 /usr/local/bin/sendEmail

[root@tang ~]# vi /opt/sendEmail.sh

#!/bin/bash

#

# Filename: SendEmail.sh

# Revision: 1.0

# Date: 2019/05/29

# Author: Qicheng

# Email:

# Website: http://51xit.top/

# Description: tang邮件告警脚本

# Notes: 使用sendEmail

#

# 脚本的日志文件

LOGFILE=”/tmp/Email.log”

:>”$LOGFILE”

exec 1>”$LOGFILE”

exec 2>&1

SMTP_server=’smtp.qq.com’ # SMTP服务器,变量值需要自行修改

username=‘1581273154@qq.com‘ # 用户名,变量值需要自行修改

password=’cgdxhxqddtrafijh’ # 密码(QQ邮箱用的是授权码),变量值需要自行修改

from_email_address=‘1581273154@qq.com‘ #### 发件人Email地址,变量值需要自行修改

to_email_address=”$1” # 收件人Email地址,tang传入的第一个参数

message_subject_utf8=”$2” # 邮件标题,tang传入的第二个参数

message_body_utf8=”$3” # 邮件内容,tang传入的第三个参数

# 转换邮件标题为GB2312,解决邮件标题含有中文,收到邮件显示乱码的问题。

message_subject_gb2312=`iconv -t GB2312 -f UTF-8 << EOF

$message_subject_utf8

EOF`

[ $? -eq 0 ] && message_subject=”$message_subject_gb2312” || message_subject=”$message_subject_utf8”

# 转换邮件内容为GB2312,解决收到邮件内容乱码

message_body_gb2312=`iconv -t GB2312 -f UTF-8 << EOF

$message_body_utf8

EOF`

[ $? -eq 0 ] && message_body=”$message_body_gb2312” || message_body=”$message_body_utf8”

# 发送邮件

sendEmail=’/usr/local/bin/sendEmail’

set -x

$sendEmail -s “$SMTP_server” -xu “$username” -xp “$password” -f “$from_email_address” -t “$to_email_address” -u “$message_subject” -m “$message_body” -o message-content-type=text -o message-charset=gb2312

[root@tang ~]# chmod +x /opt/sendEmail.sh

[root@tang ~]# /opt/sendEmail.sh 1581273154@qq.com 测试 测试

监控磁盘情况,异常发邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

disk_sda1=df -h |sed -n '3p'|awk '{print $4}'|cut -f 1 -d '%'

if

((disk_sda1 > 80));

then

echo “this is error”

echo date “192.168.56.128 this is over 70%” |mail -s “disk over 70%” 12345300@qq.com,5645645@qq.com

else

echo “this is ok”

fi

计划任务:

1
[root@localhost tmp]# crontab -e

添加

1
2
3
4
5
6
7
3个小时检查一次

\* */3 * * * /var/tmp/check_disk.sh

或者十分钟检查一次

*/10 * * * * /var/tmp/check_disk.sh

十二、特殊符号

$0:当前脚本的文件名

$n:第n个位置参数

$*:传递给脚本或函数的所有参数,$*会将这些参数视为一个整体

$@:传递给脚本或函数的所有参数,$@会将所有参数当作同一字符串中的多个独立的单词

$#:脚本运行时携带的参数个数

$*:表示所有位置参数的内容

$?:最近一个命令的退出状态码

$$:当前shell的进程ID(PID)

$!:最近一个后台命令的PID

!!:执行上一条命令

IFS:内部字段分隔符,IFS环境变量定义了shell用作字段分隔符的一系列字符。默认情况下,shell会将下列字符当做字段分隔符:

  • 空格
  • 制表符
  • 换行符

&>:将STDERR和STDOUT的输出重定向到同一个输出文件

&-:要关闭文件描述符,可以将它重定向到特殊符号&-

.:点操作符,点操作符是source命令的别名,它会在shell上下文中执行点操作符指定的脚本,而不是创建一个新的shell。

[ 操作符 文件或目录 ]

常用的测试操作符

-d:测试是否为目录(Directory)

-e:测试目录或文件是否存在(Exist)

-f:测试是否为文件(File)

-r:测试当前用户是否有权限读取(Read)

-w:测试当前用户是否有权限写入(Write)

-x:测试当前用户是否有权限执行(eXcute)