条件执行
条件执行允许我们通过达到不同的条件来控制脚本的流程。此功能是必不可少的组件之一。否则,我们只能执行一个接一个的命令。
在定义各种条件时,我们指定应针对特定值执行哪些函数或代码段。如果我们达到特定条件,则仅执行该条件的代码,而跳过其他代码。一旦代码部分完成,以下命令将在条件执行之外执行。让我们再看一遍脚本的第一部分并分析一下。
Script.sh
#!/bin/bash
# Check for given argument
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
else
domain=$1
fi
<SNIP>
总之,此代码适用于以下组件:
- #!/bin/bash
- Shebang.
- if-else-fi
- Conditional execution
- echo
- 打印指定输出
- $#
/ $0
/ $1
- 特殊变量
- domain
- 变量
条件执行的条件可以使用变量($#
,$0
,$1
,domain
)、值(values)(0
)和字符串定义,接下来的示例中看到。这些值与我们将在下一节中看到的 comparison operators
(比较运算符) (-eq
) 进行比较。
shebang
shebang 行始终位于每个脚本的顶部,并且始终以“ #!
”开头。此行包含/bin/bash
执行脚本的指定解释器 ( ) 的路径。我们还可以使用 Shebang 定义其他解释器,如 Python、Perl 等。
#!/usr/bin/env python
#!/usr/bin/env perl
If-Else-Fi
最基本的编程任务之一是检查不同的条件来处理这些问题。条件检查在编程语言和脚本语言中通常有两种不同的形式,if-else condition
即case statements
。在伪代码中,if 条件的含义如下:
if [ the number of given arguments equals 0 ]
then
Print: "You need to specify the target domain."
Print: "<empty line>"
Print: "Usage:"
Print: " <name of the script> <domain>"
Exit the script with an error
else
The "domain" variable serves as the alias for the given argument
finish the if-condition
默认情况下,一个If-Else
条件只能包含一个“ If
”,如下一个示例所示。
If-Only.sh
#!/bin/bash
value=$1
if [ $value -gt "10" ]
then
echo "Given argument is greater than 10."
fi
If-Only.sh - 执行
$ bash if-only.sh 5
$ bash if-only.sh 12
Given argument is greater than 10.
添加Elif
orElse
时,我们添加替代项来处理特定值或状态。如果特定值不适用于第一种情况,它将被其他情况捕获。
If-Elif-Else.sh
#!/bin/bash
value=$1
if [ $value -gt "10" ]
then
echo "Given argument is greater than 10."
elif [ $value -lt "10" ]
then
echo "Given argument is less than 10."
else
echo "Given argument is not a number."
fi
If-Elif-Else.sh - 执行
$ bash if-elif-else.sh 5
Given argument is less than 10.
$ bash if-elif-else.sh 12
Given argument is greater than 10.
$ bash if-elif-else.sh HTB
if-elif-else.sh: line 5: [: HTB: integer expression expected
if-elif-else.sh: line 8: [: HTB: integer expression expected
Given argument is not a number.
我们可以扩展我们的脚本并指定几个条件(Several Conditions)。这可能看起来像这样:
Several Conditions - Script.sh
#!/bin/bash
# Check for given argument
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
elif [ $# -eq 1 ]
then
domain=$1
else
echo -e "Too many arguments given."
exit 1
fi
<SNIP>
定义了另一个条件( elif [<condition>];then
),它打印一行,告诉我们(echo -e "..."
)。我们给出了多个参数,并以错误退出程序(exit 1
)
Exercise Script
#!/bin/bash
# Count number of characters in a variable:
# echo $variable | wc -c
# Variable to encode
var="nef892na9s1p9asn2aJs71nIsm"
for counter in {1..40}
do
var=$(echo $var | base64)
done
#Create an "If-Else" condition in the "For"-Loop of the "Exercise Script" that prints you the number of characters of the 35th generated value of the variable "var". Submit the number as the answer.
------
参数、变量和数组
Arguments
bash 脚本的优点是我们始终可以将最多 9 个参数 ( $0
- $9
) 传递给脚本,而无需将它们分配给变量或为这些变量设置相应的要求。9 arguments
因为第一个参数 $0
是为脚本保留的。正如我们在此处看到的,我们需要在 $
变量名称前加上美元符号 ( ) 才能在指定位置使用它。相比之下,分配看起来像这样:
$ ./script.sh ARG1 ARG2 ARG3 ... ARG9
ASSIGNMENTS: $0 $1 $2 $3 ... $9
这意味着我们已经自动将相应的参数分配给这个地方的预定义变量。这些变量称为特殊变量。这些特殊变量用作占位符。如果我们现在再次查看代码部分,我们将看到在哪里使用了哪些参数。
CIDR.sh
#!/bin/bash
# Check for given argument
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
else
domain=$1
fi
<SNIP>
有几种方法可以执行我们的脚本。但是,我们必须先设置脚本的执行权限,然后才能使用其中定义的解释器执行它。
$ chmod +x cidr.sh
CIDR.sh - 无参数执行
./cidr.sh
You need to specify the target domain.
Usage:
cidr.sh <domain>
CIDR.sh - 没有执行权限的执行
$ bash cidr.sh
You need to specify the target domain.
Usage:
cidr.sh <domain>
特殊变量
特殊变量使用内部字段分隔符( IFS
) 来识别参数何时结束以及下一个参数何时开始。Bash 提供了各种特殊变量来帮助编写脚本。其中一些变量是:
IFS | 描述
--- | ---
$#
| 此变量保存传递给脚本的参数数量。
$@
| 此变量可用于检索命令行参数列表。
$n
| 每个命令行参数都可以使用其位置有选择地检索。例如,第一个参数位于$1
。
$$
| 当前执行进程的进程 ID。
$?
| 脚本的退出状态。此变量对于确定命令是否成功很有用。值 0 表示成功执行,而 1 表示失败的结果。
在上面显示的变量中,我们的if-else
条件中有 3 个这样的特殊变量。
IFS | 描述
--- | ---
$#
| 在这种情况下,我们只需要一个需要分配给变量的domain
变量。此变量用于指定我们要使用的目标。如果我们只提供一个 FQDN 作为参数,则该$#
变量的值为1
.
$0
| 这个特殊变量被分配了执行脚本的名称,然后在“ Usage:
”示例中显示。
$1
| 第一个参数由空格分隔,分配给该特殊变量。
变量
我们还看到,在 if-else 循环的末尾,我们将第一个参数的值赋给了名为“ domain
”的变量。变量的赋值没有美元符号 ( $
)。美元符号仅用于允许在其他代码段中使用此变量的对应值。分配变量时,名称和值之间不能有空格。
<SNIP>
else
domain=$1
fi
<SNIP>
与其他编程语言不同,Bash中的变量类型(如“strings
”、“integers
”和“boolean
”)之间没有直接的区别和识别。变量的所有内容都被视为字符串字符。Bash根据是否只分配数字来启用算术函数。声明变量时必须注意,它们not
包含space
。否则,实际变量名将被解释为内部函数或命令。
声明变量 - 错误
$ variable = "this will result with an error."
command not found: variable
声明变量 - 没有错误
$ variable="Declared without an error."
$ echo $variable
Declared without an error.
数组
在Bash中,还可以为单个变量分配多个值。如果我们想扫描多个域或IP地址,这可能是有益的。这些变量称为 arrays
,我们可以用来存储和处理特定类型值的有序序列。数组用以 0
开头的索引标识每个存储项。当我们想给数组组件赋值时,我们用与标准shell变量相同的方法进行。我们所做的就是指定方括号中的字段索引。数组的声明在Bash中如下所示:
Arrays.sh
#!/bin/bash
domains=(www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com www2.inlanefreight.com)
echo ${domains[0]}
我们还可以使用带有大括号中相应索引的变量的索引来单独检索它们。花括号用于变量扩展。
$ ./Arrays.sh
www.inlanefreight.com
请务必注意,单引号 ( '
... '
) 和双引号 ( "
... "
) 可防止数组中的各个值被空格分隔。这意味着单引号和双引号之间的所有空格都将被忽略并作为分配给数组的单个值处理。
#!/bin/bash
domains=("www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com" www2.inlanefreight.com)
echo ${domains[0]}
$ ./Arrays.sh
www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com
------
## 比较运算符
要相互比较特定值,我们需要称为比较运算符的元素。comparison operators
用于确定如何比较定义的值。对于这些运算符,我们区分:
- string
operators
- integer
operators
- file
operators
- boolean
operators
字符串运算符
如果我们比较字符串,那么我们就知道我们希望在相应的值中有什么。
Operator | 描述
--- | ---
==
| 等于
!=
| 不等于
<
| 小于按 ASCII 字母顺序
>
| 大于按 ASCII 字母顺序排列
-z
| 如果字符串为空(null)
-n
| 如果字符串不为空
需要注意的是,我们将给定参数 ( $1
) 的变量放在双引号 ( "$1"
) 中。这告诉 Bash 变量的内容应该作为字符串处理。否则,我们会得到一个错误。
#!/bin/bash
# Check the given argument
if [ "$1" != "HackTheBox" ]
then
echo -e "You need to give 'HackTheBox' as argument."
exit 1
elif [ $# -gt 1 ]
then
echo -e "Too many arguments given."
exit 1
else
domain=$1
echo -e "Success!"
fi
字符串比较运算符“ <
/ >
”仅在双方括号内有效[[ <condition> ]]
。我们可以在 Internet 上找到 ASCII 表,也可以在终端中使用以下命令。我们稍后看一个例子。
$ man ascii
ASCII
代表American Standard Code for Information Interchange
并表示 7 位字符编码。由于每个位可以取两个值,因此有128
不同的位模式,也可以解释为十进制整数0
-127
或十六进制值00
- 7F
。前 32 个 ASCII 字符代码保留为所谓的控制字符。
整数运算符
如果我们知道要比较的值,比较整数对我们非常有用。因此,我们定义接下来的步骤和命令脚本应该如何处理相应的值。
Operator | 描述
--- | ---
-eq
| 等于
-ne
| 不等于
-lt
| 小于
-le
| 小于或等于
-gt
| 大于
-ge
| 大于或等于
#!/bin/bash
# Check the given argument
if [ $# -lt 1 ]
then
echo -e "Number of given arguments is less than 1"
exit 1
elif [ $# -gt 1 ]
then
echo -e "Number of given arguments is greater than 1"
exit 1
else
domain=$1
echo -e "Number of given arguments equals 1"
fi
文件运算符
如果我们想找出特定的权限或者它们是否存在,文件操作符很有用。
Operator | 描述
--- | ---
-e
| 如果文件存在
-f
| 测试它是否是一个文件
-d
| 测试它是否是一个目录
-L
| 测试是否是符号链接
-N
| 检查文件是否在上次读取后被修改
-O
| 如果当前用户拥有该文件
-G
| 如果文件的组 id 匹配当前用户的
-s
| 测试文件的大小是否大于 0
-r
| 测试文件是否具有读取权限
-w
| 测试文件是否有写权限
-x
| 测试文件是否有执行权限
#!/bin/bash
# Check if the specified file exists
if [ -e "$1" ]
then
echo -e "The file exists."
exit 0
else
echo -e "The file does not exist."
exit 2
fi
布尔和逻辑运算符
我们得到一个布尔值“ false
”或“ true
”作为逻辑运算符的结果。Bash 让我们可以使用双方括号来比较字符串[[ <condition> ]]
。要获取这些布尔值,我们可以使用字符串运算符。无论比较是否匹配,我们都会得到布尔值“ false
”或“ true
”。
#!/bin/bash
# Check the boolean value
if [[ -z $1 ]]
then
echo -e "Boolean value: True (is null)"
exit 1
elif [[ $# > 1 ]]
then
echo -e "Boolean value: True (is greater than)"
exit 1
else
domain=$1
echo -e "Boolean value: False (is equal to)"
fi
逻辑运算符
使用逻辑运算符,我们可以在一个中定义多个条件。这意味着我们定义的所有条件都必须匹配,然后才能执行相应的代码。
Operator | 描述
--- | ---
!
|逻辑协商NOT
&&
| 逻辑与
||
| 逻辑或
#!/bin/bash
# Check if the specified file exists and if we have read permissions
if [[ -e "$1" && -r "$1" ]]
then
echo -e "We can read the file that has been specified."
exit 0
elif [[ ! -e "$1" ]]
then
echo -e "The specified file does not exist."
exit 2
elif [[ -e "$1" && ! -r "$1" ]]
then
echo -e "We don't have read permission for this file."
exit 1
else
echo -e "Error occured."
exit 5
fi
练习脚本
#!/bin/bash
var="8dm7KsjU28B7v621Jls"
value="ERmFRMVZ0U2paTlJYTkxDZz09Cg"
for i in {1..40}
do
var=$(echo $var | base64)
#<---- If condition here:
done
在“For”循环中创建一个“If-Else”条件,检查名为“var”的变量是否包含名为“value”的变量的内容。此外,变量“var”必须包含超过 113,450 个字符。如果满足这些条件,则脚本必须打印变量“var”的最后 20 个字符。提交最后 20 个字符作为答案。
算数
在 Bash 中,我们可以使用七种不同arithmetic operators
的方法。这些用于执行不同的数学运算或修改某些整数。
算数运算符
Operator | 描述
| --- | --- |
+
| 添加
-
| 减法
*
| 乘法
/
| 分配
%
| 模数
variable++
| 将变量的值增加 1
variable--
| 将变量的值减 1
我们可以在一个小脚本中总结所有这些运算符:
Arithmetic.sh
#!/bin/bash
increase=1
decrease=1
echo "Addition: 10 + 10 = $((10 + 10))"
echo "Substraction: 10 - 10 = $((10 - 10))"
echo "Multiplication: 10 * 10 = $((10 * 10))"
echo "Division: 10 / 10 = $((10 / 10))"
echo "Modulus: 10 % 4 = $((10 % 4))"
((increase++))
echo "Increase Variable: $increase"
((decrease--))
echo "Decrease Variable: $decrease"
此脚本的输出如下所示:
Arithmetic.sh - 执行
darkinga@htb[/htb]$ ./Arithmetic.sh
Addition: 10 + 10 = 20
Substraction: 10 - 10 = 0
Multiplication: 10 * 10 = 100
Division: 10 / 10 = 1
Modulus: 10 % 4 = 2
Increase Variable: 2
Decrease Variable: 0
我们还可以计算变量的长度。使用这个函数${#variable}
,每个字符都被计数,我们得到变量中的字符总数。
VarLength.sh
#!/bin/bash
htb="HackTheBox"
echo ${#htb}
VarLength.sh
$ ./VarLength.sh
10
如果我们查看我们的CIDR.sh
脚本,我们会发现我们已经多次使用了increase
anddecrease
运算符。这确保了我们稍后将讨论的 while 循环运行并 ping 主机,而变量 " stat
" 的值为1
. 如果 ping 命令以代码结尾0
(成功),我们会收到一条消息,表明变量host is up
和" stat
" 以及变量 " hosts_up
" 和 " hosts_total
" 已更改。
CIDR.sh
<SNIP>
echo -e "\nPinging host(s):"
for host in $cidr_ips
do
stat=1
while [ $stat -eq 1 ]
do
ping -c 2 $host > /dev/null 2>&1
if [ $? -eq 0 ]
then
echo "$host is up."
((stat--))
((hosts_up++))
((hosts_total++))
else
echo "$host is down."
((stat--))
((hosts_total++))
fi
done
done
<SNIP>