shell编程总结
前言
shell编程学习总结,1万3千多字带你学习shell编程
往期推荐
14w+poc,nuclei全家桶:nuclei模版管理工具+Nuclei
哥斯拉二开,免杀绕过+规避流量检测设备
fscan全家桶:FscanPlus,fs,fscan适用低版本系统,FscanParser
自动爬取url地址,检测sql注入漏洞,sqlmc安装+使用
一键转换订阅为代理池工具+白嫖思路
TestNet,安装+使用,可以代替灯塔
shell1,脚本的创建和执行+变量的使用
脚本的创建和执行
以kail为例,进入终端,创建一个sh脚本
vim 1.sh
# 按i进入插入模式,输入
echo hello world
# 按esc退出插入模式,然后:wq 保存文件并且退出
直接执行1.sh发现权限不够,需要赋予权限
chmod 777 1.sh
# 777是最高权限,我这里图方便直接赋予的,实际是不推荐的,有安全风险
除了赋予权限,我们还可以让脚本解释器帮我们执行脚本,由于解释器本身就赋予权限,所有不需要在为脚本赋予权限,我们在创建一个2.sh,内容为:echo 111
#!/bin/sh
、#!/bin/bash
、#!/bin/dash
三个都是脚本解释器
可以发现2.sh是没有x:执行权限的,不过我们可以通过解释器进行执行,这里用的都是绝对路径,也可以直接sh,bash,dash这样执行
#!/bin/sh
、#!/bin/bash
、#!/bin/dash
三者的区别
在kali中,使用
ll /bin/sh
可以发现,sh指向的是dash,当然/bin/sh
指向 /bin/dash
,但这并不意味着所有的脚本都会被Dash解释器执行,别的可能就不是了,不过大部分linux系统都是这样的
使用stat命令可以更加细致的对比三者的关系,通过对比发现,bash解释器的大小差不多是dash的十倍,事实上
- 鉴于 bash 过于复杂,有人把 bash 从 NetBSD 移植到 Linux 并更名为 dash(Debian Almquist Shell),并以获得更快的脚本执行速度。Debian Almquist shell,缩写为dash,一种 Unix shell。它比 Bash 小,只需要较少的磁盘空间,但是它的对话性功能也较少。它由 NetBSD版本的Almquist shell (ash)发展而来,于1997年,由赫伯特·许(Herbert Xu)移植到Linux上,于2002年改名为 dash。
所以,dash其实是bash的简化版
总结
一般情况下sh其实调用的就是dash,而dash其实是bash的简化版
除了上面的三个脚本解释器,还有一个source
,他是内置的命令没有固定的路径,而是由Shell直接解析和执行。可以使用 type
命令来检查命令的类型:
type source
可以发现,他没有固定的路径,是内置命令
source和其他三个脚本解释器的区别
- 首先他是内置的命令,是由shell直接解析和执行的
这里主要对比一下和sh的区别
source - 当前Shell执行:命令在当前Shell会话中执行,不会启动新的子Shell。
- 变量作用域:文件中定义的变量、函数和别名会直接在当前Shell会话中生效。
- 环境变化:文件中对环境变量的修改会立即反映在当前Shell会话中。
- 权限要求:文件不需要具有可执行权限,只需要有读权限即可。
sh - 子Shell执行:命令在一个新的子Shell中执行,与当前Shell会话隔离。
- 变量作用域:文件中定义的变量、函数和别名仅在子Shell中生效,不会影响当前Shell会话。
- 环境变化:文件中对环境变量的修改仅在子Shell中生效,不会影响当前Shell会话。
- 权限要求:文件需要具有读权限,但不需要可执行权限。
对我们来说最大的影响有两个 - source执行的脚本变量会影响到当前会话,sh不会
- source执行之后输出结果有颜色变化
创建一个3.sh,内容为name=1
,通过我的演示可以很直观的感受到source影响了当前会话
创建4.sh,内容为
echo aaa
ls
可以发现有颜色变化
变量的使用
变量声明和定义
举例定义一个name变量,name=“xiaoyu”
再利用echo $name打印出来,这就是简单的变量声明
再定义一个age变量
age=20
echo $age
可以写复杂点,比如说
echo my name is $name,and my age is $age years old
然后就直接打印出了姓名和年纪
单引号和双引号的区别
单引号 ('
)
- 字面量引用:单引号内的所有字符都被视为普通字符,不进行变量替换或转义字符处理。
- 特殊字符无效:单引号内的
$
、\
、"
、'
等特殊字符都会被视为普通字符,不会被解释。
双引号 ("
)
- 部分解释:双引号内的大多数字符会被视为普通字符,但某些特殊字符(如
$
、`
、\
)会被解释。 - 变量替换:双引号内的变量会被替换为其值。
- 命令替换:双引号内的命令替换(
`command`
或$(command)
)会被执行。 - 转义字符:双引号内的某些转义字符(如
\n
、\t
)会被解释。
总之,单引号里面的字符视为普通字符,双引号不会
可以发现,使用单引号不会打印变量的值,原因是$
符没有被解释
echo "$name $age"
echo '$name $age'
变量拼接
当我们想要将变量和字符连接起来
echo my name is $name,and my age is $ageyears old
echo my name is $name,and my age is$age years old
可以发现$ageyears被当作新的变量,并且没有被声明,所以打印了空值
为了解决这个问题,问题我们可以使用 双引号" 或者 花括号{}拼接起来
echo my name is $name,and my age is "$age"years old
echo my name is $name,and my age is {$age}years old
变量命名规则
上面讲的全部是临时的一个变量,变量是由数字,字符串下划线组成,但是不能以数字开头例如1aa
这种是不行的,变量中间最好不要有空格比如说a a
如果非要用这种可以加个下划线a_a="aaa"
这种
查找和删除定义的变量
利用set命令查找
set
set | grep name # 可以使用grep进行筛选
使用unset删除变量
unset name
set | grep name
可以发现name变量没有了
shell2,临时变量和永久变量+字符串相关的操作
临时变量和永久变量
临时变量与永久变量是相对应得
常见永久变量
echo $HOME # (家目录root用户)
echo $PATH # 环境变量
对于
P
A
T
H
变量,
w
i
n
d
o
w
s
也有类似的,
P
a
t
h
!
[
i
m
a
g
e
.
p
n
g
]
(
h
t
t
p
s
:
/
/
i
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
i
m
g
c
o
n
v
e
r
t
/
e
f
6
e
91
e
e
d
c
b
04472
c
b
8
b
4
a
3
b
29516811.
p
n
g
)
他们的效果都是类似的,为了简化用户输入命令的过程,不需要指定目录再进行命令使用了比如,
PATH变量,windows也有类似的,Path  他们的效果都是类似的,为了简化用户输入命令的过程,不需要指定目录再进行命令使用了 比如,
PATH变量,windows也有类似的,Path他们的效果都是类似的,为了简化用户输入命令的过程,不需要指定目录再进行命令使用了比如,PATH下面有/bin目录,而我们的sh、bash、dash三个解释器就在这个目录下面,所有就不需要指定路径,就能直接使用,当然指定路径也可以
创建1.sh,里面写入:echo hello world
sh 1.sh
/bin/sh 1.sh
查看命令绝对路径,which
可以使用which命令查看
which sh
which -a ls # 显示所有命令的绝对路径
这里我们可以发现直接使用which ls,不行,原因是ls命令有两个绝对路径,只会输出优先级高的那个命令的全称,加上-a参数就行了
添加可直接使用的命令
添加到/bin/
之类的已经被系统定义的路径中
比如,将1.sh添加到/bin/目录下面
mv 1.sh /bin/
1.sh
直接将命令的绝对路径写入到$PATH
环境变量中
我们先删除/bin/1.sh
rm /bin/1.sh
将目录写入,会导致目录其他命令也会直接被写入
# 首先先复制一下原来的$PATH内容以做备用
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games
临时添加
export PATH=$PATH:"/home/kali/桌面/dir/test" # 有中文最好使用""括起来
记得给脚本赋予权限,不然不能使用
chmod 777 2.sh
export
命令在 Unix 和 Linux 系统中用于将变量导出到环境变量中,使得这些变量在当前 Shell 会话及其子进程中可见。环境变量在多个方面都有重要作用,例如配置系统行为、设置路径、传递参数等,不过直接这样只是在当前命令窗口改变,不会影响别的窗口
永久添加
为了使变量在每次登录时都有效,可以将 export
命令添加到 Shell 配置文件中。常见的配置文件包括:
- Bash:
~/.bashrc
或~/.bash_profile
- Zsh:
~/.zshrc
- Fish:
~/.config/fish/config.fish
示例:在 ~/.bashrc
中添加 export
命令
首先还原一下
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
echo 'export PATH=$PATH:/home/kali/桌面/dir/test' >> ~/.bashrc
source ~/.bashrc
如果直接使用,发现不行
找不到命令 “shopt”,您的意思是:
“shout” 命令来自 Debian 软件包 libshout-tools
尝试 apt install <deb name>
找不到命令 “shopt”,您的意思是:
“shout” 命令来自 Debian 软件包 libshout-tools
尝试 apt install <deb name>
找不到命令 “shopt”,您的意思是:
“shout” 命令来自 Debian 软件包 libshout-tools
尝试 apt install <deb name>
找不到命令 “shopt”,您的意思是:
“shout” 命令来自 Debian 软件包 libshout-tools
尝试 apt install <deb name>
原因是~/.bashrc
是bash解释器的,需要使用bash解释器,而默认的是使用sh解释器。这里也可以看出,bash解释器是三个当中最全的解释器
字符串相关的操作
假设我们想知道一个字符串的长度,比如我们想解析一个字符串的长度我们如何进行实现
比如
name="cybc" age=20
然后我们通过
echo "my name is $name,and I am $age years old" 打印完整字符串
使用 ${#variable}
来获取字符串的长度
str="hello world"
echo ${#str}
使用 ${variable:num:num}
来获取字符串的切片
输出前6个字符,hello
echo ${str:0:6}
shell3,参数传递+算术运算
参数传递
脚本程序传递参数如何实现
创建一个a.sh,内容如下如下
echo 执行的文件名是:$0
echo 第一个参数是:$1
echo 传递的参数作为一个字符串显示:$*
echo 传递的参数独立作为每个字符串显示:$@
echo 传递到脚本的参数个数是:$#
echo 最后命令的退出状态:$?
echo 脚本运行的当前进程ID是:$$
$0
:这个变量包含了当前执行脚本的名称。如果脚本是通过完整路径调用的,它将包含整个路径。$1, $2, ...
:分别表示传递给脚本的第一个、第二个等参数。可以一直递增到脚本接收到的最后一个参数。$*
:当未被双引号包围时,$*
与"$@"
的行为相同,都是将所有位置参数视为一个字符串。但当它们被双引号包围时,"$*"
会将所有参数视为单个以首个字符为分隔符(通常是空格)连接起来的字符串,而"$@"
则会保持每个参数独立。$@
:与$*
类似,但在被双引号包围时,它将每个参数都作为独立的字符串处理。$#
:表示传递给脚本或函数的参数数量。$?
:存储最近一次执行的前台管道的退出状态。通常,0 表示成功,非零值表示错误。$$
:表示当前shell进程的PID(进程ID)。
更多的Shell特殊变量
除了上述变量之外,还有其他一些有用的特殊变量:
$_
:这是上一个命令执行的最后一个参数。这在交互式shell中特别有用。$!
:最近一个后台进程中运行的作业的PID。$-
:显示当前shell选项设置的状态。$IFS
(Internal Field Separator):定义了用于分割单词的字符,默认为空格、制表符和换行符。这对于控制如何解析输入非常关键。$BASH_VERSION
:如果你使用的是Bash shell,这个变量保存了当前Bash版本的信息。$HOME
:用户的家目录。$PWD
:当前工作目录。$SECONDS
:自脚本开始执行以来经过的秒数。$RANDOM
:生成一个随机整数。每次引用该变量时都会产生一个新的随机数。$LINENO
:当前正在执行的代码行号。$BASH_SOURCE
:对于函数或脚本,提供了一个数组,其中包含了调用栈中每个元素的文件名。$FUNCNAME
:如果在一个函数内,该变量包含了函数的名字。
$*
与"$@"
当未被双引号包围时,$*
与"$@"
的行为相同,都是将所有位置参数视为一个字符串。但当它们被双引号包围时,"$*"
会将所有参数视为单个以首个字符为分隔符(通常是空格)连接起来的字符串,而"$@"
则会保持每个参数独立。
新建一个b.sh
#!/bin/bash
echo "Using \$*: $*"
echo "Using \"\$*\": \"$*\""
echo "Using \$@: $@"
echo "Using \"\$@\": \"$@\""
当你运行这个脚本并传入参数./script.sh "hello world" goodbye
时,输出将会是这样的:
Using $*: hello world goodbye
# 参数被当作单个字符串处理Using "$*": "hello world goodbye"
# 所有参数作为一个字符串,中间用空格分割Using $@: hello world goodbye
# 参数被视为独立的字符串Using "$@": "hello world" "goodbye"
# 每个参数都被独立地引用
算术运算
常见的命令
1. 使用 expr
命令
expr
是一个非常基础但功能有限的工具,用于执行简单的算术运算。
result=$(expr 5 + 3)
echo $result # 输出 8
注意:使用 expr
时,操作符和数字之间需要有空格。
2. 使用 $((...))
语法
这是一种更现代且更简洁的方法,可以直接在变量赋值或命令替换中使用。
result=$((5 + 3))
echo $result # 输出 8
# 或者直接输出
echo $((5 * 3)) # 输出 15
3.使用 let
命令
let
可以用来执行整数算术表达式,并将结果存储到变量中。
let result=5+3
echo $result # 输出 8
4. 使用 bc
命令
bc
是一个强大的计算器,支持浮点运算和复杂的数学函数。
result=$(echo "scale=2; 5 / 3" | bc)
echo $result # 输出 1.67 (保留两位小数)
# 执行更复杂的计算
result=$(echo "sqrt(9)" | bc -l)
echo $result # 输出 3.00000000000000000000
其中,-l
选项加载了标准数学库,允许进行更高级的数学运算。
5. 使用 awk
awk
不仅是一个文本处理工具,也提供了丰富的数学运算能力。
result=$(awk 'BEGIN{print 5+3}')
echo $result # 输出 8
# 浮点运算
result=$(awk 'BEGIN{printf "%.2f\n", 5/3}')
echo $result # 输出 1.67
6. 使用 declare -i
来定义整数变量
这可以让你对变量进行直接的算术运算而不需要额外的命令。
declare -i num=5
num+=3
echo $num # 输出 8
以expr为例,演示加减乘除取模
注意,符前后都需要空格隔开
+
-
\* 转义一下,linux的*代表任意
/
%
\( \) 括号需要转义
如
expr 6 \* 6(乘法是用\*,python.直接*就行了 要用\进行转义)
复杂一些的运算
注意括号需要转义,并且前后需要空格
shell4,shell脚本于用户交互+关系运算符
shell脚本于用户交互,read命令
read [选项] [变量名...]
常用参数
-p "提示信息"
: 在读取之前显示一条提示信息,记得手动打印一个换行符,因为该选项会阻止自动换行-t 秒数
: 设置等待用户输入的时间限制(秒)。如果超时,则返回一个非零退出状态。-s
: 安静模式,输入的内容不会回显到终端上,适用于密码输入等敏感信息。-n 字符数
: 限制输入的最大字符数。-d 分隔符
: 指定结束输入的分隔符,默认为换行符。
可以利用read命令进行shell脚本于用户的交互
例如
read name
echo $echo
但是当我们使用-p
参数的时候,会报错
原因很简单,kali中默认使用sh脚本解释器运行脚本,sh本质是指向dash解释器,而dash解释器其实是bash解释器的简化版,大小只有bash的十分之一左右,所以有很多命令参数解释并不支持
需要使用bash脚本
bash
read -p "请输入姓名:" name
可以多个参数输入
read -p "请输入姓名和年龄:" name age
echo $name $age
我们在看个例子,使用参数-t(指定时间)
read -t 10 -p "请输入您的姓名:" name
# 如果我们在10秒内输入了就会执行成功,10秒内没有输入就会自动退出
可以使用-n参数,限制用户输入的字符个数,
read -n 3 -t 10 -p" 请输入您的姓名:" name
输入三个字符自动执行,如果输入了三个以下的字符需要自己敲回车执行
关系运算符
在脚本环境中如何简单的做条件判断
运算符
-eq
:检查两个数是否相等。-lt
:检查左边的数是否小于右边的数。-gt
:检查左边的数是否大于右边的数。-ne
:检查两个数是否不相等。
使用
vim o.sh
首先定义两个变量,然后通过if条件判断来进行两个简单的条件判断再接入关系运算符
内容为
num1=11
num2=22
if [ $num1 -eq $num2 ]; then
echo "数字相等"
else
echo "数字不相等"
fi
更加复杂一些的
#!/bin/bash
# 定义两个变量
num1=10
num2=20
# 使用 if-then-else 语句进行条件判断
if [ $num1 -eq $num2 ]; then
echo "num1 等于 num2"
elif [ $num1 -lt $num2 ]; then
echo "num1 小于 num2"
elif [ $num1 -gt $num2 ]; then
echo "num1 大于 num2"
else
echo "num1 不等于 num2(虽然这个条件总是为真,但这里是为了展示 else 的用法)"
fi
# 另一个例子,使用更复杂的条件
if [ $num1 -ne $num2 ] && [ $num1 -lt 15 ]; then
echo "num1 不等于 num2 并且 num1 小于 15"
else
echo "num1 不满足上述条件"
fi
注意事项
- 在
[ ]
中使用空格是很重要的。例如,[ $num1 -eq $num2 ]
必须在每个元素之间加上空格。 - 如果你想要对字符串进行比较,可以使用
==
或者!=
。但是请注意,这些操作符需要在[[ ]]
中使用,而不是[ ]
。例如:
if [[ "hello" == "hello" ]]; then
echo "字符串相等"
fi
test和[]
[ ]
和 test
命令在 Bash 脚本中实际上是等价的。[ ]
是 test
命令的一种更直观的写法。你可以用 test
来替换 [ ]
,但需要注意的是,使用 test
时不需要方括号,参数直接传递给 test
命令。
简单的用法
复杂一些的
#!/bin/bash
# 定义两个变量
num1=10
num2=20
# 使用 test 命令进行条件判断
if test $num1 -eq $num2; then
echo "num1 等于 num2"
elif test $num1 -lt $num2; then
echo "num1 小于 num2"
elif test $num1 -gt $num2; then
echo "num1 大于 num2"
else
echo "num1 不等于 num2(虽然这个条件总是为真,但这里是为了展示 else 的用法)"
fi
# 另一个例子,使用更复杂的条件
if test $num1 -ne $num2 && test $num1 -lt 15; then
echo "num1 不等于 num2 并且 num1 小于 15"
else
echo "num1 不满足上述条件"
fi
shell5,字符串运算符+逻辑运算符
字符串运算符
[[ ... ]]
和[...]
首先我们在终端利用vim打开u.sh
内容为:
str1="hello"
str2="hello"
if [[ "$str1" == "$str2" ]]; then
echo True
else
echo false
fi
- 使用
==
来比较两个字符串是否相等。 - 使用双方括号
[[ ... ]]
来进行字符串比较,它支持模式匹配和更复杂的表达式。 - 变量应当用双引号包围,以确保即使变量值为空或包含空格时也能正确处理。
提示[[: not found
。这通常是因为shell环境不支持[[ ... ]]
条件表达式,这可能是由于您使用的是一个较旧的或非常基础的shell版本,比如sh
(Bourne shell),它不支持这种语法。
kail默认使用sh解释器,我们可以使用bash解释器,因为在kali中sh解释器的其实最终用的dash解释器,而dash解释器是bash解释器的简化版
当然了,我们如果要使用sh解释器,也可以使用旧的语法
str1="hello"
str2="hello"
if [ "$str1" = "$str2" ]; then
echo True
else
echo false
fi
注意以下几点:
- 使用单个等号
=
- 确保在
[
和]
两边都有空格。 - 变量仍然需要用双引号包围以确保安全处理。
上面的两个例子中,双引号都是可以省略的,加上可以确保即使变量值为空或包含空格时也能正确处理
大小写是否敏感
我们可以更改str1为Hello,来看看效果
str1="Hello"
str2="hello"
if [ $str1 = $str2 ]; then
echo True
else
echo false
fi
我们使用!=
在来看看他们是否不想等
str1="Hello"
str2="hello"
if [ $str1 != $str2 ]; then
echo True
else
echo false
fi
可以发现,对大小写敏感
检查字符串的长度是否为0、不为0
str1="hello"
str2="helllo"
# 检查str1的长度是否为0
if [ -z "$str1" ]; then
echo True # 如果str1长度为0,则输出True
else
echo False # 否则输出False
fi
- 使用
-z
来检测字符串长度是否为零。 - 变量名
$str1
应该被双引号包围以防止空值或包含空格的值导致的问题。 if
和[
之间以及[
和条件表达式之间需要有空格。then
关键字之前也需要有一个空格。
使用-n
测试来检查字符串是否不为空。如果字符串不为空,则返回True
;如果字符串为空,则返回False
。我们将把str1
改为str11
并检查其长度。
# 定义变量
str11="" # 这里设置为一个空字符串
# 检查str11的长度是否不为0
if [ -n "$str11" ]; then
echo True # 如果str11不为空,则输出True
else
echo False # 如果str11为空,则输出False
fi
逻辑运算符之布尔运算符
# 定义变量
num1=9
# 检查num1是否不等于9
if [ "$num1" != "9" ]; then
echo "num1不等于9"
else
echo "num1等于9"
fi
- 使用单方括号
[ ... ]
来进行条件测试。 - 使用
!=
来检查num1
是否不等于9。 - 变量
num1
被双引号包围以确保安全处理。 if
语句的格式已经正确调整。
-a和-o 参数
-a
来连接两个条件,相当于&&
-o
来连接两个条件,相当于||
num1=9
num2=19
# 检查num1是否不等于9并且num2是否小于20
if [ "$num1" != "9" -a "$num2" -lt 20 ]; then
echo True
else
echo False
fi
- 使用单方括号
[ ... ]
来进行条件测试。 - 使用
!=
来检查num1
是否不等于9。 - 使用
-lt
来检查num2
是否小于20。 - 使用逻辑与运算符
-a
来连接两个条件。 - 变量
num1
和num2
被双引号包围以确保安全处理。
更改为-o
num1=9
num2=19
# 检查num1是否不等于9并且num2是否小于20
if [ "$num1" != "9" -o "$num2" -lt 20 ]; then
echo True
else
echo False
fi
我们也可以利用多个[...]
,这样调理更加清楚
比如刚才的例子,我们可以这样
注意:这里要使用管道符链接,比如&&、||,因为-a
和-o
都是需要在[]
才能被识别的
#!/bin/sh
# 定义变量
num1=9
num2=19
# 检查num1是否不等于9或者num2是否小于20
if [ "$num1" != "9" ] || [ "$num2" -lt 20 ]; then
echo True
else
echo False
fi
shell6,if条件判断语句+for循环结构
if条件判断语句
有一点编程基础的应该都知道if语句,这里就不解释了,看一下实例就都会了
参数使用
在if
语句中使用测试表达式时,需要特别注意格式。[ ]
是test
命令的一种符号链接形式,用于进行条件测试。
参数:
- 文件测试:
-e
存在,-d
目录,-f
普通文件等。 - 字符串测试:
-z
空字符串,-n
非空字符串,==
相等,!=
不等。 - 数字比较:
-eq
等于,-ne
不等于,-lt
小于,-le
小于等于,-gt
大于,-ge
大于等于。
注意:
- 在
[ ]
内确保有适当的空格,如[ -f file ]
。 - 使用双引号包围变量,以防止变量为空或包含空格时出现错误。
- 当条件复杂时考虑使用
[[ ]]
,它提供了更多的特性,比如支持模式匹配和更宽松的空格规则。 - 利用
&&
和||
组合命令,可以在一条命令行上完成复杂的逻辑判断。
实例
# 定义变量
a=10
b=20
# 进行条件判断
if [ "$a" -eq "$b" ]; then
echo "a=b"
elif [ "$a" -gt "$b" ]; then
echo "a>b"
else
echo "没有符合上述条件"
fi
语句很好看懂,不过需要注意
- 使用
[ ]
来创建测试表达式,这是test
命令的一个符号链接。 - 在
-eq
,-gt
等运算符两边都需要有空格。 - 每个
[
后面应该有一个对应的]
,并且[
和]
之间至少要有一个空白字符(通常是一个空格)与之分隔。 elif
和else
块同样需要以fi
结束。
这里我们可以再增加一条elif语句,
elif [ $a -lt $b ]; then
echo "a<b"
注意:这里变量最好加上双引号,不然如果脚本复杂的话容易出事
for循环结构
在Shell脚本中,for
循环是一种非常有用的控制结构,它允许你重复执行一系列命令直到满足特定条件。for
循环可以处理数字、字符串列表、文件名以及命令的输出等。下面将详细介绍几种常见的for
循环用法及其示例。
基本格式
列表迭代
这是最简单的for
循环形式,它遍历一个预定义的值列表。
for variable in list
do
# 执行的命令
done
例如,打印从1到5的数字:
for i in 1 2 3 4 5
do
echo $i
done
使用范围
Bash支持使用花括号{}
来指定一个数值范围。
for i in {start..end}
do
# 执行的命令
done
例如,打印从1到10的数字:
for i in {1..10}
do
echo "Number: $i"
done
您还可以通过增加第三个参数来指定步长:
for i in {1..10..2} # 步长为2
do
echo "Odd number: $i"
done
遍历文件
for
循环也可以用于遍历目录中的文件。
for file in /path/to/directory/*
do
# 对每个文件执行操作
echo "Filename: $file"
done
处理命令输出
您可以使用$(command)
来获取命令输出,并将其作为for
循环的输入。
for line in $(cat filename.txt)
do
# 处理每一行
echo "Line: $line"
done
类C语言风格的for
循环
Bash还支持一种类似于C语言的for
循环语法,这在需要更复杂的初始化、条件测试和更新逻辑时非常有用。
for (( EXP1; EXP2; EXP3 ))
do
# 执行的命令
done
EXP1
是初始化表达式。EXP2
是条件表达式。EXP3
是每次循环后执行的更新表达式。
例如,打印1到10的数字:
for (( i=1; i<=10; i++ ))
do
echo "Count: $i"
done
特殊用途
无限循环
如果您想创建一个无限循环,可以省略for
语句中的所有元素。
for (( ; ; ))
do
# 无限执行的命令
read -p "Press [Enter] key to continue or [Ctrl+C] to exit..." key
done
跳出循环
使用break
语句可以在满足特定条件时提前退出循环。
for i in {1..10}
do
if [ $i -eq 5 ]; then
break
fi
echo "Number: $i"
done
继续下一次迭代
使用continue
语句可以让循环跳过当前迭代的剩余部分,直接进入下一次迭代。
for i in {1..10}
do
if [ $((i % 2)) -eq 0 ]; then
continue
fi
echo "Odd number: $i"
done
实际应用案例
批量添加用户
假设我们有一个包含用户名的文本文件,我们可以使用for
循环来批量添加这些用户。
#!/bin/bash
while IFS= read -r user
do
useradd $user
echo "User added: $user"
done < users.txt
检查网络连通性
我们可以使用for
循环结合ping
命令来检查一组IP地址的连通性。
#!/bin/bash
for ip in 192.168.1.{1..254}
do
ping -c 1 $ip &>/dev/null
if [ $? -eq 0 ]; then
echo "$ip is up"
else
echo "$ip is down"
fi
done
通过以上介绍,您应该能够理解如何在Shell脚本中使用for
循环来处理各种任务。for
循环是编写自动化脚本时非常强大的工具,掌握其用法可以使您的脚本更加高效和灵活。
实例
for num in 1 2 3 4 5 6
do
echo "num is $num"
done
我们可以使用{..}
来简化数值列表的定义,不过需要使用bash脚本
for str in "Hello world"
do
echo "str is $str"
done
这是因为 for
循环在这里只迭代了一次,且迭代的值就是整个字符串 "Hello world"
。
如果想要遍历 "Hello world"
中的每个单词(即 Hello
和 world
),需要使用空格来分隔这些单词:
for str in Hello world
do
echo "str is $str"
done
这样,for
循环会把 Hello
和 world
扩展:遍历字符串中的每个字符
如果你的目标是遍历 "Hello world"
中的每一个字符,那么你需要稍微修改一下脚本。一种方法是使用 while
循环结合 read
命令来逐个读取字符,或者使用 fold
命令来拆分字符串。以下是两种方法的示例:
方法一:使用 while
循环和 read
命令
需要使用bash脚本
string="Hello world"
while IFS= read -r -n1 char
do
echo "char is $char"
done <<< "$string"
这里,IFS=
确保了内部字段分隔符为空,使得 read
不会跳过空白字符。-n1
参数告诉 read
每次只读取一个字符。<<<
是 here string 的语法,用于将字符串作为输入提供给循环。
方法二:使用 fold
命令
sh和bash脚本解释器都可以
string="Hello world"
for char in $(echo -n "$string" | fold -w1)
do
echo "char is $char"
done
这里,fold -w1
将字符串按照每1个字符宽度进行分割,然后 for
循环遍历这些字符。
shell7,bash解释器的 for循环+while循环
for
前面已经讲过for循环了,不过是sh解释器的for循环,前面已经说过了:在kali中sh解释器的其实是指向dash解释器,而dash解释器是bash解释器的简化版,只有bash解释器的1/10左右的大小,所以bash解释器可以支持更多更复杂的语法
for循环有三种写法:反引号``
,$(...)
,((...))
第一种,``
for i in `seq 1 100`
do
echo $i
done
seq 1 100
: 这个命令生成一个数字序列,从1开始直到100(包括100)。seq
是一个在Linux/Unix系统中用来产生一系列数字的工具。在这个例子中,它将生成一系列连续的整数:1, 2, 3, …, 98, 99, 100。for i in ...
: 这是for
循环的开始,它会遍历由seq 1 100
生成的所有数字。每次迭代时,变量i
都会被设置为当前迭代中的数字。do
…done
: 这两个关键字定义了循环体,即在每次迭代时要执行的代码块。在这个例子中,循环体只包含了一条语句——echo $i
,这条语句用于输出当前迭代中的数字i
。echo $i
: 在每次循环中,echo
命令会被调用,并且当前值$i
会被打印到标准输出。
第二种,$(...)
for i in $(seq 1 100)
do
echo $i
done
第三种,((...))
或者使用C语言风格的for
循环语法,这可能对熟悉C语言的程序员来说更加直观:
注意:这里的语法不像sh解释器那么严苛,符号前后不需要强制空格
这样标准的也行
for (( i=1; i<=100; i++ ))
do
echo $i
done
for ((i=i;i<100;i++ ))
do
echo $i
done
这里,(( i=1; i<=100; i++ ))
直接指定了循环的初始化、条件测试和增量操作,使得整个循环结构看起来更加紧凑。
while
while循环有编程基础的应该都知道,就不细讲语法了,大家看一下实例就都知道怎么写了。还是和for循环一样,将一下sh和bash,注:sh可以的bash都可以
sh解释器
首先sh解释器只能使用[]
或者test来规定循环的结束条件,其次对于,循环变量的变化sh解释器可以使用的语法有:$((...))
和$(expr $i + 1)
$((...))
i=1
while [ $i -lt 10 ]
do
echo $i
i=$((i+1))
done
$(expr $i + 1)
i=1
while [ $i -lt 10 ]
do
echo $i
i=$(expr $i + 1)
done
sh解释器不支持:(())
和let
如果将i=$((i+1))替换成((i++))
i=1
while [ $i -lt 10 ]
do
echo $i
((i++))
done
不支持
程序报错,但是不会停止运行,会直接死循环
替换成let
i=1
while [ $i -lt 10 ]
do
echo $i
let i=i+1
done
还是一样,程序报错,但是不会停止运行,会直接死循环
bash解释器
sh可以的bash一定可以
bash可以使用更加简单的语法:循环结束条件可以使用(())
,循环变量的变化可以使用$((...))
、$(expr $i + 1)
、(())
和let
i=1
while(($i<=10))
do
echo $i
((i++))
done
let的使用上面已经讲了,这里就不演示了
shell8
until循环
until
循环与while
循环的区别
while
循环是在条件为真时执行循环体,而until
循环则是在条件为假时执行循环体。- 通常情况下,
while
循环更常用,因为它直观地反映了“只要条件成立就继续做某事”的逻辑。 until
循环更适合于那些默认行为是重复执行,直到发生某种变化的情况。
所以until循环和while循环除了循环条件是相反的(while:为真时循环,until:为假时循环),几乎一样
还是和while一样,分为sh解释器和bash解释器:在kali中sh解释器的其实是指向dash解释器,而dash解释器是bash解释器的简化版,只有bash解释器的1/10左右的大小,所以bash解释器可以支持更多更复杂的语法
while讲的文章:shell编程7,bash解释器的 for循环+while循环
这里简单演示一下,详细的去看while的文章
sh解释器
首先sh解释器只能使用[]
或者test来规定循环的结束条件,其次对于,循环变量的变化sh解释器可以使用的语法有:$((...))
和$(expr $i + 1)
i=1
until [ $i -gt 10 ]
do
echo $i
i=$((i+1))
done
- 初始化:首先,变量
i
被初始化为1。 - 条件判断:
until [ $i -gt 10 ]
部分定义了循环的终止条件。这里的条件是当$i
大于10时,循环停止。-gt
是一个比较运算符,表示“大于”。 - 循环体:
echo $i
命令输出当前i
的值。i=$((i+1))
这条语句将i
的值增加1。这里使用了算术扩展$((...))
来进行数学运算。
bash解释器
sh可以的bash一定可以
bash可以使用更加简单的语法:循环结束条件可以使用(())
,循环变量的变化可以使用$((...))
、$(expr $i + 1)
、(())
和let
i=1
until ((i>=10))
do
echo $i;
((i++))
done
- 初始化:变量
i
被初始化为1。 - 条件判断:
until ((i >= 10))
部分定义了循环的终止条件。这里的条件是当i
大于或等于10时,循环停止。((...))
是用于执行算术运算的结构,它允许在表达式中直接使用比较操作符(如>=
)。 - 循环体:
echo $i;
命令输出当前i
的值。((i++))
这条语句将i
的值增加1。这里使用了算术扩展((...))
来进行数学运算,i++
表示将i
递增1。
case语句
基本语法
有编程基础的应该都知道case多分支结构,我这里就不细讲了
case
语句的基本格式如下:
case 变量 in
模式1)
# 当变量匹配模式1时执行的命令
;;
模式2|模式3)
# 当变量匹配模式2或模式3时执行的命令
;;
*)
# 当变量不匹配任何模式时执行的命令(默认情况)
;;
esac
case 变量 in
:开始一个case
语句,其中变量
是要检查的值。模式)
:定义一个模式,如果变量
与这个模式匹配,则执行紧接着的命令序列。;;
:表示一个模式结束。*)
:通配符,代表所有情况,通常作为默认分支使用。esac
:结束case
语句。
特殊模式- 精确匹配:如
1)
、abc)
等,仅当变量值完全等于指定字符串时匹配。 - 通配符:如
*word*
,可以用来匹配包含特定子串的任意字符串。 - 范围:如
[0-9]
,可以匹配一个数字字符。 - 多个模式:通过
|
分隔,例如a|b|c)
,可以匹配’a’、‘b’或’c’。
实例
read -p "请输入一个数字:" num
case $num in
1) # 如果输入的是1
echo "您输入的数字是1"
;;
2) # 如果输入的是2
echo "您输入的数字是2"
;;
*) # 如果输入的是其他任何值
echo "您输入的是其他数字"
;;
esac
read -p "请您输入一个数值: " num
:这条命令提示用户输入一个数值,并将输入存储到变量num
中。case $num in ... esac
:这是case
语句的基本结构。它会检查$num
的值,并根据匹配的情况执行相应的代码块。1)
、2)
和*)
:这些是模式匹配项。1)
匹配输入为1的情况,2)
匹配输入为2的情况,而*)
则匹配所有其他情况(即除了1和2之外的所有输入)。;;
:每个case
分支以;;
结束,表示该分支的结束。
基本函数
在Shell脚本中,函数是一种组织代码的方式,可以将一段代码封装起来,以便于重复使用。通过定义和调用函数,可以使脚本更加模块化、易于维护,并且提高代码的复用性。下面是一些关于如何在Shell脚本中使用函数的基本指导和示例。
定义函数
在Bash中,可以通过以下方式定义一个函数:
function_name() {
# 函数体
}
其中function_name
是你给函数起的名字,应该具有描述性以方便理解其功能。
调用函数
一旦定义了函数,就可以通过简单地写函数名来调用它:
function_name
如果函数需要参数,可以在调用时传递这些参数:
function_name arg1 arg2
实例
基本函数
这是一个简单的函数,用于打印一条消息:
greet() {
echo "Hello, $1"
}
# 调用函数
greet "World" # 输出: Hello, World
在这个例子中,$1
是第一个参数的位置变量,代表传入的第一个参数。
带返回值的函数
函数可以返回一个状态码(0表示成功,非0表示错误),也可以通过全局变量或输出来返回结果:
add_numbers() {
local sum=$(( $1 + $2 ))
echo $sum # 输出求和的结果
}
result=$(add_numbers 5 3) # 将函数的输出捕获到变量result中
echo "The result is: $result" # 输出: The result is: 8
这里使用local
关键字声明了一个局部变量sum
,它只在函数内部可见。echo $sum
用来输出计算结果,这个输出被外部命令$(...)
捕获并赋值给result
变量。
使用return语句
虽然return
通常用于返回状态码,但也可以用来控制流程:
check_number() {
if [ "$1" -gt 10 ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
if check_number 15; then
echo "Number is greater than 10."
else
echo "Number is not greater than 10."
fi
在这个例子中,check_number
函数检查传入的数字是否大于10,并通过return
语句返回相应的状态码。
注意事项
- 作用域:在函数内部定义的变量默认是局部的,除非使用
global
关键字。 - 参数处理:函数可以接受任意数量的参数,它们分别对应位置参数
$1
,$2
, … 等。 - 返回值:函数的返回值通常是通过
return
语句设置的状态码,范围从0到255。对于复杂的数据结构,通常使用输出或全局变量来返回数据。 - 命名规则:函数名遵循与变量相同的命名规则,即不能以数字开头,且不能包含特殊字符。
shell9,不同脚本的互相调用+重定向的使用
- 重定向操作和不同脚本的互相调用
不同脚本的互相调用
1. 直接调用
直接从一个脚本中使用bash
(或sh
等其他shell解释器)命令来执行另一个脚本文件
1.sh
#!/bin/bash
let num=5+3
echo $num
2.sh
#!/bin/sh
bash ./1.sh
看这个例子,可以发现,哪怕2.sh用的解释器是sh,也不会影响必须要使用解释器的1.sh,除非1.sh没有指定bash解释器,那么就会按照默认的解释器,kali中刚好是sh,所以报错
echo $SHEll # 查看默认解释器
2. 使用点号(.
)或者source命令来包含脚本
可以使用.
操作符(也称为source命令)。使得被包含的脚本在当前shell环境中执行,而不是创建一个新的子shell。
相当于c语言中的include
1.sh
#!/bin/bash
let num=5+3
echo $num
2.sh
#!/bin/sh
. 1.sh
点号.
,和source
的区别
- 虽然他们都可以进行包含,并且都可以进行命令执行,甚至使用which命令指向的内置shell都一样,但是还是有区别的
- 通上面图片和下面的图片,可以发现,使用source命令必须指定bash解释器,而
.
不需要,所有优先使用.
,代码的鲁棒性更好
其他的两个就没有这么常用了,就自己看看就行了
3. 传递参数
当调用另一个脚本时,可以传递参数给它。这些参数可以通过位置参数$1, $2, … 来接收。
#!/bin/bash
# script1.sh
echo "This is script1"
./script2.sh arg1 arg2
echo "Back to script1"
在script2.sh
中,你可以这样获取参数:
#!/bin/bash
# script2.sh
echo "Received arguments: $1 and $2"
4. 设置环境变量
如果你想让被调用的脚本能够访问到某些环境变量,可以在调用之前设置它们。
#!/bin/bash
# script1.sh
export MY_VAR="Hello, World!"
./script2.sh
然后,在script2.sh
里就可以读取这个环境变量了:
#!/bin/bash
# script2.sh
echo "Value of MY_VAR: $MY_VAR"
重定向
- 分为输出重定向和输入重定向
输出重定向
使用 >
和>>
来实现
- 使用符号
>
将标准输出重定向到一个文件。如果文件已经存在,则会被覆盖。 - 使用符号
>>
将标准输出追加到一个文件的末尾,不会覆盖现有内容。
ls > 1.txt
ls >> 1.txt
输入重定向
使用<
、<<
、<<<
实现
- 使用
<
符号将文件内容作为命令的标准输入 - 使用
<<
符号可以从标准输入中读取多行文本,直到遇到指定的终止字符串 - 使用
<<<
符号将一个字符串作为命令的标准输入。
2.txt内容为
. # 表示当前目录
test
# 空的也表示当前目录
<
+xargs
命令进行转换的使用
使用 <
符号将文件内容作为命令的标准输入,使用命令行参数的命令不能直接这样输入,需要使用 xargs
命令进行转换
比如:这是因为 ls
命令不从标准输入读取文件名,而是从命令行参数中读取。
创建2.txt,写入
/home/kali/桌面/dir/test
.
- . 表示当前目录
ls < 2.txt
xargs ls < 2.txt
可以发现,直接使用ls是不行的,运行 ls <2.txt
时,ls
命令并没有从 2.txt
文件中读取文件名,而是直接列出了当前目录下的文件和文件夹
<<
使用 <<
符号可以从标准输入中读取多行文本,直到遇到指定的终止字符串。
cat << EOF
Hello, World!
This is a test.
EOF
cat
命令会读取从 EOF
开始到下一个 EOF
之间的所有文本,并将其作为输入。
<<<
使用 <<<
符号将一个字符串作为命令的标准输入。
wc -c <<< "Hello World"
wc -c
命令会计算字符串 "Hello World"
中的字符数
文件描述符+/dev/null
- 标准输入(0): 默认情况下,标准输入是从键盘读取数据。
- 标准输出(1): 默认情况下,标准输出是显示在终端上的文本。
- 标准错误(2): 默认情况下,标准错误也是显示在终端上的文本,但通常用于输出错误信息。
ls > 1.txt
s > 1.txt 2>2.txt
s 1> 1.txt 2>2.txt
这里我们可以发现,使用s > 1.txt 2>2.txt
命令使1.txt的内容为空了,其实这里相当于执行了
> 1.txt
所以内容被覆盖了,刚好覆盖的内容是空,所以1.txt为空
使用s 1> 1.txt 2>2.txt
发现是一样的
/dev/null
指向的是kali的回收站,所以如果我们直接指向回收站
ls > /dev/null
ls 2> /dev/null
ls 1> /dev/null
> /dev/null 2>&1
详细解释
&
是在这里是重定向符,可以重定向流,这里就是将2:报错流重定向到1:标准流,而标准流到了回收站,所以这里不显示任何输出和报错
2>&1
:2
表示标准错误流(文件描述符 2)。&1
表示标准输出流(文件描述符 1),这里的&
是必要的,因为它表示这是一个文件描述符,而不是一个文件名2>&1
的意思是将标准错误流(2)重定向到标准输出流(1)的位置。
执行顺序
-
> /dev/null
:- 首先,标准输出(1)被重定向到
/dev/null
。这意味着任何原本会输出到标准输出的内容都会被丢弃。
- 首先,标准输出(1)被重定向到
-
2>&1
:- 接着,标准错误(2)被重定向到标准输出(1)的位置。由于标准输出已经被重定向到
/dev/null
,因此标准错误也会被重定向到/dev/null
。
- 接着,标准错误(2)被重定向到标准输出(1)的位置。由于标准输出已经被重定向到
shell编程作业
⼀、⽤Shell写⼀个计算器
通过 read 命令获取用户输入的表达式,表达式的格式设定为 操作数1 运算符 操作数2 ,例如 5 + 3 ,然后利用设计的脚本输出运算结果。
要求:实现 加、减、乘、除运算
简单的可以使用bc命令
#!/bin/bash
while true
do
read -p "请输入一个表达式(格式是:操作数1 运算符 操作数2):" expression
result=$(echo "$expression" | bc)
echo $result
done
复杂的可以是case语句或者if-else语句
read -p "请输入一个表达式(格式是:操作数1 运算符 操作数2):" expression
IFS=' ' read -r num1 operator num2 <<< "$expression"
case $operator in
+)
result=$((num1 + num2))
;;
-)
result=$((num1 - num2))
;;
\*)
result=$((num1 * num2))
;;
/)
if (("$num2" == "0")); then
echo "错误: 除数不能为零"
exit 1
fi
result=$((num1 / num2))
;;
*)
echo "不支持,请重新输入"
exit 1
;;
esac
echo "结果为:$result"
⼆、⽤Shell定义⼀个求n的阶乘函数
定义一个计算n的阶乘的函数(含参函数、if判断、for循环)
写一个脚本去调用求阶乘的函数,并定义一个变量 n 可用read交互输入,最终输出 n的阶乘 结果
在kali中sh解释器的其实是指向dash解释器,而dash解释器是bash解释器的简化版,只有bash解释器的1/10左右的大小,所以bash解释器可以支持更多更复杂的语法
sh解释器
jiechen() {
local n=$1
local result=1
if [ "$n" -lt 0 ]; then
echo "错误: 阶乘只适用于非负整数"
return 1
fi
for i in $(seq 1 $n); do
result=$((result * i))
done
echo $result
}
read -p "请输入一个非负整数 n:" n
nn=$(jiechen $n)
echo "n 的阶乘是: $nn"
bash解释器
jiechen() {
local n=$1
local result=1
if (("$n" < 0)); then
echo "请输入一个>=0的数字"
return 1
fi
for (( i=1; i<=n; i++ )); do
result=$((result * i))
done
echo $result
}
read -p "请输入一个非负整数 n:" n
nn=$(jiechen $n)
echo "n 的阶乘是: $nn"
扩展
获取ipv4的地址
写⼀个Shell脚本去筛选出eth0⽹卡的ipv4地址,并赋值⼀个变量输出
(可以去了解grep、awk文本处理工具)
ip_address=$(ip a show eth0 | grep "inet" | awk '$1=="inet" {print $2}' | cut -d '/' -f1
)
if [ -z "$ip_address" ]; then
echo "没有找到eth0的ipv4的地址"
else
echo "eth0的ipv4的地址是: $ip_address"
fi
crontab
将上⾯的脚本编辑到计划任务中,并将echo输出内容重定向到⼀个固定⽂件中,计划时间随意⼀天⼀次也可以
使用crontab -e命令进行写入,选择nano
0 0 * * * /home/kali/桌面/dir/test/ipv4.sh >> ipv4.txt
无限重启Linux
做⼀个像Windows中的⼀样⽆限重启脚本(了解LINUX中的自启动)
#!/bin/bash
while true; do
echo "5秒后重启"
sleep 5
sudo reboot
done
使用nano进行写入
sudo nano /etc/systemd/system/cq.service
写入内容
[Unit]
Description=Infinite Reboot Service
After=network.target
[Service]
ExecStart=/home/kali/桌面/dir/test/cq.sh
Restart=always
User=kali
[Install]
WantedBy=multi-user.target
启用并启动服务
sudo systemctl daemon-reload
sudo systemctl enable cq.service
sudo systemctl start cq.service
失败了,应该是sudo reboot哪里没有权限
使用root
[Unit]
Description=Infinite Reboot Service
After=network.target
[Service]
ExecStart=/home/kali/桌面/dir/test/cq.sh
Restart=always
User=root
[Install]
WantedBy=multi-user.target
可以了,现在只需要写成一个脚本就行了