Shell 脚本编程

Shell 脚本编程

简介

Shell 运行于 terminal 中,是用户使用 Linux 的桥梁。这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

本篇博客用来记录 Shell 脚本的学习。如果过时、错误、新学会的东西,我知道的就会去修改增加。

我写的一些shell脚本地址:sun-ruijiang/some-shell-scripts: 一些有用的shell脚本 (github.com)

环境

  • 系统:WSL 1 Ubuntu 20.04
  • 编辑器:VS code
  • 插件:shellman(智能补全) + shellcheck(自动检错) + shell-format(格式化文档),在VS code内部 terminal 执行 Shell script。

Shell 的 hello word

1
2
#!/bin/bash
echo "Hello world"

#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行

echo命令用于向窗口输出文本。

如何执行?

一是作为可执行程序去执行,这种方式默认解释器。

1
2
chmod +x ./test.sh  # 使脚本具有执行权限
./test.sh # 执行脚本

二是作为解释器参数执行,这种方式需要制定解释器。

1
2
/bin/sh test.sh
/bin/php test.php

image-20211207103135839

Shell 基础

Shell 变量

定义赋值

1
2
3
4
5
6
7
# 直接赋值
variable_name="example" # 注意不能有空格
# 语句赋值
for variable_name in `ls /etc`
for variable_name in $(ls /etc)
# 只读变量
readonly variable_name

读取元素

1
2
echo $variable_name
echo ${variable_name} # 推荐加上{}以区分外界

重置变量

1
unset variable_name    # 不能删除只读变量

分类

  1. 环境变量,可以在创建他们的 Shell 及其派生出来的任意子进程 Shell 中使用,退出这个 Shell 环境变量就会丢失。如果想要永久保存这些环境变量,可以在用户家目录下的.bashrc(对当前用户生效)或者/etc/profile(对所有用户生效)文件中定义,在用户登录时这些变量会被初始化。
  2. 局部变量,仅在当前 Shell 进程中有效,其他 Shell 启动的程序不能访问。
  3. shell 变量,初始化的shell的变量,既有环境变量,又有局部变量。

字符串

拼接字符串

1
2
3
4
5
your_name="runoob"
# 使用双引号拼接
greeting="hello, ${your_name} !"
# 使用单引号拼接,单引号不会使用Shell变量
greeting='hello, '$your_name' !'

获取字符串长度

1
2
string="abcd"
echo ${#string} #输出 4

提取子字符串

1
2
string="runoob is a great site"
echo ${string:1:4} # 从字符串第 2 个字符开始截取 4 个字符:unoo

查找子字符串

1
2
string="runoob is a great site"
echo $(expr index "$string" io) # 查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):输出 4。

数组

定义数组

1
2
3
4
5
6
# 空格分隔
array_name=(value0 value1 value2 value3)
# 单独定义
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

读取数组

1
2
valuen=${array_name[n]}   
echo ${array_name[@]} # 使用@符号可以获取数组中的所有元素

获取数组的长度

1
2
3
4
5
6
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

注释

1
2
3
4
5
# 单行注释

:<<EOF
多行注释
EOF

Shell 传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。

n 代表一个数字,0为输入的文件名(包括路径,这个不算参数),1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

另外,还有几个特殊字符用来处理参数:

参数处理 说明
$# 传递到脚本的参数个数
$* 显示所有向脚本传递的参数。 以”$1 $2 … $n”的形式输出所有参数。
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 显示所有向脚本传递的参数。 以”$1” “$2” … “$n”的形式输出所有参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

Shell 基本运算符

expr已经过时了,考虑重新用$(())${}[[]]

算数运算符(只支持数字)

1
2
3
4
5
6
7
val=$((2 + 2))
val=$((2 * 2))
val=$((2 / 2))
val=$((2 * 2))
val=$((2 % 2))
val=$((2 == 2))
val=$((2 != 2))

关系运算符

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

布尔运算符

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

逻辑运算符

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100

字符串运算符

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否不相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否不为 0,不为 0 返回 true。 [ -n “$a” ] 返回 true。
$ 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

文件测试运算符

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

流程控制

if-elif-else

注意:如果else没有执行语句,就不要写。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
a=10
b=1
if [[ $a == "${b}0" ]]; then
echo "a 等于 b"
elif [ $a -gt $b ]; then
echo "a 大于 b"
elif [ $a -lt $b ]; then
echo "a 小于 b"
else
echo "没有符合的条件"
fi

for 循环

1
2
3
4
5
#!/bin/bash
command='ls'
for loop in $(${command}); do
echo "The value is: ${loop}"
done

while 语句

1
2
3
4
5
6
#!/bin/bash
int=$((1 + 0))
while ((int <= 5)); do
echo $int
((int++))
done

until 循环

直到条件为true时停止

1
2
3
4
5
6
#!/bin/bash
a=0
until [ ! $a -lt 10 ]; do # 1 < 10
echo $a
a=$((a + 1))
done

case-esac

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
echo '输入:'
read -r aNum
case $aNum in
1)
echo '你选择了 1'
;;
2)
echo '你选择了 2'
;;
3)
echo '你选择了 3'
;;
4)
echo '你选择了 4'
;;
"google")
echo "Google 搜索"
;;
*)
echo '你没有输入 1 到 4 之间的数字'
;;
esac

break 和 continue

break 命令跳出所有循环

continue 命令仅仅跳出当前循环,执行下一个循环

函数

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
funWithReturn() {
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read -r aNum
echo "输入第二个数字: "
read -r anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $((aNum + anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !" # $?为上一个命令状态返回码,会被覆盖

输入/输出重定向

命令 说明
command > file 将输出重定向到 file。
command < file 将输入重定向到 file。
command >> file 将输出以追加的方式重定向到 file。
n > file 将文件描述符为 n 的文件重定向到 file。
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m 将输出文件 m 和 n 合并。
n <& m 将输入文件 m 和 n 合并。
<< tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。

注意:文件描述符 0 通常是标准输入(STD IN),1 是标准输出(STD OUT),2 是标准错误输出(STD ERR)。

文件包含

和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

1
2
. filename      # 注意点号和文件名中间有一空格
source filename

应用

如果有C/C++基础,这些都是非常快速地学会的,接下来是我编写的一些脚本

WSL 1 下的 GDB 插件切换

select.sh文件,思路是将.gdbinit替换source ~/software/my_dbg/peda/peda.py这一行的文件

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
38
39
40
#!/bin/bash

echo -e "Please choose one mode of GDB?\n1.peda 2.gef 3.pwndbg"
read -r -p "Input your choice:" num
case "${num}" in
1)
name='1'
;;
2)
name='2'
;;
3)
name='3'
;;
*)
echo -e "Error!!!!\nPleasse input right number!"
;;
esac
gdbinitfile=~/.gdbinit

peda="source ~/software/my_dbg/peda/peda.py"
gef="source /home/sung/software/my_dbg/gef/.gdbinit-gef.py"
pwndbg="source /home/sung/software/my_dbg/pwndbg/gdbinit.py"

sign=$(cat $gdbinitfile | grep -n "# this place is controled by user's shell")
number=${sign:0:1}
location=$((number + 1))

if [ $name -eq "1" ]; then
sed -i "${location}c $peda" $gdbinitfile
echo -e "Please enjoy the peda!\n"
elif [ $name -eq "2" ]; then
sed -i "${location}c $gef" $gdbinitfile
echo -e "Please enjoy the gef!\n"
else
sed -i "${location}c $pwndbg" $gdbinitfile
echo -e "Please enjoy the pwndbg!\n"
fi
/usr/bin/gdb

然后通过alias工具设置alias gdb=~/software/my_dbg/select.sh

image-20211208091656121

setup.sh文件,实现永久化,需要在~/.bashrc里追加这个内容

1
2
3
#!/bin/bash
echo alias gdb='~/software/my_dbg/select.sh' >> ~/.bashrc
source ~/.bashrc
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2021 Sung
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信