文章目录
- [1. 如何调试 Shell 脚本?](#1. 如何调试 Shell 脚本?)
- [2. 如何在 Shell 脚本中传递参数?](#2. 如何在 Shell 脚本中传递参数?)
- [3. Shell 中exec跟source差在哪?](#3. Shell 中exec跟source差在哪?)
- [4. 如何让 Shell 就脚本得到来自终端的输入?](#4. 如何让 Shell 就脚本得到来自终端的输入?)
- [5. 什么是Shell函数](#5. 什么是Shell函数)
- [6. var=value? export前后差在哪?](#6. var=value? export前后差在哪?)
- [7. Bash Shell 中 $! 表示什么意思](#7. Bash Shell 中 $! 表示什么意思)
- [8. Bash $\* 和 $@ 有什么区别](#8. Bash $* 和 $@ 有什么区别)
- [9. 查找某个路径下的所有包含某个特定字符串的文件(递归查找)](#9. 查找某个路径下的所有包含某个特定字符串的文件(递归查找))
- [10. 写一个脚本实现判断192.168.1.0/24网络里,当前在线的IP有哪些,能ping通则认为在线](#10. 写一个脚本实现判断192.168.1.0/24网络里,当前在线的IP有哪些,能ping通则认为在线)
- [11. Shell 添加一个新组为class1,添加属于这个组的30个用户,用户名的形式为stdxx,其中xx从01 到30?](#11. Shell 添加一个新组为class1,添加属于这个组的30个用户,用户名的形式为stdxx,其中xx从01 到30?)
- [12. 某系统管理员需每天做一定的重复工作,请按照下列要求,编制一个解决方案?](#12. 某系统管理员需每天做一定的重复工作,请按照下列要求,编制一个解决方案?)
- [13. Linux shell中单引号、双引号及不加引号的简单区别](#13. Linux shell中单引号、双引号及不加引号的简单区别)
- [14. 编写Shell脚本获取本机网络地址 如:本机的IP是:192.168.100.2/255.255.255.0,那么它的网络地址是 ?](#14. 编写Shell脚本获取本机网络地址 如:本机的IP是:192.168.100.2/255.255.255.0,那么它的网络地址是 ?)
- [15. 添加一个新组为 class1 ,然后添加属于这个组的 30 个用户,用户名的形式为 stdxx ,其中 xx 从 01 到 30](#15. 添加一个新组为 class1 ,然后添加属于这个组的 30 个用户,用户名的形式为 stdxx ,其中 xx 从 01 到 30)
- [16. 写一个 sed 命令,修改 /tmp/input.txt 文件的内容?](#16. 写一个 sed 命令,修改 /tmp/input.txt 文件的内容?)
- [17. 编写个 shell 脚本将/usr/local/test 目录下大于 100K 的文件转移到/tmp 目录下](#17. 编写个 shell 脚本将/usr/local/test 目录下大于 100K 的文件转移到/tmp 目录下)
- [18. 编写以下命令执行达到预期效果: 查找linux系统下以txt结尾,30天没有修改的文件大小大于20K同时具有执行权限的文件并备份到/data/backup/目录下](#18. 编写以下命令执行达到预期效果: 查找linux系统下以txt结尾,30天没有修改的文件大小大于20K同时具有执行权限的文件并备份到/data/backup/目录下)
- [19. 查看/web.log第25行第三列的内容](#19. 查看/web.log第25行第三列的内容)
- [20. 编写个shell脚本将当前目录下大于10K的文件转移到/tmp目录下 ?](#20. 编写个shell脚本将当前目录下大于10K的文件转移到/tmp目录下 ?)
- [21. 编写Shell显示file.txt的1,3,5,7,10,15行?](#21. 编写Shell显示file.txt的1,3,5,7,10,15行?)
- [22. Bash 与 Dash 的区别?](#22. Bash 与 Dash 的区别?)
- [23. 编写Shell找出系统内大于50k,小于100k的文件,并删除它们 ?](#23. 编写Shell找出系统内大于50k,小于100k的文件,并删除它们 ?)
- [24. 编写Shell 脚本:目录dir1、dir2、dir3下分别有file1、file2、file2,请使用脚本将文件改为dir1_file1、dir2_file2、dir3_file3?](#24. 编写Shell 脚本:目录dir1、dir2、dir3下分别有file1、file2、file2,请使用脚本将文件改为dir1_file1、dir2_file2、dir3_file3?)
- [25. Shell ( ) 与 { } 区别在哪?](#25. Shell ( ) 与 { } 区别在哪?)
- [26. 在 Shell 编程中,()、$() 和 {}区别在哪?](#26. 在 Shell 编程中,()、() 和 ${}区别在哪?)
- [27. shell命令: name=John && echo 'My name is $name' 的输出是什么?](#27. shell命令: name=John && echo ‘My name is $name’ 的输出是什么?)
- [28. 如何使用 awk 列出 UID 小于 100 的用户](#28. 如何使用 awk 列出 UID 小于 100 的用户)
- [29. ${variable:-10} 和 ${variable: -10} 有什么区别?](#29. ${variable:-10} 和 ${variable: -10} 有什么区别?)
- [30. "export $variable" 或 "export variable" 哪个正确](#30. “export $variable” 或 “export variable” 哪个正确)
- [31. 列出当前目录下第二个字母是 a 或 b 的文件](#31. 列出当前目录下第二个字母是 a 或 b 的文件)
- [32. Shell 如何去除字符串中的所有空格 ?](#32. Shell 如何去除字符串中的所有空格 ?)
- [33. Shell 写出输出数字 0 到 100 中 3 的倍数(0 3 6 9 ...)的命令 ?](#33. Shell 写出输出数字 0 到 100 中 3 的倍数(0 3 6 9 …)的命令 ?)
- [34. [ $a == $b \] 和 [ $a -eq $b ] 有什么区别?](#34. [ $a == $b ] 和 [ $a -eq $b ] 有什么区别?)
- [35. [[ $string == abc\* \]] 和 [[ $string == "abc\*" ]] 有什么区别](#35. [[ $string == abc* ]] 和 [[ $string == “abc*” ]] 有什么区别)
- [36. Shell脚本如何实现监控iptables运行状态](#36. Shell脚本如何实现监控iptables运行状态)
- [37. 使用 tcpdump 嗅探 80 端口的访问看看谁最高](#37. 使用 tcpdump 嗅探 80 端口的访问看看谁最高)
- [38. 编写Shell代码实现以下逻辑:仅开放本机两个IP地址中的一个地址172.16.0.X上绑定的sshd和vsftpd服务给172.16.0.0/16网络中除了172.16.0.0/24网络中的主机之外的所有主机,但允许172.16.0.200访问,每次的用户访问都要记录于日志文件中,注:其中X为学号](#38. 编写Shell代码实现以下逻辑:仅开放本机两个IP地址中的一个地址172.16.0.X上绑定的sshd和vsftpd服务给172.16.0.0/16网络中除了172.16.0.0/24网络中的主机之外的所有主机,但允许172.16.0.200访问,每次的用户访问都要记录于日志文件中,注:其中X为学号)
- [39. 编写Shell代码,实现以下逻辑:编写脚本/root/bin/checkip.sh,每5分钟检查一次,如果发现通过ssh登录失败次数超过10次,自动将此远程IP放入Tcp Wrapper的黑名单中予以禁止防问](#39. 编写Shell代码,实现以下逻辑:编写脚本/root/bin/checkip.sh,每5分钟检查一次,如果发现通过ssh登录失败次数超过10次,自动将此远程IP放入Tcp Wrapper的黑名单中予以禁止防问)
- [40. 写一个脚本/root/mysqlbak.sh,备份mysql数据库,打成tar包放到/data/下,以备份时间命名,并只保留最近的2个tar包,做一个定时任务,每个月第一个周六的00:01执行/root/mysqlbak.sh](#40. 写一个脚本/root/mysqlbak.sh,备份mysql数据库,打成tar包放到/data/下,以备份时间命名,并只保留最近的2个tar包,做一个定时任务,每个月第一个周六的00:01执行/root/mysqlbak.sh)
1. 如何调试 Shell 脚本?
-
使用
-x
选项打印每条命令的执行过程,有助于跟踪脚本的执行流程。 -
在脚本内部使用
set -x
和set +x
来动态开启和关闭调试模式。 -
利用
trap
命令捕获DEBUG
信号,在每个命令执行前执行自定义的调试代码。 -
使用
-v
选项逐行打印出脚本中的命令,方便理解脚本的流程。
2. 如何在 Shell 脚本中传递参数?
在 Shell 脚本中传递参数是非常常见的需求,Shell 提供了几种常见的方式:
- 位置参数:
- 脚本后面跟的参数会被依次存储在
$1
、$2
、$3
等变量中。 - 可以通过
$#
获取参数的个数。
- 脚本后面跟的参数会被依次存储在
例如:
bash
#!/bin/bash
echo "第一个参数是: $1"
echo "第二个参数是: $2"
echo "总共有 $# 个参数"
运行时: ./script.sh hello world
- 命令行选项:
- 使用
getopts
命令解析带选项的参数。 - 可以定义必选和可选参数,并设置默认值。
- 使用
例如:
bash
#!/bin/bash
while getopts ":n:p:" opt; do
case $opt in
n) name=$OPTARG;;
p) port=$OPTARG;;
\?) echo "Invalid option: -$OPTARG" >&2
exit 1;;
esac
done
echo "Name: $name"
echo "Port: $port"
运行时: ./script.sh -n Alice -p 8080
- 数组形式:
- 将所有参数存储在数组
$@
中。 - 通过
${@}
或"$@"
访问数组中的元素。
- 将所有参数存储在数组
例如:
bash
#!/bin/bash
params=("$@")
echo "第一个参数是: ${params[0]}"
echo "第二个参数是: ${params[1]}"
echo "总共有 ${#params[@]} 个参数"
运行时: ./script.sh hello world foo bar
3. Shell 中exec跟source差在哪?
Shell 中的 exec
和 source
命令有以下区别:
-
作用不同:
exec
命令是用来替换当前进程的,它会直接执行指定的命令,并替换掉当前的 Shell 进程。source
命令是用来执行指定的脚本文件,并将其中的命令在当前 Shell 进程中执行。
-
进程关系:
exec
会终止当前进程,并启动新的进程来执行命令。执行完毕后,当前 Shell 进程就结束了。source
只是在当前 Shell 进程中执行指定脚本的命令,不会创建新的进程。
-
返回值:
exec
命令不返回任何值,因为当前进程已经被替换掉了。source
命令会返回被执行脚本中最后一条命令的退出状态码。
-
应用场景:
exec
通常用于在 Shell 脚本中替换掉当前进程,比如退出脚本或切换到其他程序。source
通常用于在 Shell 脚本中引入外部的配置文件或库文件,以便使用其中定义的变量和函数。
总之,exec
是用于进程替换,source
是用于在当前 Shell 进程中执行外部脚本。它们在使用场景上有所不同,开发者需要根据具体需求选择合适的命令。
4. 如何让 Shell 就脚本得到来自终端的输入?
在 Shell 脚本中获取来自终端的输入有以下几种常见方式:
-
read
命令:-
read
命令可以将用户在终端输入的内容存储到指定的变量中。 -
例如:
bashecho "请输入您的名字: " read name echo "您好, $name!"
-
-
使用管道符
|
:-
可以将其他命令的输出作为脚本的输入,如
cat
、grep
等。 -
例如:
bashecho "请输入一些文字: " input=$(cat -) echo "您输入的内容是: $input"
-
-
使用
$()
或反引号 ````` 捕获命令输出:-
可以将其他命令的输出赋值给变量。
-
例如:
bashecho "请输入一个数字: " num=$(read) echo "您输入的数字是: $num"
-
-
读取环境变量:
-
可以通过读取环境变量来获取用户输入的值。
-
例如:
bashecho "请输入您的用户名: " username=$USER echo "您的用户名是: $username"
-
以上几种方式各有优缺点,可以根据具体需求选择合适的方式。比如 read
命令比较直观,但无法处理包含空格的输入;使用管道则更灵活,但需要额外的命令。开发者可以根据实际情况选择合适的方式。
5. 什么是Shell函数
Shell 函数是 Shell 脚本中的一种重要概念,它允许开发者将一组命令组合成一个可重复使用的单元。
Shell 函数的基本语法如下:
bash
function_name() {
# function body
commands
return [n]
}
其中:
function_name
是函数的名称,遵循与变量名相同的命名规则。commands
是函数体中要执行的一系列命令。return [n]
是可选的,用于返回一个整数值作为函数的退出状态码。
使用 Shell 函数主要有以下优点:
- 代码复用:将常用的命令组织成函数,可以在脚本中多次调用,提高代码的可重用性。
- 代码组织:使用函数可以将脚本拆分成更小的模块,提高代码的可读性和可维护性。
- 参数传递:函数可以接受参数,并在函数体内使用这些参数。
- 返回值 :函数可以使用
return
语句返回一个整数值,以便调用者获取执行结果。
下面是一个简单的 Shell 函数示例:
bash
# 定义一个打印 hello world 的函数
hello_world() {
echo "Hello, World!"
return 0
}
# 调用该函数
hello_world
运行这个脚本,会在终端输出 "Hello, World!"。这就是一个简单的 Shell 函数的使用示例。
6. var=value? export前后差在哪?
在 Shell 脚本中,赋值给变量的 var=value
和使用 export
命令之间有以下几个主要区别:
-
作用域不同:
var=value
定义的变量是局部变量,只在当前 Shell 会话或脚本中有效。export var=value
定义的变量是环境变量,会被传递给当前 Shell 会话及其子进程。
-
继承性:
- 使用
var=value
定义的局部变量,只能在当前 Shell 脚本或进程中使用,无法被子进程继承。 - 使用
export var=value
定义的环境变量,可以被子进程继承和使用。
- 使用
-
优先级:
- 当同时存在同名的局部变量和环境变量时,局部变量优先级更高,会覆盖环境变量的值。
-
持久性:
- 使用
var=value
定义的局部变量,仅在当前 Shell 会话或脚本中有效,退出后就会丢失。 - 使用
export var=value
定义的环境变量,会一直保存在系统环境中,直到被手动删除或 Shell 会话结束。
- 使用
例如:
bash
# 定义一个局部变量
var=hello
# 输出变量值
echo $var # 输出 hello
# 声明为环境变量
export var=world
# 再次输出变量值
echo $var # 输出 world
# 启动子 Shell
bash
echo $var # 输出 world(子 Shell 可以访问环境变量)
使用 var=value
定义变量是为了在当前 Shell 会话或脚本中使用,而 export
命令则是为了将变量设置为环境变量,使其能够被子进程继承和访问。根据具体需求选择合适的方式是很重要的。
7. Bash Shell 中 $! 表示什么意思
Bash Shell 中的 $!
表示最后一个后台运行进程的进程 ID (PID)。
这里需要解释一下后台运行进程的概念:
在 Bash 中,可以使用 &
符号将一个命令或进程放到后台执行。这样就可以继续在当前 Shell 中执行其他命令,而不必等待该后台进程完成。
例如:
bash
# 将一个长时间运行的命令放到后台执行
sleep 100 &
# 输出最后一个后台进程的 PID
echo $!
上面的例子中,sleep 100 &
会在后台执行 100 秒,同时 $!
会输出这个后台进程的 PID。
$!
的一些常见用法包括:
- 在脚本中监控后台进程的状态和输出
- 在出现错误时杀掉最后一个后台进程
- 将后台进程的 PID 存储起来以便后续使用
8. Bash $* 和 $@ 有什么区别
Bash 中的 $*
和 $@
都是用来表示脚本或函数接收到的参数的,但它们之间存在一些差异:
-
引用方式的区别:
$*
将所有参数视为一个整体,并将其作为一个字符串返回。$@
将每个参数视为独立的字符串,并将它们作为一个数组返回。
-
处理空格的方式不同:
$*
会将所有参数连接成一个字符串,中间用空格分隔。如果参数中包含空格,则无法正确区分。$@
会将每个参数作为独立的字符串返回,即使参数中包含空格也不会被连在一起。
-
引号包围的差异:
"$*"
会将所有参数视为一个整体,并将其作为一个双引号包围的字符串返回。"$@"
会将每个参数视为独立的字符串,并将它们放在双引号中返回。
下面是一个例子:
bash
#!/bin/bash
# 传递参数: "hello world" "foo bar"
echo "\$*: $*" # 输出: hello world foo bar
echo "\"\$*\": \"$*\"" # 输出: "hello world foo bar"
echo "\$@: $@" # 输出: "hello world" "foo bar"
echo "\"\$@\": \"$@\"" # 输出: "hello world" "foo bar"
总之,$*
和 $@
都是用来获取脚本或函数的参数,但在处理参数中的空格时有不同的表现。通常情况下,建议使用 "$@"
的方式,因为它可以更好地保留参数的原始形式。
9. 查找某个路径下的所有包含某个特定字符串的文件(递归查找)
在 Bash 中,可以使用 grep
命令结合 find
命令来递归查找包含特定字符串的文件。
命令语法如下:
bash
find <路径> -type f -exec grep -l '<字符串>' {} \;
其中:
<路径>
: 要递归查找的目录路径。-type f
: 表示只查找普通文件,不包括目录。-exec grep -l '<字符串>' {} \;
: 对找到的每个文件执行grep -l '<字符串>'
命令,输出包含指定字符串的文件路径。
例如,在当前目录及其子目录中查找包含 "hello" 字符串的所有文件:
bash
find . -type f -exec grep -l 'hello' {} \;
上述命令会输出所有包含 "hello" 字符串的文件路径。
如果需要更加灵活的查找,还可以结合其他选项:
-name '<文件名模式>'
: 按文件名过滤-size <大小>
: 按文件大小过滤-mtime <天数>
: 按文件修改时间过滤
例如,查找当前目录及其子目录中,最近 7 天内修改的 .txt 文件,并查找其中包含 "foo" 字符串的文件:
bash
find . -type f -name '*.txt' -mtime -7 -exec grep -l 'foo' {} \;
通过灵活组合 find
和 grep
命令,可以满足各种复杂的文件查找需求。
10. 写一个脚本实现判断192.168.1.0/24网络里,当前在线的IP有哪些,能ping通则认为在线
要实现这个功能,我们可以编写一个简单的 Bash 脚本,使用 ping 命令逐个检查指定网络中的 IP 地址是否在线。以下是一个基本的脚本示例,它遍历 192.168.1.0/24 网络中的每个 IP 地址(从 192.168.1.1 到 192.168.1.254),使用 ping 命令发送少量的 ICMP Echo 请求以判断主机是否在线。
bash
#!/bin/bash
# 网络的基础部分
base_ip="192.168.1"
# 循环遍历 1 到 254 的每个可能的主机号
for i in {1..254}; do
# 构造完整的 IP 地址
ip="base_ip.i"
# 使用 ping 命令检查 IP 地址
# -c 1 表示发送一个 ICMP Echo 请求
# -W 1 表示等待回应的最大时间(秒)
ping -c 1 -W 1 ip &> /dev/null
# 检查 ping 命令的退出状态
if [? -eq 0 ]; then
echo "IP 地址 $ip 在线"
fi
done
这个脚本的工作流程如下:
- 定义了网络的基础 IP 部分(
base_ip
)。 - 使用
for
循环遍历网络中的每个主机号(从 1 到 254)。 - 对于循环中的每个数字,构造完整的 IP 地址,并使用 ping 命令尝试向该地址发送 ICMP Echo 请求。
ping
命令的选项-c 1
指定发送请求数量为1
,-W 1
设置等待回应的最大时间为 1 秒。这样做是为了加快脚本的执行速度。- 通过检查 ping 命令的退出状态(
$?
),判断主机是否在线。如果退出状态为 0(表示成功),则认为该 IP 地址在线,并打印相应的消息。
请注意,这个脚本可能需要管理员权限(或使用 sudo
)来运行,因为 ping
命令在某些系统上可能需要这些权限。此外,这种方法可能不会检测到配置为不响应 ICMP Echo 请求的主机。
11. Shell 添加一个新组为class1,添加属于这个组的30个用户,用户名的形式为stdxx,其中xx从01 到30?
好的,我来帮你实现这个任务。在 Shell 脚本中,我们可以使用以下步骤来完成:
-
创建一个名为
class1
的新组:bashsudo groupadd class1
-
使用
for
循环创建 30 个用户,用户名形式为std01
到std30
,并将它们添加到class1
组中:bashfor i in {01..30}; do username="std$i" sudo useradd -g class1 $username done
这个循环会创建 30 个用户,用户名从
std01
到std30
,并将它们添加到class1
组中。
整个脚本如下所示:
bash
#!/bin/bash
# 创建 class1 组
sudo groupadd class1
# 创建 30 个用户并添加到 class1 组
for i in {01..30}; do
username="std$i"
sudo useradd -g class1 $username
done
echo "添加新用户到 class1 组完成!"
保存这个脚本,然后使用 chmod +x script.sh
赋予执行权限,最后运行 ./script.sh
即可完成任务。
这个脚本会在系统中创建一个名为 class1
的新组,然后创建 30 个用户,用户名从 std01
到 std30
,并将它们全部添加到 class1
组中。
12. 某系统管理员需每天做一定的重复工作,请按照下列要求,编制一个解决方案?
要自动化和优化系统管理员的重复工作,我们可以利用 Linux 系统中的 cron 作业来安排这些任务定时执行。不过,我将提供一个稍微不同的角度来解决这个问题,增加一些解释和改进,使解决方案更加健壮和灵活。
- 创建 cron 作业
首先,我们将编写一个 cron 文件来定义所有定时任务。我们可以直接编辑 crontab,而不是首先创建一个外部文件。这样可以确保 crontab 的内容始终与系统的 cron 守护进程同步。
打开终端并输入以下命令编辑当前用户的 crontab 文件(如果需要以超级用户权限执行,可以在命令前加 sudo):
bash
crontab -e
然后,在打开的编辑器中添加以下内容:
bash
# 每天16:50清理 /abc 目录下的所有文件和文件夹
50 16 * * * rm -rf /abc/*
# 每小时备份 /xyz/x1 文件的第一列到 /backup/bak01.txt,从早上8点到晚上18点,每小时执行一次
0 8-18 * * * cut -f1 /xyz/x1 >> /backup/bak01.txt
# 每天17:50对 /data 目录进行压缩备份
50 17 * * * tar zcvf /path/to/backup/backup.tar.gz /data
# 每天17:55卸载 /dev/hdc
55 17 * * * umount /dev/hdc
- 解释和改进
- 使用
crontab -e
直接编辑:这样做的好处是可以避免权限问题,且 cron 服务会自动加载新的配置,无需手动加载。 - 路径注意事项:在 cron 作业中使用全路径(例如,使用 /usr/bin/cut 而不是 cut),因为 cron 的环境变量可能与你的交互式 shell 环境不同。
- 日志记录:对于每个任务,特别是可能失败的任务(如
umount
),考虑添加日志记录,例如:umount /dev/hdc >> /var/log/umount.log 2>&1
,以便于问题追踪。 - 错误处理:为了确保任务的健壮性,可以在命令中添加一些错误处理逻辑,或者使用脚本包装复杂的命令,并在脚本中进行错误检查。
- 自动启动 cron 服务
确保 cron 服务在系统启动时自动运行是很重要的。大多数现代 Linux 发行版默认会启动cron
服务,但你可以使用以下命令检查和启用 cron 服务(以 systemd 为例):
bash
# 检查cron服务状态
systemctl status cron
# 如果没有运行,则启动cron服务
sudo systemctl start cron
# 设置cron服务在系统启动时自动运行
sudo systemctl enable cron
通过这种方式,我们不仅自动化了重复的日常任务,还增加了日志记录和错误处理,提高了维护性和可靠性。
13. Linux shell中单引号、双引号及不加引号的简单区别
在 Linux Shell 中,单引号、双引号和不加引号之间有以下几个主要区别:
-
单引号
'
:- 单引号内的内容会被原样输出,不会进行任何变量替换或命令替换。
- 单引号内部的特殊字符如
$
、"
、\
等都会被视为普通字符,不会有特殊含义。 - 单引号内部不能嵌套其他引号。
-
双引号
"
:- 双引号内部可以进行变量替换和命令替换。
- 双引号内部的特殊字符如
$
、`````、\
等会被赋予特殊含义。 - 双引号内部可以嵌套单引号。
-
不加引号:
- 不加引号时,变量和命令替换会正常进行。
- 空格会被视为分隔符,将值拆分成多个参数。
- 特殊字符如
$
、`````、\
等会被赋予特殊含义。
下面是一些示例:
bash
# 单引号
var='$HOME'
echo '$HOME' # 输出: $HOME
echo '$((2+2))' # 输出: $((2+2))
# 双引号
var="$HOME"
echo "$HOME" # 输出: /home/user
echo "$((2+2))" # 输出: 4
echo "$var" # 输出: /home/user
# 不加引号
var=$HOME
echo $var # 输出: /home/user
echo $((2+2)) # 输出: 4
echo a b c # 输出: a b c
总的来说,单引号会原样输出内容,双引号会进行变量和命令替换,不加引号则取决于具体情况。在编写 Shell 脚本时,根据需求选择合适的引号使用方式很重要。
14. 编写Shell脚本获取本机网络地址 如:本机的IP是:192.168.100.2/255.255.255.0,那么它的网络地址是 ?
在 Linux 系统中,我们可以使用 ip
命令来获取网络接口的信息,然后通过计算得到网络地址。
以下是一个示例脚本:
bash
#!/bin/bash
# 获取默认网络接口
interface=$(ip route | grep default | awk '{print $5}')
# 获取 IP 地址和子网掩码
ip_address=$(ip addr show $interface | grep -w inet | awk '{print $2}' | cut -d'/' -f1)
netmask=$(ip addr show $interface | grep -w inet | awk '{print $2}' | cut -d'/' -f2)
# 计算网络地址
octets=(${ip_address//./ })
mask_octets=(${netmask//./ })
network_address=""
for i in {0..3}; do
network_address+=$((octets[i] & mask_octets[i]))
[ $i -lt 3 ] && network_address+="."
done
echo "本机的 IP 地址是: $ip_address/$netmask"
echo "本机的网络地址是: $network_address"
这个脚本的工作原理如下:
- 首先使用
ip route
命令获取默认网络接口,存储在$interface
变量中。 - 然后使用
ip addr show
命令获取 IP 地址和子网掩码,分别存储在$ip_address
和$netmask
变量中。 - 接下来,将 IP 地址和子网掩码分别转换为数组形式,方便后续计算。
- 使用位运算
&
对 IP 地址的每个部分与子网掩码的对应部分进行逐位与运算,得到网络地址的各部分。 - 将计算得到的各部分拼接成最终的网络地址字符串。
- 最后输出 IP 地址和网络地址。
运行这个脚本,输出类似于:
本机的 IP 地址是: 192.168.100.2/255.255.255.0
本机的网络地址是: 192.168.100.0
15. 添加一个新组为 class1 ,然后添加属于这个组的 30 个用户,用户名的形式为 stdxx ,其中 xx 从 01 到 30
为了添加一个新组 class1 并在该组下添加 30 个用户(用户名形式为 std01 到 std30),你可以执行以下 Shell 命令:
bash
groupadd class1
useradd -m -g class1 std01
useradd -m -g class1 std02
useradd -m -g class1 std03
useradd -m -g class1 std04
useradd -m -g class1 std05
useradd -m -g class1 std06
useradd -m -g class1 std07
useradd -m -g class1 std08
useradd -m -g class1 std09
useradd -m -g class1 std10
useradd -m -g class1 std11
useradd -m -g class1 std12
useradd -m -g class1 std13
useradd -m -g class1 std14
useradd -m -g class1 std15
useradd -m -g class1 std16
useradd -m -g class1 std17
useradd -m -g class1 std18
useradd -m -g class1 std19
useradd -m -g class1 std20
useradd -m -g class1 std21
useradd -m -g class1 std22
useradd -m -g class1 std23
useradd -m -g class1 std24
useradd -m -g class1 std25
useradd -m -g class1 std26
useradd -m -g class1 std27
useradd -m -g class1 std28
useradd -m -g class1 std29
useradd -m -g class1 std30
这组命令首先创建了一个名为 class1
的新用户组,然后逐个添加用户 std01
到 std30
,并将这些用户指定到 class1
组。这里的 -m
选项用于创建用户的主目录,-g
选项指定用户的初始登录组。请以 root
用户执行这些命令,或在命令前使用 sudo
来获取必要的权限。
16. 写一个 sed 命令,修改 /tmp/input.txt 文件的内容?
要求:
· 删除所有空行。
· 一行中,如果包含 "11111",则在 "11111" 前面插入 "AAA",在 "11111" 后面插入 "BBB" 。比如:将内容为 0000111112222 的一行改为 0000AAA11111BBB2222 。
好的,我来提供一个 sed 命令来实现你提到的需求:
bash
sed -i '/^$/d; s/\(.*\)11111\(.*\)/\1AAA11111BBB\2/g' /tmp/input.txt
这个 sed 命令的解释如下:
-
/^$/d
: 这部分用来删除所有空行。^$
匹配空行,d
命令用于删除匹配到的行。 -
s/\(.*\)11111\(.*\)/\1AAA11111BBB\2/g
: 这部分用于在包含 "11111" 的行中,在 "11111" 前后插入 "AAA" 和 "BBB"。\(.*\)
匹配 "11111" 前面的任意字符,并保存在第一个子模式中。11111
匹配字符串 "11111"。\(.*\)
匹配 "11111" 后面的任意字符,并保存在第二个子模式中。\1AAA11111BBB\2
表示用第一个子模式、"AAA"、"11111"、"BBB" 和第二个子模式来替换原始字符串。g
表示全局替换,即替换所有匹配到的字符串。
-
-i
选项表示直接修改文件内容,而不是输出到屏幕。
你可以将这个 sed 命令保存到一个 Shell 脚本中,然后运行该脚本来修改 /tmp/input.txt
文件。
17. 编写个 shell 脚本将/usr/local/test 目录下大于 100K 的文件转移到/tmp 目录下
将 /usr/local/test
目录下大于 100K 的文件转移到 /tmp
目录下:
bash
#!/bin/bash
# 设置要处理的源目录和目标目录
src_dir="/usr/local/test"
dst_dir="/tmp"
# 遍历源目录下的所有文件
for file in "$src_dir"/*
do
# 检查文件是否为普通文件且大小超过 100K
if [ -f "$file" ] && [ "$(du -k "$file" | cut -f1)" -gt 100 ]; then
# 移动文件到目标目录
mv "$file" "$dst_dir"
echo "已将 $file 移动到 $dst_dir 目录"
fi
done
脚本的主要步骤如下:
- 设置要处理的源目录
$src_dir
和目标目录$dst_dir
。 - 使用
for
循环遍历源目录下的所有文件。 - 对于每个文件,先检查它是否为普通文件
-f "$file"
。 - 然后使用
du -k "$file" | cut -f1
获取文件大小(单位 KB),并判断是否大于 100。 - 如果文件满足条件,则使用
mv "$file" "$dst_dir"
命令将其移动到目标目录。 - 最后输出一行日志,提示文件已被移动。
将上述脚本保存为 move_large_files.sh
,并赋予执行权限 chmod +x move_large_files.sh
。
然后运行脚本 ./move_large_files.sh
,它就会将 /usr/local/test
目录下大于 100K 的文件移动到 /tmp
目录。
注意:在运行脚本之前,请确保 /usr/local/test
目录存在,且你有权限访问和修改该目录。如果需要,可以修改脚本中的源目录和目标目录路径。
18. 编写以下命令执行达到预期效果: 查找linux系统下以txt结尾,30天没有修改的文件大小大于20K同时具有执行权限的文件并备份到/data/backup/目录下
bash
#!/bin/bash
# 设置源目录和备份目录
src_dir="/"
backup_dir="/data/backup"
# 创建备份目录
mkdir -p "$backup_dir"
# 查找符合条件的文件并备份
find "$src_dir" -type f -name "*.txt" -mtime +30 -size +20k -perm -100 | \
while read file; do
backup_file="$backup_dir/$(basename "$file")"
cp "$file" "$backup_file"
echo "已备份文件: $file -> $backup_file"
done
这个脚本的主要步骤如下:
- 设置源目录
$src_dir
为根目录/
,备份目录$backup_dir
为/data/backup
。 - 使用
mkdir -p "$backup_dir"
创建备份目录,如果目录不存在的话。 - 使用
find
命令查找符合条件的文件:-type f
表示只查找普通文件,不包括目录。-name "*.txt"
表示只查找以.txt
结尾的文件。-mtime +30
表示查找 30 天前修改的文件。-size +20k
表示查找大小大于 20K 的文件。-perm -100
表示查找具有执行权限的文件。
- 对于每个找到的文件,使用
cp "$file" "$backup_file"
命令将其备份到$backup_dir
目录下。 - 输出一行日志,提示文件已被备份。
将上述脚本保存为 backup_files.sh
,并赋予执行权限 chmod +x backup_files.sh
。
然后运行脚本 ./backup_files.sh
,它就会在 /data/backup
目录下备份所有符合条件的文件。
注意:在运行脚本之前,请确保 /data/backup
目录存在,且你有权限访问和写入该目录。如果需要,可以修改脚本中的源目录和备份目录路径。
19. 查看/web.log第25行第三列的内容
在 Linux 中,可以使用以下命令来查看 /web.log
文件的第 25 行第三列的内容:
bash
awk 'NR==25 {print $3}' /web.log
解释如下:
awk
: 这是一个强大的文本处理工具,可以用于操作和分析文本数据。NR==25
: 这个条件告诉awk
只处理第 25 行。NR
表示行号。{print $3}
: 这个动作告诉awk
打印第三个字段。在awk
中,字段默认以空格或制表符分隔。/web.log
: 这是要处理的文件路径。
如果你想查看不同行或不同列的内容,只需要修改上述命令中的行号和列号即可。例如:
- 查看第 50 行第 5 列:
awk 'NR==50 {print $5}' /web.log
- 查看第 100 行:
awk 'NR==100 {print}' /web.log
需要注意的是,如果文件不存在或你没有权限访问,命令将无法正常执行。此外,如果文件的格式不是标准的空格/制表符分隔,你可能需要修改 awk
命令中的字段分隔符。
20. 编写个shell脚本将当前目录下大于10K的文件转移到/tmp目录下 ?
bash
#!/bin/bash
# 设置要处理的目录
src_dir="."
dst_dir="/tmp"
# 创建目标目录(如果不存在)
mkdir -p "$dst_dir"
# 遍历当前目录下的所有文件
for file in "$src_dir"/*
do
# 检查文件是否为普通文件且大小超过 10K
if [ -f "$file" ] && [ "$(du -k "$file" | cut -f1)" -gt 10 ]; then
# 移动文件到目标目录
mv "$file" "$dst_dir"
echo "已将 $file 移动到 $dst_dir 目录"
fi
done
这个脚本的工作原理:
- 首先设置要处理的源目录
$src_dir
为当前目录.
,目标目录$dst_dir
为/tmp
。 - 使用
mkdir -p "$dst_dir"
创建目标目录/tmp
,如果该目录不存在的话。 - 使用
for
循环遍历当前目录下的所有文件。 - 对于每个文件,首先检查它是否为普通文件
-f "$file"
。 - 然后使用
du -k "$file" | cut -f1
获取文件大小(单位 KB),并判断是否大于 10。 - 如果文件满足条件,则使用
mv "$file" "$dst_dir"
命令将其移动到目标目录/tmp
。 - 最后输出一行日志,提示文件已被移动。
保存这个脚本为 move_large_files.sh
,并赋予执行权限 chmod +x move_large_files.sh
。
然后在当前目录下运行脚本 ./move_large_files.sh
,它就会将当前目录下大于 10K 的文件移动到 /tmp
目录。
需要注意的是,如果你需要处理其他目录下的文件,可以修改 $src_dir
变量的值。同时,如果需要调整文件大小的阈值,也可以修改 10
这个数字。
21. 编写Shell显示file.txt的1,3,5,7,10,15行?
使用以下 Shell 脚本来显示 file.txt
文件的第 1、3、5、7、10 和 15 行:
bash
#!/bin/bash
file="file.txt"
# 显示第 1 行
head -n 1 "$file"
# 显示第 3 行
head -n 3 "$file" | tail -n 1
# 显示第 5 行
head -n 5 "$file" | tail -n 1
# 显示第 7 行
head -n 7 "$file" | tail -n 1
# 显示第 10 行
head -n 10 "$file" | tail -n 1
# 显示第 15 行
head -n 15 "$file" | tail -n 1
工作原理:
- 首先设置了要处理的文件名
file="file.txt"
。 - 使用
head -n 1 "$file"
命令显示第 1 行。 - 使用
head -n 3 "$file" | tail -n 1
命令显示第 3 行。这里,先使用head
命令获取前 3 行,然后使用tail -n 1
命令输出最后一行,也就是第 3 行。 - 同样的方法,我们分别显示了第 5、7、10 和 15 行。
这个脚本可以很方便地显示指定行号的文件内容,而不需要手动计算行号或使用复杂的 awk
命令。你可以根据需要修改行号或文件名。
保存这个脚本为 show_lines.sh
,并赋予执行权限 chmod +x show_lines.sh
。
然后运行脚本 ./show_lines.sh
,它就会显示 file.txt
文件的第 1、3、5、7、10 和 15 行。
22. Bash 与 Dash 的区别?
Bash 和 Dash 都是 Linux 系统中的 Shell,但它们有一些关键区别:
-
功能和复杂度:
- Bash(Bourne Again Shell)功能强大,支持命令历史记录、自动补全、数组、字符串操作等高级功能,非常适合编写复杂的脚本。
- Dash(Debian Almquist Shell)则更轻量级,主要用于执行脚本,速度更快,但功能相对较少。
-
语法支持:
- Bash 支持更多的语法特性,比如
function
关键字、C 风格的for
循环、数组操作等。 - Dash 不支持这些高级语法,只支持基本的 POSIX 标准。
- Bash 支持更多的语法特性,比如
-
性能:
- Dash 更小巧,执行脚本速度更快,适合系统启动脚本等对性能要求高的场景。
- Bash 虽然功能多,但相对较慢,适合日常使用和复杂脚本。
-
默认 Shell:
- 在一些 Linux 发行版(如 Ubuntu)中,默认的
/bin/sh
是指向 Dash 的,而不是 Bash。这是为了提高系统启动速度。
- 在一些 Linux 发行版(如 Ubuntu)中,默认的
总结一下,如果你需要编写复杂的脚本,Bash 是更好的选择;如果你追求脚本执行速度,Dash 会更合适。
23. 编写Shell找出系统内大于50k,小于100k的文件,并删除它们 ?
bash
find /path/to/directory -type f -size +50k -size -100k -exec rm {} \;
/path/to/directory
:替换为你要查找文件的目录路径。-type f
:只查找文件。-size +50k
:查找大于 50k 的文件。-size -100k
:查找小于 100k 的文件。-exec rm {} \;
:对找到的每个文件执行rm
命令,即删除文件。
这个命令会递归地查找指定目录及其子目录中的所有符合条件的文件并删除它们。运行前建议先用 find /path/to/directory -type f -size +50k -size -100k
检查一下找到的文件,确保没有误删重要文件。
24. 编写Shell 脚本:目录dir1、dir2、dir3下分别有file1、file2、file2,请使用脚本将文件改为dir1_file1、dir2_file2、dir3_file3?
bash
#!/bin/bash
# 定义目录和文件名的数组
dirs=("dir1" "dir2" "dir3")
files=("file1" "file2" "file3")
# 遍历数组并重命名文件
for i in "${!dirs[@]}"; do
dir=${dirs[$i]}
file=${files[$i]}
if [ -f "$dir/$file" ]; then
mv "$dir/$file" "$dir/${dir}_$file"
echo "Renamed $dir/$file to $dir/${dir}_$file"
else
echo "File $dir/$file does not exist"
fi
done
- 定义了两个数组,一个存储目录名,一个存储文件名。
- 使用
for
循环遍历数组,检查每个目录下是否存在对应的文件。 - 如果文件存在,使用
mv
命令重命名文件,并输出重命名的结果。 - 如果文件不存在,输出提示信息。
将脚本保存为 rename_files.sh
,然后使用 chmod +x rename_files.sh
赋予执行权限,最后运行 ./rename_files.sh
来执行脚本。
25. Shell ( ) 与 { } 区别在哪?
在 Shell 中,( )
和 { }
有不同的用途和行为:
-
子 Shell 与当前 Shell:
-
( )
会在一个新的子 Shell 中执行括号内的命令。这意味着括号内的变量和环境不会影响到外部的 Shell 环境。例如:bash(cd /tmp; ls) pwd # 仍然是原来的目录
-
{ }
则在当前 Shell 中执行命令,不会创建新的子 Shell。括号内的变量和环境会影响到外部的 Shell 环境。例如:bash{ cd /tmp; ls; } pwd # 现在是 /tmp 目录
-
-
语法要求:
( )
内的命令可以用分号分隔,最后一个命令可以没有分号。{ }
内的命令必须用分号分隔,最后一个命令也必须有分号,并且{
和第一个命令之间需要有一个空格¹²³。
-
用途:
( )
常用于需要隔离环境的场景,比如临时改变目录或环境变量。{ }
常用于将一组命令作为一个整体执行,特别是在条件判断或循环中。
26. 在 Shell 编程中,()、$() 和 ${}区别在哪?
在 Shell 编程中,()
、$()
和 ${}
有不同的用途和行为:
-
()
:-
子 Shell :
()
用于在一个新的子 Shell 中执行括号内的命令。这样,括号内的变量和环境不会影响到外部的 Shell 环境。例如:bash(cd /tmp; ls) pwd # 仍然是原来的目录
-
-
$()
:-
命令替换 :
$()
用于命令替换,将括号内命令的输出作为一个字符串返回。例如:bashkernel_version=$(uname -r) echo "Kernel version is $kernel_version"
-
这种方式比反引号(``)更易读且支持嵌套。
-
-
${}
:-
变量替换 :
${}
用于变量替换,特别是在需要明确变量边界时。例如:bashname="world" echo "Hello, ${name}!"
-
参数扩展 :
${}
还支持一些高级的参数扩展功能,比如默认值、字符串操作等。例如:bashecho ${name:-"default"} # 如果 name 为空,则输出 "default"
-
27. shell命令: name=John && echo 'My name is $name' 的输出是什么?
在 Shell 中,单引号会阻止变量扩展,所以命令 name=John && echo 'My name is $name'
的输出是:
My name is $name
如果想让变量 $name
被替换为其值,应该使用双引号包围字符串:
bash
name=John && echo "My name is $name"
这样,输出将会是:
My name is John
28. 如何使用 awk 列出 UID 小于 100 的用户
使用 awk
命令来列出 UID 小于 100 的用户
bash
awk -F: '$3 < 100 {print $1}' /etc/passwd
-F:
:指定字段分隔符为冒号(:),因为/etc/passwd
文件中的字段是用冒号分隔的。$3 < 100 {print $1}
:表示如果第三个字段(即 UID)小于 100,则打印第一个字段(即用户名)。
这条命令会扫描 /etc/passwd
文件,并列出所有 UID 小于 100 的用户。
29. ${variable:-10} 和 ${variable: -10} 有什么区别?
这两个语法在 Bash 中有不同的用途:
-
${variable:-10}
:-
这是一个参数扩展的语法,用于提供默认值。如果
variable
未设置或为空,则返回10
。例如:bashecho ${variable:-10}
如果
variable
未设置或为空,输出将是10
。
-
-
${variable: -10}
:-
这是一个子字符串提取的语法,用于从变量中提取子字符串。这里的
-10
表示从字符串的倒数第十个字符开始提取到字符串的末尾。例如:bashvariable="Hello, World!" echo ${variable: -10}
输出将是
o, World!
,因为它从倒数第十个字符开始提取。
-
30. "export $variable" 或 "export variable" 哪个正确
在 Bash shell 中,正确的用法是 export variable
,而不是 export $variable
。
具体来说:
export variable
:这是正确的用法,用于将变量variable
导出为环境变量,使其在子进程中也可用。export $variable
:这是错误的用法,因为$variable
表示变量的值,而不是变量名。
例如:
bash
variable="hello"
export variable
这样,variable
的值 "hello"
就会被导出为环境变量。
31. 列出当前目录下第二个字母是 a 或 b 的文件
使用以下命令来列出当前目录下第二个字母是 a
或 b
的文件:
bash
ls ?[ab]*
?
匹配任意单个字符。[ab]
匹配a
或b
。*
匹配零个或多个任意字符。
因此,ls ?[ab]*
将列出当前目录下所有第二个字母是 a
或 b
的文件。
32. Shell 如何去除字符串中的所有空格 ?
使用 tr
或 sed
命令来去除字符串中的所有空格。以下是两种方法:
- 使用
tr
命令:
bash
echo "your string with spaces" | tr -d ' '
- 使用
sed
命令:
bash
echo "your string with spaces" | sed 's/ //g'
这两种方法都会将字符串中的所有空格删除。如果需要将结果存储在变量中,可以这样做:
bash
str="your string with spaces"
str_no_spaces=$(echo "$str" | tr -d ' ')
echo "$str_no_spaces"
或者使用 sed
:
bash
str="your string with spaces"
str_no_spaces=$(echo "$str" | sed 's/ //g')
echo "$str_no_spaces"
33. Shell 写出输出数字 0 到 100 中 3 的倍数(0 3 6 9 ...)的命令 ?
可以使用 seq
命令结合 xargs
来输出 0 到 100 中 3 的倍数。
bash
seq 0 3 100 | xargs
seq 0 3 100
:生成从 0 到 100 之间的数字,步长为 3。xargs
:将生成的数字以空格分隔的形式输出。
运行这个命令后,你会得到以下输出:
bash
0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99
34. [ $a == $b ] 和 [ $a -eq $b ] 有什么区别?
在 Bash shell 中,[ $a == $b ]
和 [ $a -eq $b ]
有不同的用途和适用场景:
-
字符串比较:
-
[ $a == $b ]
用于比较两个字符串是否相等。 -
例如:
basha="hello" b="hello" if [ $a == $b ]; then echo "Strings are equal" fi
-
-
整数比较:
-
[ $a -eq $b ]
用于比较两个整数是否相等。 -
例如:
basha=5 b=5 if [ $a -eq $b ]; then echo "Numbers are equal" fi
-
- 使用
==
进行字符串比较。 - 使用
-eq
进行整数比较。
35. [[ $string == abc* ]] 和 [[ $string == "abc*" ]] 有什么区别
在 shell 中,[[ $string == abc* ]]
和 [[ $string == "abc*" ]]
有明显的区别:
-
[[ $string == abc* ]]
:- 这里的
abc*
是一个模式(pattern),*
是通配符,表示匹配任意数量的字符。 - 例如,如果
$string
的值是abc123
或abcd
,这个条件都会返回true
。
- 这里的
-
[[ $string == "abc*" ]]
:- 这里的
"abc*"
是一个字符串字面量(literal string),包括了星号*
。 - 只有当
$string
的值恰好是abc*
时,这个条件才会返回true
。
- 这里的
总结来说,第一个条件用于模式匹配,而第二个条件用于精确匹配字符串。
36. Shell脚本如何实现监控iptables运行状态
使用一个简单的 Shell 脚本来监控 iptables
的运行状态,并在检测到 iptables
停止运行时重新启动它:
bash
#!/bin/bash
# 检查 iptables 服务状态的函数
check_iptables_status() {
# 使用 iptables -L 命令检查规则,如果出错,则假设 iptables 服务出现问题
if ! iptables -L > /dev/null 2>&1; then
echo "警告: iptables 服务可能未运行或配置存在问题。"
# 在这里,你可以添加发送警告通知的命令,比如使用 mail 命令发送电子邮件
else
echo "iptables 服务运行正常。"
fi
}
# 定义检查间隔(秒)
INTERVAL=60
# 主循环
while true; do
check_iptables_status
sleep $INTERVAL
done
check_iptables
函数 :使用iptables -L
命令检查iptables
是否在运行。如果命令返回非零状态码(表示失败),则重新启动iptables
。- 定时检查 :使用
while true
循环每隔 60 秒调用一次check_iptables
函数。
你可以将这个脚本保存为 monitor_iptables.sh
,并使用 chmod +x monitor_iptables.sh
赋予执行权限。然后运行脚本即可开始监控 iptables
的状态。
37. 使用 tcpdump 嗅探 80 端口的访问看看谁最高
bash
#!/bin/bash
# 捕获 80 端口的流量并保存到文件
tcpdump -i eth0 'port 80' -w /tmp/tcpdump_output.pcap -c 1000
# 解析捕获的流量并统计每个 IP 地址的流量
tcpdump -nn -r /tmp/tcpdump_output.pcap | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -nr | head -n 10
- 捕获流量 :使用
tcpdump
命令捕获接口eth0
上 80 端口的流量,并将结果保存到/tmp/tcpdump_output.pcap
文件中。-c 1000
表示捕获 1000 个数据包后停止。 - 解析流量:读取捕获的文件,提取源 IP 地址,并统计每个 IP 地址出现的次数。最后,按流量大小排序并显示前 10 个 IP 地址。
你可以根据需要调整捕获的数据包数量和接口名称。如果你想实时监控流量,可以去掉 -w
选项并直接处理输出:
bash
tcpdump -i eth0 'port 80' -nn | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -nr | head -n 10
这个命令会实时显示产生最多流量的 IP 地址。
38. 编写Shell代码实现以下逻辑:仅开放本机两个IP地址中的一个地址172.16.0.X上绑定的sshd和vsftpd服务给172.16.0.0/16网络中除了172.16.0.0/24网络中的主机之外的所有主机,但允许172.16.0.200访问,每次的用户访问都要记录于日志文件中,注:其中X为学号
假设你的学号为 123,那么 IP 地址为 172.16.0.123。这个脚本将配置 iptables 规则来实现所需的访问控制,并记录访问日志。
bash
#!/bin/bash
# 定义变量
IP="172.16.0.123"
ALLOWED_IP="172.16.0.200"
LOG_FILE="/var/log/iptables_access.log"
# 清除现有的 iptables 规则
iptables -F
iptables -X
# 允许本地回环接口
iptables -A INPUT -i lo -j ACCEPT
# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许 172.16.0.0/16 网络中的所有主机访问 sshd 和 vsftpd 服务
iptables -A INPUT -p tcp -s 172.16.0.0/16 --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -s 172.16.0.0/16 --dport 21 -j ACCEPT
# 拒绝 172.16.0.0/24 网络中的所有主机访问 sshd 和 vsftpd 服务,但允许 172.16.0.200
iptables -A INPUT -p tcp -s 172.16.0.0/24 --dport 22 -j REJECT
iptables -A INPUT -p tcp -s 172.16.0.0/24 --dport 21 -j REJECT
iptables -A INPUT -p tcp -s $ALLOWED_IP --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -s $ALLOWED_IP --dport 21 -j ACCEPT
# 记录所有访问尝试
iptables -A INPUT -p tcp --dport 22 -j LOG --log-prefix "sshd access: " --log-level 4
iptables -A INPUT -p tcp --dport 21 -j LOG --log-prefix "vsftpd access: " --log-level 4
# 拒绝所有其他访问
iptables -A INPUT -j REJECT
# 保存 iptables 规则
iptables-save > /etc/iptables/rules.v4
# 创建日志文件并设置权限
touch $LOG_FILE
chmod 644 $LOG_FILE
# 配置 rsyslog 记录 iptables 日志
echo ':msg, contains, "sshd access: " -'$LOG_FILE >> /etc/rsyslog.d/iptables.conf
echo ':msg, contains, "vsftpd access: " -'$LOG_FILE >> /etc/rsyslog.d/iptables.conf
echo '& ~' >> /etc/rsyslog.d/iptables.conf
# 重启 rsyslog 服务
systemctl restart rsyslog
这个脚本的工作原理如下:
- 清除现有规则 :清除所有现有的
iptables
规则。 - 允许本地回环接口和已建立的连接:确保本地回环接口和已建立的连接可以正常工作。
- 允许特定网络的访问 :允许
172.16.0.0/16
网络中的所有主机访问sshd
和vsftpd
服务,但拒绝172.16.0.0/24
网络中的主机,除了172.16.0.200
。 - 记录访问日志 :记录所有对
sshd
和vsftpd
服务的访问尝试。 - 拒绝其他访问:拒绝所有其他访问。
- 保存规则和配置日志记录 :保存
iptables
规则并配置rsyslog
记录日志。
39. 编写Shell代码,实现以下逻辑:编写脚本/root/bin/checkip.sh,每5分钟检查一次,如果发现通过ssh登录失败次数超过10次,自动将此远程IP放入Tcp Wrapper的黑名单中予以禁止防问
每 5 分钟检查一次 /var/log/auth.log
文件中的 SSH 登录失败次数,并将超过 10 次失败的 IP 地址添加到 TCP Wrapper 的黑名单中。
bash
#!/bin/bash
# 定义日志文件和黑名单文件
LOG_FILE="/var/log/auth.log"
BLACKLIST_FILE="/etc/hosts.deny"
TEMP_FILE="/tmp/ssh_failed_ips.txt"
# 检查 SSH 登录失败次数
check_ssh_failures() {
# 提取过去 5 分钟内的失败登录尝试
grep "Failed password" $LOG_FILE | grep "$(date --date='5 minutes ago' '+%b %d %H:%M')" > $TEMP_FILE
# 统计每个 IP 的失败次数
awk '{print $(NF-3)}' $TEMP_FILE | sort | uniq -c | while read COUNT IP; do
if [ $COUNT -gt 10 ]; then
# 检查 IP 是否已经在黑名单中
if ! grep -q "$IP" $BLACKLIST_FILE; then
echo "sshd: $IP" >> $BLACKLIST_FILE
echo "$(date): Added $IP to blacklist" >> /var/log/ssh_blacklist.log
fi
fi
done
}
# 每 5 分钟检查一次
while true; do
check_ssh_failures
sleep 300 # 5 分钟
done
这个脚本的工作原理如下:
- 检查 SSH 登录失败次数 :从
/var/log/auth.log
文件中提取过去 5 分钟内的失败登录尝试,并统计每个 IP 地址的失败次数。 - 添加到黑名单 :如果某个 IP 地址的失败次数超过 10 次,并且不在黑名单中,则将其添加到
/etc/hosts.deny
文件中,并记录到日志文件/var/log/ssh_blacklist.log
中。 - 定时检查 :使用
while true
循环每 5 分钟调用一次check_ssh_failures
函数。
你可以将这个脚本保存为 /root/bin/checkip.sh
,并使用 chmod +x /root/bin/checkip.sh
赋予执行权限。然后可以通过 nohup /root/bin/checkip.sh &
命令在后台运行这个脚本。
40. 写一个脚本/root/mysqlbak.sh,备份mysql数据库,打成tar包放到/data/下,以备份时间命名,并只保留最近的2个tar包,做一个定时任务,每个月第一个周六的00:01执行/root/mysqlbak.sh
脚本 /root/mysqlbak.sh
bash
#!/bin/bash
# MySQL登录信息
MYSQL_USER="root"
MYSQL_PASSWORD="your_password"
MYSQL_HOST="localhost"
# 备份目录
BACKUP_DIR="/data"
DATE=$(date +%Y%m%d%H%M)
# 备份文件名
BACKUP_FILE="$BACKUP_DIR/mysql_backup_$DATE.sql"
# 备份数据库
mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD -h$MYSQL_HOST --all-databases > $BACKUP_FILE
# 打包备份文件
tar -czf $BACKUP_FILE.tar.gz -C $BACKUP_DIR $(basename $BACKUP_FILE)
# 删除原始SQL文件
rm $BACKUP_FILE
# 保留最近的2个备份
cd $BACKUP_DIR
ls -t mysql_backup_*.tar.gz | sed -e '1,2d' | xargs -d '\n' rm -f
设置定时任务
编辑crontab文件:
bash
crontab -e
添加以下行:
bash
1 0 1-7 * 6 /root/mysqlbak.sh
这行表示每个月的第一个周六的00:01执行脚本/root/mysqlbak.sh
。
请将脚本中的your_password
替换为你的MySQL密码。这样设置后,脚本会按时备份MySQL数据库,并保留最近的两个备份文件。需要注意的是,确保脚本有执行权限:
bash
chmod +x /root/mysqlbak.sh