环境搭建
虚拟机安装
- 镜像下载网站
- 为了避免环境问题建议 22.04 ,20.04,18.04,16.04 等常见版本 ubuntu 虚拟机环境各准备一份。注意定期更新快照以防意外。
- 虚拟机建议硬盘 256 G 以上,内存也尽量大一些。硬盘大小只是上界,256 G 不是真就占了 256 G,而后期如果硬盘空间不足会很麻烦。
基础工具
vim
sudo apt install vim
gedit
不习惯 vim 的可以使用 gedit 文本编辑器。
sudo apt install gedit
git
sudo apt install git
gcc
sudo apt install gcc
python
ipython 提供了很好的 python 交互命令行,建议安装。
sudo apt install python2 ipython2
sudo apt install python3 ipython3
另外有的版本 ubuntu 的不好安装 pip2 可以使用 get-pip.py
脚本安装。
curl https://bootstrap.pypa.io/get-pip.py --output get-pip.py
sudo python2 get-pip.py
pwn 相关工具
gdb
sudo apt-get install gdb gdb-multiarch
pwntools
注意我这里的 pwntools 是 python2 版本的。
pip install pwntools
这样安装的 pwntools 的 plt 功可能无法正常使用,需要手动安装 Unicorn 库。
pip install unicorn==1.0.3
gdb 插件
主要有 pwndbg,peda,gef ,这里我常用的是 pwndbg 。对于一些版本过于古老导致环境装不上的可以尝试一下 peda 。
先将三个项目的代码都拉取下来。
git clone https://github.com/longld/peda.git
git clone https://github.com/pwndbg/pwndbg.git
git clone https://github.com/hugsy/gef.git
pwndbg 需要运行初始化脚本。
cd pwndbg & sudo ./setup.sh
另外还有一个 pwngdb 插件在调试多线程堆(heapinfoall
命令)的时候很有用,建议安装。
git clone https://github.com/scwuaptx/Pwngdb.git
gdb 在启动的时候会读取当前用户的主目录的 .gdbinit
文件进行 gdb 插件的初始化,这里提供一个配置方案。
source /home/sky123/tools/pwndbg/gdbinit.py
#source /home/sky123/tools/peda/peda.py
#source /home/sky123/tools/gef/gef.py
#source /home/sky123/tools/muslheap/muslheap.py
source /home/sky123/tools/Pwngdb/pwngdb.py
source /home/sky123/tools/Pwngdb/angelheap/gdbinit.py
define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end
注意,以普通用权限和管理员权限启动 gdb 时读取的 .gdbinit
文件的路径是不同的,普通权限读取的是 /home/<username>/.gdbinit
而管理员权限读取的是 /root/.gdbinit
。
gadget 搜索工具
ROPgdbget
安装:
git clone https://github.com/JonathanSalwan/ROPgadget.git
cd ROPgadget
sudo python3 setup.py install
使用:
ROPgadget --binary ntdll.dll > rop
ropper
- 安装:
- 在 pypi 的 ropper 官网上下载 ropper
- 运行安装脚本完成 ropper 安装
python setup.py install
- 使用:
ropper --file ./pwn --nocolor > rop
one_gadget
用于搜索 libc 中能够实现 execve("/bin/sh", (char *[2]) {"/bin/sh", NULL}, NULL);
的效果的跳转地址,由于是采用特征匹配的方法,因此只能是在 libc 中查找。
- 安装:
sudo apt install -y ruby ruby-dev sudo gem install one_gadget
- 使用:可以查找到 gadget 地址以及条件限制。
➜ ~ one_gadget /lib/x86_64-linux-gnu/libc.so.6 0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ) constraints: rsp & 0xf == 0 rcx == NULL rbp == NULL || (u16)[rbp] == NULL 0xebcf1 execve("/bin/sh", r10, [rbp-0x70]) constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [[rbp-0x70]] == NULL || [rbp-0x70] == NULL 0xebcf5 execve("/bin/sh", r10, rdx) constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [rdx] == NULL || rdx == NULL 0xebcf8 execve("/bin/sh", rsi, rdx) constraints: address rbp-0x78 is writable [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
如果 one_gadget
在一个版本的 Ubuntu 中搜索某一版本的glibc 的 gadget 出现如下报错可以尝试换另一个版本的 Ubuntu ,具体原因未知。
seccomp-tools
用于查看和生成程序沙箱规则。
- 安装:
sudo gem install seccomp-tools
- 使用:
seccomp-tools dump ./pwn
LibcSearcher
通过泄露的 libc 中函数的地址来确定 libc 版本。
git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
sudo python3 setup.py install
patchelf
安装:
sudo apt install patchelf
qemu
sudo apt install qemu-user qemu-system
工具使用
vim
功能
命令行模式下的文本编辑器。
- 根据文件扩展名自动判别编程语言。支持代码缩进、代码高亮等功能。
- 使用方式:
vim filename
- 如果已有该文件,则打开它。
- 如果没有该文件,则打开个一个新的文件,并命名为
filename
。
模式
- 一般命令模式
默认模式,按不同字符即可进行不同操作。可以复制、粘贴、删除文本等。 - 编辑模式
- 在一般命令模式里按下
i
,会进入编辑模式。 - 按下
Esc
会退出编辑模式,返回到一般命令模式。
- 在一般命令模式里按下
- 命令行模式
- 在一般命令模式里按下
:/?
三个字母中的任意一个,会进入命令行模式。命令行在最下面。 - 可以查找、替换、保存、退出、配置编辑器等。
- 在一般命令模式里按下
操作
i
:进入编辑模式。Esc
:进入一般命令模式。h
或←
:光标向左移动一个字符。j
或↓
:光标向下移动一个字符。k
或↑
:光标向上移动一个字符。- l 或
→
:光标向右移动一个字符。 n<Space>
:n
表示数字,按下数字后再按空格,光标会向右移动这一行的n
个字符。0
或功能键[Home]
:光标移动到本行开头。$
或功能键[End]
:光标移动到本行末尾。G
:光标移动到最后一行。:n
或nG
:n
为数字,光标移动到第n
行。gg
:光标移动到第一行,相当于1G
。n<Enter>
:n
为数字,光标向下移动n
行。/word
:向光标之下寻找第一个值为word
的字符串。?word
:向光标之上寻找第一个值为word
的字符串。n
:重复前一个查找操作。N
:反向重复前一个查找操作。:n1,n2s/word1/word2/g
:n1
与n2
为数字,在第n1
行与n2
行之间寻找word1
这个字符串,并将该字符串替换为word2
。:1,$s/word1/word2/g
:将全文的word1
替换为word2
。:1,$s/word1/word2/gc
:将全文的word1
替换为word2
,且在替换前要求用户确认。v
:选中文本。d
:删除选中的文本。dd
: 删除当前行。y
:复制选中的文本。yy
: 复制当前行。p
: 将复制的数据在光标的下一行/下一个位置粘贴。u
:撤销。Ctrl + r
:取消撤销。>
:将选中的文本整体向右缩进一次。<
:将选中的文本整体向左缩进一次。:w
:保存。:w!
:强制保存。:q
:退出。:q!
:强制退出。:wq
:保存并退出。:set paste
:设置成粘贴模式,取消代码自动缩进。:set nopaste
:取消粘贴模式,开启代码自动缩进。:set nu
:显示行号。:set nonu
:隐藏行号。gg=G
:将全文代码格式化。:noh
:关闭查找关键词高亮。Ctrl + q
:当 vim 卡死时,可以取消当前正在执行的命令。
异常处理
每次用 vim 编辑文件时,会自动创建一个 .filename.swp
的临时文件。如果打开某个文件时,该文件的 swp
文件已存在,则会报错。此时解决办法有两种:
- 找到正在打开该文件的程序,并退出。
- 直接删掉该
swp
文件即可。
shell
概论
Linux 中常见的 shell 脚本有很多种,常见的有:
- Bourne Shell (
/usr/bin/sh
或/bin/sh
) - Bourne Again Shell (
/bin/bash
) - C Shell (
/usr/bin/csh
) - K Shell (
/usr/bin/ksh
) - zsh (
/usr/bin/zsh
) - …
Linux 系统中一般默认使用 bash ,所以接下来讲解 bash 中的语法。文件开头需要写#! /bin/bash
,指明 bash 为脚本解释器。
注意,不同的 shell 之间语法并不完全兼容,因此需要按照说明用指定的 shell 解释器运行 shell 。
注释
单行注释
每行中 #
之后的内容均是注释。
多行注释
格式:
: <<EOF
第一行注释
第二行注释
第三行注释
EOF
其中 EOF
可以换成其它任意字符串。例如:
: <<abc
第一行注释
第二行注释
第三行注释
abc
: <<!
第一行注释
第二行注释
第三行注释
!
变量
定义变量
定义变量,不需要加 $
符号,例如:
name1='abc' # 单引号定义字符串
name2="abc" # 双引号定义字符串
name3=abc # 也可以不加引号,同样表示字符串
注意:
- 等号两侧不能有空格。
- 等号左边的变量认为是定义,不能加
$
,等号右侧使用的变量要加$
。name=abc name=${name}def echo $name # abcdef
使用变量
使用变量,需要加上 $
符号,或者 ${}
符号。花括号是可选的,主要为了帮助解释器识别变量边界。
name=abc
echo $name # 输出 abc
echo ${name} # 输出 abc
echo ${name}def # 输出 abcdef
只读变量
使用 readonly
或者 declare
可以将变量变为只读。
name=abc
readonly name
declare -r name # 两种写法均可
name=def # 会报错,因为此时name只读
删除变量
unset
可以删除变量。
name=abc
unset name
echo $name # 输出空行
变量类型
- 自定义变量(局部变量):子进程不能访问的变量。
- 环境变量(全局变量):子进程可以访问的变量。
自定义变量改成环境变量:
name=abc # 定义变量
export name # 第一种方法
declare -x name # 第二种方法
环境变量改为自定义变量:
export name=abc # 定义环境变量
declare +x name # 改为自定义变量
效果如下:
$ export a=abc
$ echo $a
abc
$ bash
$ echo $a
abc
$ declare +x a
$ echo $a
abc
$ bash
$ echo $a
字符串
字符串可以用单引号,也可以用双引号,也可以不加引号。
单引号,双引号和不加引号的区别:
- 单引号中的内容会原样输出,不会执行、不会取变量。
- 双引号中的内容可以执行、可以取变量,相当于把变量的值和表达式的结果都拼接到字符串中。
- 不加引号相当于把变量的值和表达式的结果拼接到 shell 语句中执行。
- 双引号内的单引号不需要转义,单引号内的双引号同样不需要转义。
name=abc # 不用abc引号
echo 'hello, $name "hh"' # 单引号字符串,输出 hello, $name "hh"
echo "hello, $name 'hh'" # 双引号字符串,输出 hello, abc 'hh'
获取字符串长度:
name="abc"
echo ${#name} # 输出3
提取子串:
name="abcdefgh"
echo ${name:2:5} # 提取从 2 开始的 5 个字符 "cdefg"
默认变量
文件参数变量
在执行 shell 脚本时,可以向脚本传递参数。$1
是第一个参数,$2
是第二个参数,以此类推。特殊的,$0
是文件名(包含路径)。例如:
创建文件 test.sh
:
#! /bin/bash
echo "文件名:"$0
echo "第一个参数:"$1
echo "第二个参数:"$2
echo "第三个参数:"$3
echo "第四个参数:"$4
然后执行该脚本:
$ chmod +x test.sh
$ ./test.sh 1 2 3 4
文件名:./test.sh
第一个参数:1
第二个参数:2
第三个参数:3
第四个参数:4
其它参数相关变量:
参数 | 说明 |
---|---|
$# | 代表文件传入的参数个数,如上例中值为 4 。 |
$* | 由所有参数构成的用空格隔开的字符串,如上例中值为 "$1 $2 $3 $4" 。 |
$@ | 每个参数分别用双引号括起来的字符串,如上例中值为 "$1" "$2" "$3" "$4" 。 |
$$ | 脚本当前运行的进程 ID 。 |
$? | 上一条命令的退出状态(注意不是 stdout,而是 exit code),0 表示正常退出,其他值表示错误。 |
$(command) | 返回 command 这条命令的 stdout(可嵌套)。 |
`command` | 返回 command 这条命令的 stdout(不可嵌套) |
数组
数组中可以存放多个不同类型的值(数组除外),只支持一维数组,初始化时不需要指明数组大小。数组下标从 0 开始。
定义
数组用小括号表示,元素之间用空格隔开。例如:
array=(abc 123 "def" 456)
也可以直接定义数组中某个元素的值:
array[0]=abc
array[1]=123
array[2]="def"
array[3]=456
读取数组中某个元素的值
格式如下:
${array[index]}
例如:
array=(abc 123 "def" 456)
echo ${array[0]}
echo ${array[1]}
echo ${array[2]}
echo ${array[3]}
读取整个数组
格式:
${array[@]} # 第一种写法
${array[*]} # 第二种写法
例如:
array=(abc 123 "def" 456)
array[3000]=789
echo ${array[@]} # 第一种写法,输出 abc 123 def 456 789
echo ${array[*]} # 第二种写法,输出 abc 123 def 456 789
数组长度
类似于字符串:
${#array[@]} # 第一种写法
${#array[*]} # 第二种写法
例如:
array=(abc 123 "def" 456)
array[3000]=789
echo ${#array[@]} # 第一种写法,输出 5
echo ${#array[*]} # 第二种写法,输出 5
expr 命令
expr
命令用于求表达式的值,格式为:expr 表达式
。
表达式说明:
- 用空格隔开每一项。
- 用反斜杠放在 shell 特定的字符前面(发现表达式运行错误时,可以试试转义或者加
""
或''
)。 - 对包含空格和其他特殊字符的字符串要用引号括起来。
- expr 会在 stdout 中输出结果。如果为逻辑关系表达式,则结果为真时,stdout 输出 1 ,否则输出 0 。
- expr 的 exit code:如果为逻辑关系表达式,则结果为真时,exit code 为 0,否则为 1 。
字符串表达式
length STRING
:返回STRING
的长度。index STRING CHARSET
:CHARSET
中任意单个字符在STRING
中最前面的字符位置,下标从 1 开始。如果在STRING
中完全不存在CHARSET
中的字符,则返回 0 。substr STRING POSITION LENGTH
:返回STRING
字符串中从POSITION
(下标从 1 开始)开始,长度最大为LENGTH
的子串。如果POSITION
或LENGTH
为负数,0 或非数值,则返回空字符串。
示例:
str="Hello World!"
echo $(expr length "$str") # ``不是单引号,表示执行该命令,输出 12
echo $(expr index "$str" aWd) # 输出 7,下标从 1 开始
echo $(expr substr "$str" 2 3) # 输出 ell
整数表达式
expr
支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
+ -
:加减运算。两端参数会转换为整数,如果转换失败则报错。* / %
:乘,除,取模运算。两端参数会转换为整数,如果转换失败则报错。()
:可以改变优先级,但需要用反斜杠转义。
示例:
a=3
b=4
echo `expr $a + $b` # 输出7
echo `expr $a - $b` # 输出-1
echo `expr $a \* $b` # 输出12,*需要转义
echo `expr $a / $b` # 输出0,整除
echo `expr $a % $b` # 输出3
echo `expr \( $a + 1 \) \* \( $b + 1 \)` # 输出20,值为(a + 1) * (b + 1)
逻辑关系表达式
|
:如果第一个参数非空且非 0 ,则返回第一个参数的值,否则返回第二个参数的值,但要求第二个参数的值也是非空或非 0 ,否则返回 0 。如果第一个参数是非空或非 0 时,不会计算第二个参数。&
:如果两个参数都非空且非 0 ,则返回第一个参数,否则返回 0 。如果第一个参为 0 或为空,则不会计算第二个参数。< <= = == != >= >
:比较两端的参数,如果为 true ,则返回 1 ,否则返回 0 。==
是=
的同义词。expr
首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。()
:可以改变优先级,但需要用反斜杠转义
a=3
b=4
echo $(expr $a \> $b) # 输出 0,>需要转义
echo $(expr $a '<' $b) # 输出 1,也可以将特殊字符用引号引起来
echo $(expr $a '>=' $b) # 输出 0
echo $(expr $a \<\= $b) # 输出 1
c=0
d=5
echo $(expr $c \& $d) # 输出 0
echo $(expr $a \& $b) # 输出 3
echo $(expr $c \| $d) # 输出 5
echo $(expr $a \| $b) # 输出 3
read 命令
read
命令用于从标准输入中读取单行数据。当读到文件结束符时,exit code 为 1 ,否则为 0 。
参数说明
-p
: 后面可以接提示信息-t
:后面跟秒数,定义输入字符的等待时间,超过等待时间后会自动忽略此命令
实例:
sky123@ubuntu:~$ read -p "Please input your name: " -t 30 name
Please input your name: sky123
sky123@ubuntu:~$ echo $name
sky123
echo 命令
echo
用于输出字符串。命令格式:echo STRING
。
显示换行
-e
开启转义,单双引号内均会转义。
echo -e 'a\nb\nc\n'
输出结果:
a
b
c
显示不换行
-e
开启转义,\c
不换行。
echo -e "a\c"
echo -e 'b\c'
echo -e c
输出结果:
abc
printf 命令
printf
命令用于格式化输出,类似于 C/C++ 中的 printf
函数。
命令格式:printf format-string [arguments...]
。
sky123@sky123:~$ a=`printf "%10lf" 123`
sky123@sky123:~$ echo $a
123.000000
test 命令与判断符号 []
逻辑运算符 && 和 ||
&&
表示与,||
表示或- 二者具有短路原则:
expr1 && expr2
:当expr1
为假时,直接忽略expr2
。expr1 || expr2
:当expr1
为真时,直接忽略expr2
。
- 表达式的 exit code 为 0 ,表示真;为非零,表示假。(与 C/C++ 中的定义相反)
test 命令
在命令行中输入 man test
,可以查看 test
命令的用法。
test
命令用于判断文件类型,以及对变量做比较。test
命令用 exit code 返回结果,而不是使用 stdout 。0 表示真,非 0 表示假。
例如:
sky123@sky123:~$ test 2 -lt 3 # 为真,返回值为 0
sky123@sky123:~$ echo $? # 输出上个命令的返回值,输出 0
0
test
命令常与短路原则结合,下面这条命令可以判断文件 test.sh
是否存在。
test -e test.sh && echo "exist" || echo "Not exist"
test
的判断参数:
- 文件类型判断,命令格式:
test -e filename
(判断文件是否存在)
测试参数 | 代表意义 |
---|---|
-e | 文件是否存在 |
-f | 是否为文件 |
-d | 是否为目录 |
- 文件权限判断,命令格式:
test -r filename
(判断文件是否可读)
测试参数 | 代表意义 |
---|---|
-r | 文件是否可读 |
-w | 文件是否可写 |
-x | 文件是否可执行 |
-s | 是否为非空文件 |
- 整数间的比较,命令格式:
test $a -eq $b
(a
是否等于b
)
测试参数 | 代表意义 |
---|---|
-eq | a 是否等于 b |
-ne | a 是否不等于 b |
-gt | a 是否大于 b |
-lt | a 是否小于 b |
-ge | a 是否大于等于 b |
-le | a 是否小于等于 b |
- 字符串比较
测试参数 | 代表意义 |
---|---|
test -z STRING | 判断 STRING 是否为空,如果为空,则返回 true |
test -n STRING | 判断 STRING 是否非空,如果非空,则返回 true(-n 可以省略) |
test str1 == str2 | 判断 str1 是否等于 str2 |
test str1 != str2 | 判断 str1 是否不等于 str2 |
- 多重条件判定,命令格式:
test -r filename -a -x filename
测试参数 | 代表意义 |
---|---|
-a | 两条件是否同时成立 |
-o | 两条件是否至少一个成立 |
! | 取反。如 test ! -x file ,当 file 不可执行时,返回 true |
判断符号[]
[]
与 test
用法几乎一模一样,更常用于 if 语句中。另外 [[]]
是 []
的加强版,支持的特性更多。
例如:
sky123@sky123:~$ [ 2 -lt 3 ] # 为真,返回值为 0
sky123@sky123:~$ echo $? # 输出上个命令的返回值,输出 0
0
[]
命令同样可以与短路原则结合,下面这条命令可以判断文件 test.sh
是否存在。
[ -e test.sh ] && echo "exist" || echo "Not exist"
注意:
[]
内的每一项都要用空格隔开- 中括号内的变量,最好用双引号括起来
- 中括号内的常数,最好用单或双引号括起来
name="123 456"
[ $name == "123 456" ] # 错误,等价于 [ 123 456 == "123 456" ],参数太多
[ "$name" == "123 456" ] # 正确
判断语句
if…then 形式
类似于 C/C++ 中的 if-else 语句。
- 单层 if
- 命令格式:
if condition; then 语句1 语句2 ... fi
- 示例:
a=3 b=4 if [ "$a" -lt "$b" ] && [ "$a" -gt 2 ]; then echo ${a}在范围内 fi
- 输出结果:
3在范围内
- 命令格式:
- 单层 if-else
- 命令格式
if condition; then 语句1 语句2 ... else 语句1 语句2 ... fi
- 示例:
a=3 b=4 if ! [ "$a" -lt "$b" ]; then echo ${a}不小于${b} else echo ${a}小于${b} fi
- 输出结果:
3小于4
- 命令格式
- 多层 if-elif-elif-else
- 命令格式
if condition; then 语句1 语句2 ... elif condition; then 语句1 语句2 ... elif condition; then 语句1 语句2 else 语句1 语句2 ... fi
- 示例:
a=4 if [ $a -eq 1 ]; then echo ${a}等于1 elif [ $a -eq 2 ]; then echo ${a}等于2 elif [ $a -eq 3 ]; then echo ${a}等于3 else echo 其他 fi
- 命令格式
- 输出结果:
其他
case…esac 形式
类似于 C/C++ 中的 switch 语句。
- 命令格式:
case $变量名称 in 值1) 语句1 语句2 ... ;; # 类似于C/C++中的break 值2) 语句1 语句2 ... ;; *) # 类似于C/C++中的default 语句1 语句2 ... ;; esac
- 示例:
a=4 case $a in 1) echo ${a}等于1 ;; 2) echo ${a}等于2 ;; 3) echo ${a}等于3 ;; *) echo 其他 ;; esac
- 输出结果:
其他
循环语句
for…in…do…done
- 命令格式:
for var in val1 val2 val3; do 语句1 语句2 ... done
- 示例 1,输出
a
、2
、cc
,每个元素一行:for i in a 2 cc; do echo $i done
- 示例 2,输出当前路径下的所有文件名,每个文件名一行:
for file in $(ls); do echo $file done
- 示例 3,输出1-10:
for i in $(seq 1 10); do echo $i done
- 示例 4,使用
{1..10}
或者{a..z}
:for i in {a..z}; do echo $i done
for ((…;…;…)) do…done
- 命令格式:
for ((expression; condition; expression)); do 语句1 语句2 done
- 示例,输出1-10,每个数占一行:
for ((i = 1; i <= 10; i++)); do echo $i done
while…do…done 循环
- 命令格式:
while condition; do 语句1 语句2 ... done
- 示例,文件结束符为 Ctrl+d ,输入文件结束符后 read 指令返回 false 。
while read name do echo $name done
until…do…done 循环
当条件为真时结束。
- 命令格式:
until condition; do 语句1 语句2 ... done
- 示例,当用户输入
yes
或者YES
时结束,否则一直等待读入。until [ "${word}" == "yes" ] || [ "${word}" == "YES" ]; do read -p "Please input yes/YES to stop this program: " word done
break 命令
跳出当前一层循环,注意与 C/C++ 不同的是:break
不能跳出 case
语句。
- 示例:
该示例每读入非 EOF 的字符串,会输出一遍 1-7 。该程序可以输入 Ctrl+d 文件结束符来结束,也可以直接用 Ctrl+c 杀掉该进程。while read name do for ((i=1;i<=10;i++)) do case $i in 8) break ;; *) echo $i ;; esac done done
continue 命令
跳出当前循环。
示例:
for ((i = 1; i <= 10; i++)); do
if [ $(expr $i % 2) -eq 0 ]; then
continue
fi
echo $i
done
该程序输出1-10中的所有奇数。
函数
bash 中的函数类似于 C/C++ 中的函数,但 return 的返回值与 C/C++ 不同,返回的是exit code ,取值为 0-255 ,0 表示正常结束。
如果想获取函数的输出结果,可以通过 echo
输出到 stdout 中,然后通过 $(function_name)
来获取 stdout 中的结果。
函数的 return 值可以通过 $?
来获取。
命令格式:
[function] func_name() { # function关键字可以省略
语句1
语句2
...
}
不获取 return 值和 stdout 值
- 示例:
func() { echo abc } func
- 输出结果:
abc
获取 return 值和 stdout 值
不写 return 时,默认 return 0 。
- 示例:
func() { echo abc return 123 } output=$(func) ret=$? echo "output = $output" echo "return = $ret"
- 输出结果:
output = abc return = 123
函数的输入参数
在函数内,$1
表示第一个输入参数,$2
表示第二个输入参数,依此类推。
注意:函数内的 $0
仍然是文件名,而不是函数名。
- 示例:
func() { # 递归计算 $1 + ($1 - 1) + ($1 - 2) + ... + 0 word="" while [ "${word}" != 'y' ] && [ "${word}" != 'n' ]; do read -p "要进入func($1)函数吗?请输入y/n:" word done if [ "$word" == 'n' ]; then echo 0 return 0 fi if [ $1 -le 0 ]; then echo 0 return 0 fi echo $(expr $(func $(expr $1 - 1)) + $1) } echo $(func 10)
- 输出结果:
55
函数内的局部变量
可以在函数内定义局部变量,作用范围仅在当前函数内。可以在递归函数中定义局部变量。
命令格式:local 变量名=变量值
。
例如:
func() {
local a=abc
echo $a
}
func
echo $a
输出结果:
abc
第一行为函数内的 a
变量,第二行为函数外调用 a
变量,会发现此时该变量不存在。
exit 命令
exit
命令用来退出当前 shell 进程,并返回一个退出状态;使用 $?
可以接收这个退出状态。exit
命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0 。exit
退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
文件重定向
每个进程默认打开 3 个文件描述符:
- stdin 标准输入,从命令行读取数据,文件描述符为 0 。
- stdout 标准输出,向命令行输出数据,文件描述符为 1 。
- stderr 标准错误输出,向命令行输出数据,文件描述符为 2 。
可以用文件重定向将这三个文件重定向到其他文件中。
重定向命令
command > file
:将 stdout 重定向到 file 中。command < file
:将 stdin 重定向到 file 中。command >> file
:将 stdout 以追加方式重定向到 file 中。command n> file
:将文件描述符 n 重定向到 file 中。command n>> file
:将文件描述符 n 以追加方式重定向到 file 中。exec 1>&0
:stdout 重定向到 stdin 。
输入和输出重定向
echo -e "Hello \c" > output.txt # 将stdout重定向到output.txt中
echo "World" >> output.txt # 将字符串追加到output.txt中
read str < output.txt # 从output.txt中读取字符串
echo $str # 输出结果:Hello World
同时重定向 stdin 和 stdout
创建 bash 脚本:
#! /bin/bash
read a
read b
echo $(expr "$a" + "$b")
创建 input.txt
,里面的内容为:
3
4
执行命令:
$ ./test.sh < input.txt > output.txt # 从input.txt中读取内容,将输出写入output.txt中
$ cat output.txt # 查看output.txt中的内容
7
引入外部脚本
类似于 C/C++ 中的 include 操作,bash 也可以引入其他文件中的代码。
语法格式:
. filename # 注意点和文件名之间有一个空格
或
source filename
docker
ssh
ssh 登录
基本用法
远程登录服务器:
ssh user@hostname
user
: 用户名hostname
: IP 地址或域名
第一次登录时会提示:
The authenticity of host '123.57.47.211 (123.57.47.211)' can't be established.
ECDSA key fingerprint is SHA256:iy237yysfCe013/l+kpDGfEG9xxHxm0dnxnAbJTPpG8.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
输入 yes
,然后回车即可。这样会将该服务器的信息记录在 ~/.ssh/known_hosts
文件中。然后输入密码即可登录到远程服务器中。
ssh 默认登录端口号为 22 。如果想登录某一特定端口可以用 -p
参数指定。
ssh user@hostname -p 22
配置文件
创建文件 ~/.ssh/config
,然后在文件中输入:
Host myserver1
HostName IP地址或域名
User 用户名
Host myserver2
HostName IP地址或域名
User 用户名
之后再使用服务器时,可以直接使用别名 myserver1
、myserver2
。
密钥登录
创建密钥:
ssh-keygen
然后一直回车即可。
执行结束后,~/.ssh/
目录下会多两个文件:
id_rsa
:私钥id_rsa.pub
:公钥
之后想免密码登录哪个服务器,就将公钥传给哪个服务器即可。
例如,想免密登录 myserver
服务器。则将公钥中的内容,复制到 myserver
中的 ~/.ssh/authorized_keys
文件里即可。
也可以使用如下命令添加公钥:
ssh-copy-id myserver
执行命令
命令格式:ssh user@hostname command
。
scp传文件
命令格式:
- 将
source
路径下的文件复制到destination
中:scp source destination
。 - 一次复制多个文件:
scp source1 source2 destination
。 - 复制文件夹:
scp -r ~/tmp myserver:/home/
。 - 指定服务器的端口号:
scp -P 22 source1 source2 destination
。
注意:scp
的-r
、-P
等参数尽量加在source
和destination
之前。
git
git 基本概念
- 工作区:仓库的目录。工作区是独立于各个分支的。
- 暂存区:数据暂时存放的区域,类似于工作区写入版本库前的缓存区。暂存区是独立于各个分支的。
- 版本库:存放所有已经提交到本地仓库的代码版本
- 版本结构:树结构,树中每个节点代表一个代码版本。
git 常用命令
git config --global user.name xxx
:设置全局用户名,信息记录在~/.gitconfig
文件中。git config --global user.email xxx@xxx.com
:设置全局邮箱地址,信息记录在~/.gitconfig
文件中。git init
:将当前目录配置成 git 仓库,信息记录在隐藏的.git
文件夹中。git add XX
:将XX
文件添加到暂存区。git add .
:将所有待加入暂存区的文件加入暂存区。git rm --cached XX
:将文件从仓库索引目录中删掉。git commit -m "给自己看的备注信息"
:将暂存区的内容提交到当前分支。git status
:查看仓库状态。git diff XX
:查看XX
文件相对于暂存区修改了哪些内容。git log
:查看当前分支的所有版本。git reflog
:查看HEAD
指针的移动历史(包括被回滚的版本)。git reset --hard HEAD^
或git reset --hard HEAD~
:将代码库回滚到上一个版本。git reset --hard HEAD^^
:往上回滚两次,以此类推。git reset --hard HEAD~100
:往上回滚 100 个版本。git reset --hard 版本号
:回滚到某一特定版本。git checkout -- XX
或git restore XX
:将XX
文件尚未加入暂存区的修改全部撤销。git remote add origin git@git.acwing.com:xxx/XXX.git
:将本地仓库关联到远程仓库。git push -u
(第一次需要-u以后不需要):将当前分支推送到远程仓库。git push origin branch_name
:将本地的某个分支推送到远程仓库。git clone git@git.acwing.com:xxx/XXX.git
:将远程仓库XXX
下载到当前目录下。git checkout -b branch_name
:创建并切换到branch_name
这个分支。git branch
:查看所有分支和当前所处分支。git checkout branch_name
:切换到branch_name
这个分支。git merge branch_name
:将分支branch_name
合并到当前分支上。git branch -d branch_name
:删除本地仓库的branch_name
分支。git branch branch_name
:创建新分支。git push --set-upstream origin branch_name
:设置本地的branch_name
分支对应远程仓库的branch_name
分支。git push -d origin branch_name
:删除远程仓库的branch_name
分支。git pull
:将远程仓库的当前分支与本地仓库的当前分支合并。git pull origin branch_name
:将远程仓库的branch_name
分支与本地仓库的当前分支合并。git branch --set-upstream-to=origin/branch_name1 branch_name2
:将远程的branch_name1
分支与本地的branch_name2
分支对应。git checkout -t origin/branch_name
:将远程的branch_name
分支拉取到本地。git stash
:将工作区和暂存区中尚未提交的修改存入栈中。git stash apply
:将栈顶存储的修改恢复到当前分支,但不删除栈顶元素。git stash drop
:删除栈顶存储的修改。git stash pop
:将栈顶存储的修改恢复到当前分支,同时删除栈顶元素。git stash list
:查看栈抱歉,似乎在我的回答中遗漏了一部分内容。以下是完整的。git config --global user.name xxx
:设置全局用户名,信息记录在~/.gitconfig
文件中。git config --global user.email xxx@xxx.com
:设置全局邮箱地址,信息记录在~/.gitconfig
文件中。git init
:将当前目录配置成 git 仓库,信息记录在隐藏的.git
文件夹中。git add XX
:将XX
文件添加到暂存区。git add .
:将所有待加入暂存区的文件加入暂存区。git rm --cached XX
:将文件从仓库索引目录中删掉。git commit -m "给自己看的备注信息"
:将暂存区的内容提交到当前分支。git status
:查看仓库状态。git diff XX
:查看XX
文件相对于暂存区修改了哪些内容。git log
:查看当前分支的所有版本。git reflog
:查看HEAD指针的移动历史(包括被回滚的版本)。git reset --hard HEAD^
或git reset --hard HEAD~
:将代码库回滚到上一个版本。git reset --hard HEAD^^
:往上回滚两次,以此类推。git reset --hard HEAD~100
:往上回滚 100 个版本。git reset --hard 版本号
:回滚到某一特定版本。git checkout -- XX
或git restore XX
:将XX
文件尚未加入暂存区的修改全部撤销。git remote add origin git@git.acwing.com:xxx/XXX.git
:将本地仓库关联到远程仓库。git push -u
(第一次需要-u
以后不需要):将当前分支推送到远程仓库。git push origin branch_name
:将本地的某个分支推送到远程仓库。git clone git@git.acwing.com:xxx/XXX.git
:将远程仓库XXX
下载到当前目录下。git checkout -b branch_name
:创建并切换到branch_name
这个分支。git branch
:查看所有分支和当前所处分支。git checkout branch_name
:切换到branch_name
这个分支。git merge branch_name
:将分支branch_name
合并到当前分支上。git branch -d branch_name
:删除本地仓库的branch_name
分支。git branch branch_name
:创建新分支。git push --set-upstream origin branch_name
:设置本地的branch_name
分支对应远程仓库的branch_name分支。git push -d origin branch_name
:删除远程仓库的branch_name
分支。git pull
:将远程仓库的当前分支与本地仓库的当前分支合并。git pull origin branch_name
:将远程仓库的branch_name
分支与本地仓库的当前分支合并。git branch --set-upstream-to=origin/branch_name1 branch_name2
:将远程的branch_name1
分支与本地的branch_name2
分支对应。git checkout -t origin/branch_name
:将远程的branch_name
分支拉取到本地。git stash
:将工作区和暂存区中尚未提交的修改存入栈中。git stash apply
:将栈顶存储的修改恢复到当前分支,但不删除栈顶元素。git stash drop
:删除栈顶存储的修改。git stash pop
:将栈顶存储的修改恢复到当前分支,同时删除栈顶元素。git stash list
:查看栈中所有元素。
管道、环境变量与常用命令
管道
概念
管道类似于文件重定向,可以将前一个命令的 stdout 重定向到下一个命令的 stdin 。
要点
- 管道命令仅处理 stdout ,会忽略 stderr 。
- 管道右边的命令必须能接受 stdin 。
- 多个管道命令可以串联。
与文件重定向的区别
- 文件重定向左边为命令,右边为文件。
- 管道左右两边均为命令,左边有 stdout ,右边有 stdin 。
举例
统计当前目录下所有 python 文件的总行数
find . -name '*.py' | xargs cat | wc -l
环境变量
概念
Linux系统中会用很多环境变量来记录配置信息。环境变量类似于全局变量,可以被各个进程访问到。我们可以通过修改环境变量来方便地修改系统配置。
查看
列出当前环境下的所有环境变量:
env # 显示当前用户的变量
set # 显示当前 shell 的变量,包括当前用户的变量;
export # 显示当前导出成用户变量的 shell 变量
输出某个环境变量的值
例如环境变量 PATH
的值:
echo $PATH
修改
-
环境变量的定义、修改、删除操作可以参考 shell 中对变量的操作。
-
为了将对环境变量的修改应用到未来所有环境下,可以将修改命令放到
~/.bashrc
文件中。修改完~/.bashrc
文件后,记得执行source ~/.bashrc
,来将修改应用到当前的 bash 环境下。 -
为何将修改命令放到~/.bashrc,就可以确保修改会影响未来所有的环境呢?
- 每次启动 bash ,都会先执行
~/.bashrc
。 - 每次 ssh 登陆远程服务器,都会启动一个 bash 命令行给我们。
- 每次 tmux 新开一个 pane ,都会启动一个 bash 命令行给我们。
- 所以未来所有新开的环境都会加载我们修改的内容。
- 每次启动 bash ,都会先执行
常见环境变量
HOME
:用户的家目录。PATH
:可执行文件(命令)的存储路径。路径与路径之间用:分隔。当某个可执行文件同时出现在多个路径中时,会选择从左到右数第一个路径中的执行。下列所有存储路径的环境变量,均采用从左到右的优先顺序。LD_LIBRARY_PATH
:用于指定动态链接库(.so
文件)的路径,其内容是以冒号分隔的路径列表。C_INCLUDE_PATH
:C 语言的头文件路径,内容是以冒号分隔的路径列表。CPLUS_INCLUDE_PATH
:CPP 的头文件路径,内容是以冒号分隔的路径列表。PYTHONPATH
:Python 导入包的路径,内容是以冒号分隔的路径列表。JAVA_HOME
:jdk 的安装目录。CLASSPATH
:存放 Java 导入类的路径,内容是以冒号分隔的路径列表。