SHELL June 10, 2021

Shell脚本编程

Words count 37k Reading time 34 mins. Read count 0

一、什么是shell

以前没有系统学习shell,只是在做pwn时一般是直接调用system(“/bin/sh”)实现root权限的获取,所以觉得/bin/sh就是getshell。

Shell脚本中很多开头有#!/bin/sh,#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。个人感觉shell有点像python,所以就着类比学习一下,

二、一般规则

1、变量命名和C差不多,赋值变量直接写名字赋值,使用变量用${变量名}的形式。
1
2
3
4
5
6
7
8
skill = "king"
for skill in Ada Coffe Action Java; do
echo "I am good at ${skill}Script"
done
#I am good at AdaScript
#I am good at CoffeScript
#I am good at ActionScript
#I am good at JavaScript
2、使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
1
2
3
4
#!/bin/bash
myUrl="https://www.google.com"
readonly myUrl
myUrl="https://www.runoob.com" #报错/bin/sh: NAME: This variable is read only.
3、删除变量
1
2
3
#!/bin/sh
myUrl="https://www.runoob.com"
unset myUrl #删完不能再$使用了
4、字符串

好用的字符串,一般是双引号为主可以转义和拼接

1
2
3
aa="run"
bb="hello \"$aa\""
echo $bb

字符串拼接

1
2
3
4
5
6
7
8
$string="runoob is a great site"
king="victor"
aa="run $king"
bb="hello \"$aa\""
echo $bb $aa
hhh=$king" "$string
echo $hhh
#victor runoob is a great site

计算字符串长度,用#,相当于len()

1
2
3
king="victor"
echo ${#king}
#6

根据下标范围提取子串,可以类比python的写法

1
2
3
4
string="runoob is a great site"
kk=${string:1:4} #
echo $kk
expr substr "$string" 1 5

查找字母第一次出现的位置

1
2
string="runoob is a great site"
expr index "$string" run
5、shell的数组

在 Shell 中,用括号来表示数组,数组元素用”空格”符号分割开。可以看成python中的列表(*表示全部,#表示len())

1
2
3
4
5
6
7
8
value=(1 "king" 3 4 5)
echo $value
echo ${value[*]}
echo ${value[1]}
value[2]=7
echo ${value[*]}
echo ${#value[*]}
echo ${#value[1]}
6、注释方式:#、!、EOF、`均可
1
2
3
4
5
6
# value=“king”
:<<!
value=(1 "king" 3 4 5)
echo $value
echo ${value[*]}
!
7、脚本传递参数: @表示全部参数,*全部参数表示一行
1
2
3
4
5
6
7
8
9
echo "$0 $1 $2"
echo "$*"
for i in "$@";do
echo $i
done

echo $$ #脚本运行的当前进程号
echo $# #传递的参数个数
echo $? #函数的返回值~
8、运算符表达式
  • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。

  • 完整的表达式要被 `包含,注意这个字符不是常用的单引号,在 Esc 键下边。

    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
    #算术运算、逻辑运算、布尔运算、关系运算
    val=`expr 100 % 2 + 2 - 6 \* 3 / 2`
    echo $val
    if [ $val == -7 ];then
    echo "good!"
    fi

    if [[ $val -ne -10 && $val -lt -1 || $val -gt -12 || $val -eq -7 || $val -ge -7 ]];then
    echo "this is good!!"
    fi

    if [ $val -ne -10 -a $val -lt -1 -o $val != -9 ];then
    echo "yes"
    fi
    #字符串运算
    if [ -z $a ]
    then
    echo "-z $a : 字符串长度为 0"
    else
    echo "-z $a : 字符串长度不为 0"
    fi
    if [ -n "$a" ] #记得加双引号
    then
    echo "-n $a : 字符串长度不为 0"
    else
    echo "-n $a : 字符串长度为 0"
    fi

    关于文件运算符的描述:

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

不如实践来的快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if [ -e 1.sh ];then
echo "exit"
if [ -s 1.sh ];then
echo "full"
if [ -d 1.sh ];then
echo "dir"
else
echo "no dir"
if [ -x 1.sh ];then
echo "excutable!"
else
echo "No excutable!"
fi
fi
else
echo "null"
fi
else
echo "no exist"
fi
9、echo、printf输出字符串总结

能否引用变量 | 能否引用转移符 | 能否引用文本格式符(如:换行符、制表符)

单引号 | 否 | 否 | 否

双引号 | 能 | 能 | 能

无引号 | 能 | 能 | 否

1
2
3
4
5
6
7
8
read -p "Input name and pwd:" -n 10 -t 10 -s name password #-n表示长度,-t表示时间,-s表示隐藏 
echo -e "\nyour name:${name}" #-e是否开启转义
echo -e "your password:${password}"
echo -e "okk\nbb"
echo "hello world!" > 2.sh
echo '$name\"'
echo `ls`
printf "%-10s %-8s %-4s\n" king aman 65kg
10、test命令的使用

和[]作用一样的,其中$[]中括号里面可以进行算术运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
num1=100
num2=200
if test $[num1+200] -eq $[num2+100];then
echo "equal!"
else
echo "Nonequal!"
fi

#类比于(()):
num1=100
num2=200
if (($[num1+200]==$[num2+100]));then
echo "equal!"
else
echo "Nonequal!"
fi
11、流程控制的书写

其中还是(())最好用,类似c语言的写法

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
41
42
43
44
45
46
#if的脚本书写的形式
val=10
if [ $val == -7 ];then
echo "good!"
fi
#if的命令行书写的形式
if [ -e 1.sh ];then echo "true"; fi

#所有样式的循环写法
for i in $(ls);do
echo $i
done

number=1
while [[ $number -le 5 ]];do
echo "number:$number"
# number=`expr $number + 1`
number=$[number+1]
# let "number=number+1"
done
number=10
if (($number<=11));then
echo "5555"
fi

aa=0
until (($aa>=10));do #do while写法
echo $aa
let "aa++"
done

while true;do
read -p "input number:" number
case "$number" in
"777") echo "666"
break;;
"7") echo "777";;
"9") echo "888";;
"*") echo "All!!"
continue;;
esac
done

for ((i=0;i<6;i++));do
echo $i
done
12、函数调用
$# 传递到脚本或函数的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误,存函数返回值

第11个参数起都要${11}这样。

1
2
3
4
5
6
7
8
9
10
11
function1(){
echo "第一个参数$1"
echo "第二个参数$2"
echo "第十个参数$10"
echo "第十一个参数${11}"
echo "参数总数$#个"
return $(($1+$2))
}

function1 10 20 30 40 50 60 70 80 90 100 110
echo "number-->$?"

函数与命令的执行结果可以作为条件语句使用。要注意的是,和 C 语言不同,shell 语言中 0 代表 true,0 以外的值代表 false。

13、关于输入输出的重定向
类 型 符 号 作 用
标准输出重定向 command >file 以覆盖的方式,把 command 的正确输出结果输出到 file 文件中。
command >>file 以追加的方式,把 command 的正确输出结果输出到 file 文件中。
标准错误输出重定向 command 2>file 以覆盖的方式,把 command 的错误信息输出到 file 文件中。
command 2>>file 以追加的方式,把 command 的错误信息输出到 file 文件中。
正确输出和错误信息同时保存 command >file 2>&1 以覆盖的方式,把正确输出和错误信息同时保存到同一个文件(file)中。
command >>file 2>&1 以追加的方式,把正确输出和错误信息同时保存到同一个文件(file)中。
command >file1 2>file2 以覆盖的方式,把正确的输出结果输出到 file1 文件中,把错误信息输出到 file2 文件中。
command >>file1 2>>file2 以追加的方式,把正确的输出结果输出到 file1 文件中,把错误信息输出到 file2 文件中。

直接上代码:

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
echo "king" > name.txt
echo "V1ct0r" >> name.txt

read password < password.txt
echo $password >> name.txt
cat name.txt

cat 777.sh >>error.log 2>&1

./7.sh >>success.log 2>>error.log #正确和错误的分开装

./7.sh >& /dev/nul #linux下的垃圾箱

wc -l <<eof #eof的标志值可以自行设置
1
2
3
4
5
eof

#在遇到ctf题目关闭输出流时
#close(1)
#我们需要进行输出重定向的操作
#cat flag 1>&2 将输出流重定向到报错流
#/bin/sh 1>&0 将输出流重定向到输入流
14、关于代码复用、库的使用
1
2
3
4
. ./7.sh   #先声明一下需要使用到的文件,还可以用source命令
echo "myname-->"$myname
myfunc 77
echo "return" $?

这里只要把文件载入进来了就可以直接用里面的值了。

三、变量类型

运行shell时,会同时存在三种变量:

  • 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  • 2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
  • 3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

四、方法和易错点总结

在shell脚本要运行linux的命令的话,可以有三种方法:

1
2
3
4
5
ls
`ls`
$(ls)
#但是有参数的智能用最原始的方法:
cat name.txt

进行数值运算也有3种方法:

1
2
3
4
5
6
#实现number=number+1的效果
number=1
number=`expr $number + 1`
number=$[number+1]
let "number=number+1"
sumber=$((number+1))

进行条件判断有2种方法:

1
2
if [[ $number -le 5 ]] #shell的正规军要求
if (($number<=5)) #要求更低,简直就是c语言的翻版

之所以有些shell命令不能直接运行是因为:

image-20210611173033182

/bin/sh链接到了dash而不是bash,所以脚本李敏#!后面写解析器时可以自己定义为bash。

0%