git使用

原文链接:git使用
发布时间:2021-08-29 11:25:18

目录

原理

对于一个使用5年svn的人来说,突然使用git,确实不太习惯,而且他们的设计方式上有很大的不同。

他们最大的不同有2点:

  • svn没有分支
  • svn没有本地仓库

分支

在svn中,没有分支,都是保存一个一个文件,这些文件是平行存放的。而在git中,你进入一个仓库,要切换到你需要的分支中。那么这些文件的存储也比svn要复杂,据我观察,大概是只存储不同分支代码的不同部分。

  • git branch develop 创建develop分支
  • git checkout develop 切换到develop分支

本地仓库

对于svn而言,就只有一个仓库,就是中心仓库,所有开发者的都提交到这个仓库。git则不同,所有开发者在本地有自己的仓库,可以提交自己的更新,在需要的时候再推送到远程仓库。举例:

当切换一个分支后,就可以修改代码和提交更新了,假设修改了main.go这个文件。

  • git add main.go 增加代码到暂存区,类似于svn add
  • git commit -m’fix a bug’ 提交更新到本地仓库
  • git push 提交更新到远程仓库

可以看出,与svn不同,多了第三步推送到远程仓库。在svn中,第二步就可以提交到远程仓库。所以,这里就是本地仓库的作用。因为git是分布式的,有自己的本地仓库,所有的更新可以不提交到中心仓库,每个开发者可以离线工作,不依赖于中心仓库。

所以更新代码需要2个过程:

  • 代码更新到本地仓库
  • 代码更新到远程仓库

撤销

有几个关于撤销的操作。

撤销add

git add了一个不需要的文件main.exe,需要撤销。

  • git reset HEAD main.exe 撤回main.exe文件
  • git reset HEAD 撤回所有add的文件

撤销commit

注意此时,还没有git push到远程仓库。

git commit 了一个不需要的文件,需要撤回上一个git commit 操作。
git reset –soft HEAD^ 修改的代码依然在文件中

如果需要撤回多个git commit,操作如下:
git reset –soft commit_id

这里需要查看你需要回滚到哪个commit id,使用git log查看。

撤销push

这里指已经推送到远端的commit,如何撤销?需要两步:

  • git reset –hard HEAD^ 或者 git reset –hard commit_id
  • git push origin HEAD –force

注意,这里使用了–hard 参数,一经回滚,原来的代码的就消失了,不会留在原来的文件中,使用是要注意

关于HEAD

HEAD可以理解为一个指针,指向当前分支的最新commit。

删除

  • git rm main.exe 删除文件
  • git mv main.exe mian.exe_bak 文件重命名

对比

关于对比,这里需要明确几个概念。

  • 工作区 当前编辑的代码文件
  • 暂存区 暂存编辑后的更新
  • 本地仓库 本地的代码仓库
  • 远程仓库 远程的代码仓库

举例:当修改main.go之后,使用git diff 命令,可以看到修改的内容。然后使用git add main.go,修改就保存了暂存区,再使用git diff就没有任何内容了,因为此时工作区和暂存区内容一致了。所以,引申出2个对比:

  • 工作区和本地仓库对比
  • 暂存区和本地仓库对比

总结如下:

  • git diff 查看工作区和暂存区(git add)的变化
  • git diff –cached 查看暂存区(git add)和上一次commit版本变化
  • git diff HEAD 查看工作区和上一次commit版本的变化
  • git diff master develop main.go 比较master develop分支下的main.go的不同

修改message

有时候刚刚提交的日志描述不准确,需要修改。
git commit –amend 修改上一个提交的log

有时候需要合并多次提交的log:
git rebase -i commit_start commit_end

即这个commit区间内的commit的合并成一个commit,如果commit_end缺省的话,默认是HEAD.

其他常用操作

  • git status 查看状态
  • git log 查看提交记录
  • git branch 查看当前所在分支
  • git clone 克隆仓库
  • git show commit_id 查看一个提交的详细记录
  • git remote -v 查看远端地址

一个简单的socket编程demo

原文链接:一个简单的socket编程demo
发布时间:2018-12-16 16:44:03

记录这个demo,相当于是保留一份代码片段,以后可以随时拿来用,验证新的想法。人的大脑是很不靠谱的,经常记错东西,需要保留一个索引,放在这里。

服务端

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
```cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>

int main(int argc, char *argv[])
{
if (argc < 3)
{
printf("USAGE:./server 127.0.0.1 5555\n");
return -1;
}
//解析参数
int nPort = atoi(argv[2]);

struct in_addr addr;
//得到的是网络字节序的IP
if (inet_aton(argv[1], &addr) < 0)
{
printf("addr[%s] is invalid\n", argv[1]);
return -1;
}

int listenfd = 0, connfd = 0;
struct sockaddr_in serv_addr;

char sendBuff[1025];
time_t ticks;

listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
memset(sendBuff, '0', sizeof(sendBuff));

serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = addr.s_addr;
serv_addr.sin_port = htons(nPort);

bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

listen(listenfd, 10);

while(1)
{
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
printf("new client connected.\n");
ticks = time(NULL);
snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
write(connfd, sendBuff, strlen(sendBuff));

close(connfd);
sleep(1);
}
} // server.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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
```cpp
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
int sockfd = 0, n = 0;
char recvBuff[1024];
struct sockaddr_in serv_addr;

if(argc < 3)
{
printf("\n Usage: %s 127.0.0.1 9999\n",argv[0]);
return 1;
}

//解析参数
int nPort = atoi(argv[2]);

struct in_addr addr;
if (inet_aton(argv[1], &addr) < 0)//得到的是网络字节序的IP
{
printf("addr[%s] is invalid\n", argv[1]);
return -1;
}

memset(recvBuff, '0',sizeof(recvBuff));
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Error : Could not create socket \n");
return 1;
}

memset(&serv_addr, '0', sizeof(serv_addr));

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(nPort);
serv_addr.sin_addr.s_addr = addr.s_addr;

/* if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
{
printf("\n inet_pton error occured\n");
return 1;
} 这个支持IPv6
*/
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
printf("\n Error : Connect Failed \n");
return 1;
}

while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
{
recvBuff[n] = 0;
if(fputs(recvBuff, stdout) == EOF)
{
printf("\n Error : Fputs error\n");
}
}

if(n < 0)
{
printf("\n Read error \n");
}

return 0;
} // client.c

mysql使用笔记

原文链接:mysql使用笔记
发布时间:2018-12-16 16:24:10

常用界面工具

有时候为了方面,可以使用界面工具查看修改mysql,推荐两个工具:

Navicat 需要破解

HeidiSQL 开源免费

shell操作

远程登录

1
2
```sql
mysql -u username -p123456 -h 192.168.97.200 -P 3300 -D dbname ## 注意 -p后面无空格,紧跟密码

查看数据库和表

1
2
3
4
5
6
7
8
9
10
11
12
13
```sql
show databases;

show tables;

use database_name; # 进入这个数据库

desc table_name; # 查看表结构

status; # 查看当前数据库的名称、ip、端口等等信息

select database(); #查看当前的数据库名称 这是调用函数

查看存储过程或者函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
```sql
SHOW CREATE { PROCEDURE | FUNCTION } sp_name ;

//例子
SHOW CREATE PROCEDURE GetTableReset\G;
*************************** 1. row ***************************
Procedure: GetTableReset
sql_mode:
Create Procedure: NULL
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)

因为没有root权限,所以 Create ProcedureNULL,看不到具体的定义。

常用函数

1
2
3
4
5
6
7
8
9
10
11
```sql
-- unix时间戳转为日期
select FROM_UNIX(timestamp) ;
-- 获取unix时间戳
select UNIX_TIMESTAMP();

-- 字符串IP转为整数
select INET_ATON("192.168.1.1")
-- 整数IP转为字符串IP
select INET_NTOA(12343445)

补充–函数和存储过程的区别

存储过程,包括的多条SQL,可以返回结果集,调用存储过程,就像调用sql一样。

而函数则不同,函数强调的是计算获得一个结果,和内置的函数类似,比如sum count等等。

1:限制

存储函数和存储过程统称为存储例程(store routine),存储函数的限制比较多,例如不能用临时表,只能用表变量,而存储过程的限制较少,存储过程的实现功能要复杂些,而函数的实现功能针对性比较强

2:返回值不同

存储函数必须有返回值,且仅返回一个结果值

存储过程可以没有返回值,但是能返回结果集(out,inout)

3:调用时的不同

存储函数嵌入在SQL中使用,可以在select 存储函数名(变量值);

存储过程通过call语句调用 call 存储过程名

4:参数的不同

存储函数的参数类型类似于IN参数

存储过程的参数类型有三种

1: in 数据只是从外部传入内部使用(值传递),可以是数值也可以是变量

2: out 只允许过程内部使用(不用外部数据),给外部使用的(引用传递:外部的数据会被先清空才会进入到内部),只能是变量

3: inout 外部可以在内部使用,内部修改的也可以给外部使用,典型的引用 传递,只能传递变量

pragma pack和内存对齐

原文链接:pragma pack和内存对齐
发布时间:2018-12-16 15:47:53

概述

内存对齐和CPU有关系,可以认为内存对齐的大小就是CPU一次读取内存的大小,例如32CPU一次读取4字节,那么内存的对齐方式就4字节,64位就是8字节。本质上是保证CPU使用尽量少的次数将某一个变量取出来。关于结构体内存对齐的方式,只需要把握两点即可:

  • 每个成员变量的对齐原则,即成员的偏移量计算。参见成员对齐原则。
  • 整个结构体的对齐原则,即结构大小的计算。参见整体对齐原则。

pragma pack(n)的作用

上面说了,编译默认对齐方式是4字节(32位系统)或者8字节(64)位系统,但是通过pragma pack可以自己定义内存的对齐方式。

#pragma pack(n) // n = 1,2,4,8,16……

1

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
//#pragma pack(2)
//#pragma pack(4)
struct node{

char f;
int e;
short a;
char d;
};

int main(void)
{
node n;
printf("len=%d\n", sizeof(node));
return 0;
}

// 输出:
#pragma pack(2) 时, len = 10
#pragma pack(4) 时, len = 12
#pragma pack(8) 时, len = 12
当 不使用#pragma pack(n) 时, len = 12

内存对齐的原则

对照上面的实例,下面讲解具体原则。

1、成员对齐原则

对于结构体的各个成员,第一个成员的偏移量是0,后面的成员其当前偏移量必须是当前成员类型大小的整数倍。

上面的案例中,在默认的对齐方式中,e的偏移量必须是4的倍数,因为e占4个字节,所以f要补齐3个字节,使e偏移量为4。f偏移量为0,a的偏移量为2的倍数是8,d的偏移量为1的倍数,为11。

2、整体对齐原则

结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍。

上面可知,d的偏移量为11,其实11个字节就可以存下,但是整体的大小必须是最大成员e的倍数,因此至少为12,d需要补齐一个字节。

3、自定义对齐方式

即使用#pragma pack(n)预编译指令。

如果程序中使用#pragma pack(n),则忽略上面的两个原则。只需遵循以下面两个新原则:

1、成员对齐

  • 当 n >= 成员变量的大小, 该变量的偏移量仍然是自身大小的整数倍。
  • 当 n < 成员变量的大小, 该变量的偏移量是n的整数倍。

2、整体对齐

  • 当 n >= 最大成员变量的大小, 整个结构体大小仍然是最大成员的整数倍。
  • 当 n < 最大成员变量的大小, 整个结构体大小是n的整数倍。

定时任务管理工具crontab

原文链接:定时任务管理工具crontab
发布时间:2018-12-16 13:47:56

文章目录

cron

cron是linux的后台程序用来执行提前计划好的命令。什么意思呢?

就是你提前写好一些脚本,放在特定的目录,cron就会按照你设置的时间来定期执行。举一个简单的应用场景,我自己启动一个程序,需要每1分钟,检查程序是否存在,不存在则拉起这个程序。就可以使用cron来完成。

crontab

上面说了cron是执行各种定时任务的,但是你如何把你的任务传递给cron呢?就需要使用crontable命令了。crontable用来添加、删除、修改定时任务的。

定时执行命令

1
[KentZhang@LOCAL-192-168-97-2]$ sudo crontab -u KentZhang -e  ## 打开文件
1
* * * * * touch /home/KentZhang/log.txt  ## 添加一行内容

然后保存退出,这个任务就生效了,表示每分钟会执行touch一次,修改log.txt文件的时间戳,这就是一个简单的定时任务。

定时执行脚本

1
* * * * * /bin/bash /home/KentZhang/myjob.sh  > /dev/null 2>&1 &

表示每分钟执行myjob.sh这个脚本一次。

注意

当使用crontab打开的用户的定时任务表后,一个文件是可以写入多个定时任务的。

1
2
* * * * * touch /home/KentZhang/log.txt  ## 添加一行内容
* * * * * /bin/bash /home/KentZhang/myjob.sh > /dev/null 2>&1 &

定时的语法格式

上面的案例都是每分钟执行一次,其实还可以设置其他多种、复杂的时间格式。

1
2
3
4
5
6
7
.---------------- minute (0 - 59)
| .------------- hour (0 - 23)
| | .---------- day of month (1 - 31)
| | | .------- month (1 - 12) OR jan,feb,mar ...
| | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue ...
| | | | |
* * * * * command to be executed

分、时、天、月、周

1:每1分钟执行一次myCommand

1
* * * * * myCommand

2:每小时的第3和第15分钟执行

1
3,15 * * * * myCommand

3:在上午8点到11点的第3和第15分钟执行

1
3,15 8-11 * * * myCommand

4:每隔两天的上午8点到11点的第3和第15分钟执行

1
3,15 8-11 */2  *  * myCommand

5:每周一上午8点到11点的第3和第15分钟执行

1
3,15 8-11 * * 1 myCommand

6:每晚的21:30重启smb

1
30 21 * * * /etc/init.d/smb restart

7:每月1、10、22日的4 : 45重启smb

1
45 4 1,10,22 * * /etc/init.d/smb restart

8:每周六、周日的1 : 10重启smb

1
10 1 * * 6,0 /etc/init.d/smb restart

9:每天18 : 00至23 : 00之间每隔30分钟重启smb

1
0,30 18-23 * * * /etc/init.d/smb restart

10:每星期六的晚上11 : 00 pm重启smb

1
0 23 * * 6 /etc/init.d/smb restart

11:每一小时重启smb

1
* */1 * * * /etc/init.d/smb restart

12:晚上11点到早上7点之间,每隔一小时重启smb

1
* 23-7/1 * * * /etc/init.d/smb restart

其他说明

几个重要的目录

  • /var/spool/cron/ 目录下存放的是每个用户包括root的crontab任务,每个任务以创建者的名字命名
  • /etc/cron.d/ 这个目录用来存放任何要执行的crontab文件或脚本。
  • 我们还可以把脚本放在/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly目录中,让它每小时/天/星期、月执行一次。

直接编辑任务

1
crontab -u KentZhang -e

这个编辑appuser的定时任务,相当于打开 /var/spool/cron/KentZhang文件,因此可以不使用crontab命令,直接打开这个文件进行修改,保存后,依然会生效。

这种方式之所以可行,是因为corn会监控任务表文件的变化,一旦任务表有变化,会重新加载这个修改的任务表。

实现golang中memset函数

原文链接:实现golang中memset函数
发布时间:2018-12-16 12:11:11

C语言中的memset

在C/C++中,有一个memset函数,常常用来清空一段内存。

1
2
3
4
5
6
7
8
9
```cpp
#include <string.h>
// 将s的前n个字节用c填充
void *memset(void *s, int c, size_t n);

// 用法举例
int arr[100];
// 将数组内存全部置为0
memset(&arr, 0, sizeof(arr));

由于是s是void* 类型的指针,那么任何类型的内存都可以清空。但是在golang中,类型检查特别严格,并没有提供类似的函数。

Golang中一种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
```go
// 可以填充数组、结构体
func MemSet(s unsafe.Pointer, c byte, n uintptr) {
ptr := uintptr(s)
var i uintptr
for i = 0; i < n; i++ {
pByte := (*byte)(unsafe.Pointer(ptr + i))
*pByte = c
}
}

func main() {
var arr [10]int32
MemSet(unsafe.Pointer(&arr), 1, unsafe.Sizeof(arr))
fmt.Printf("%+v\n", arr)

MemSet(unsafe.Pointer(&arr), 0, unsafe.Sizeof(arr))
fmt.Printf("%+v\n", arr)
}
// 输出:
[KentZhang@LOCAL-192-168-97-2]$ go run memset.go
[16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009]
[0 0 0 0 0 0 0 0 0 0]

三点说明:

  • unsafe.Pointer 是一种通用的指针类型,可以将任何数据类型的指针转化为 unsafe.Pointer,相当于C语言中的void*指针。
  • unsafe.Pointer不能进行计算,即指针不能加减偏移计算,所以还必须转化为uintptr类型,uintptr本质上是一个整数,于是就可以计算了。
  • uintptr是整数,不是指针,无法进行取值赋值运算,还要转化为unsafe.Pointer,再转为对应类型的指针。

shell脚本使用总结

原文链接:shell脚本使用总结
发布时间:2018-12-09 15:42:05

指定解释器

1
2
#!/bin/bash
## 这行代码必须加入文件头

定义或者赋值变量

因为shell是一个脚本语言,只有赋值了,才能成为一个变量,不像其他静态语言可以声明定义一个变量,其实就是赋值,变量必须赋值,才能成为变量,然后才能使用。修改变量,也是赋值。

注意,等号两边不能有空格

1
2
3
4
5
6
7
8
num=123
name="Kent"

## 也可以通过其他命令赋值,pwd两边的是反单引号,也称重音符,ascii是96
path=`pwd`

## 这里对num再次赋值,其实就是修改变量
num=456

使用变量

1
2
3
4
5
6
echo $num
echo $name
echo $path

## 加上大括号,是为了确定变量的边界
echo "my name is ${name}Zhang"

特殊变量

变量含义$0当前脚本的文件名$n传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。$#传递给脚本或函数的参数个数。$传递给脚本或函数的所有参数。$@传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $ 稍有不同,下面将会讲到。$?上个命令的退出状态,或函数的返回值。$$当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

字符串操作

0、 单引号和双引号

对于定义字符串,双引号和单引号都可以。但是单引号有如下的问题:

单引号会原样输出内容,忽略里面的变量和转义符号。

1
echo 'my name is ${name}Zhang\n'

输出:
my name is ${name}Zhang\n

1、拼接

1
2
3
4
5
6
7
8
fname="Kent"
sname="Zhang"

fullname="${fname}${sname}"
echo $fullname

fullname="${fname}Zhang"
echo $fullname

2、获取字符串长度

1
2
len=${#fullname}
echo $len

3、截取切片

1
2
fisrtname=${fullname:0:4} ## 表示从fullname,0位置开始,到4位置结束截取这部分字符串,不包含4的位置。
echo $firstname

4、查找子串

这个好像只能查找 单个字符,不能查找字符串,输出0表示查不到,输出1表示第1个字符。

1
echo `expr index "$fullname" K`

输出:
1

5、字符串替换

1
2
3
4
5
6
str="apple, tree, apple tree apple"
echo ${str/apple/APPLE} # 替换第一次出现的apple
echo ${str//apple/APPLE} # 替换所有apple

echo ${str/#apple/APPLE} # 如果字符串str以apple开头,则用APPLE替换它
echo ${str/%apple/APPLE} # 如果字符串str以apple结尾,则用APPLE替换它

逻辑判断

关于[[ ]]和[ ]判断判断表达式,尽量使用前者,因为前者更加通用,后者在特殊情况有可能无法正确执行。有关参考:

https://blog.csdn.net/taiyang1987912/article/details/39551385

一般形式

1
2
3
4
5
if [[ "$1" == "stop" ]]; then ## $1 表示第一个参数
exit 0
else
echo "go to here"
fi

多个逻辑分支

1
2
3
4
5
6
7
if [[ "$1" == "stop" ]]; then ## $1 表示第一个参数
echo 0
else [[ "$1" == "start" ]]; then
echo 1
else
echo "go to here"
fi

字符串比较

1
2
3
4
5
str1 = str2      ## 当两个串有相同内容、长度时为真
str1 != str2      ## 当串str1和str2不等时为真
-n str1        ## 当串的长度大于0时为真(串非空)
-z str1        ## 当串的长度为0时为真(空串)
str1         ## 当串str1为非空时为真

数字比较

1
2
3
4
5
6
int1 -eq int2    ## 两数相等为真
int1 -ne int2    ## 两数不等为真
int1 -gt int2    ## int1大于int2为真
int1 -ge int2    ## int1大于等于int2为真
int1 -lt int2    ## int1小于int2为真
int1 -le int2    ## int1小于等于int2为真

文件判断

1
2
3
4
5
6
7
8
9
-r file     ## 用户可读为真
-w file     ## 用户可写为真
-x file     ## 用户可执行为真
-f file     ## 文件为正规文件为真
-d file     ## 文件为目录为真
-c file     ## 文件为字符特殊文件为真
-b file     ## 文件为块特殊文件为真
-s file     ## 文件大小非0时为真
-t file     ## 当文件描述符(默认为1)指定的设备为终端时为真

包含文件

c++中,可以包含一个头文件,调用其他文件中的函数,shell同样可以,达到复用代码的效果,相当于把别的文件包含到了本文件中,其中变量函数都可以直接使用。

两种方式:

1、. filename //英文句号,后面一个空格

2、source filename

数组

定义

1
2
3
4
5
6
7
8
array1=(value0 value1 value2 value3)

array2=(
value0
value1
value2
value3
)

修改

1
2
array1[0]="1000"
array1[1]="1001"

使用

1
num=${array1[0]}

获取长度

1
2
# 取得数组元素的个数
length=${#array1[@]}

函数

定义函数,也可以去掉function关键字

1
2
3
4
5
6
7
8
9
10
11
function Sum()
{
## 注意,这里$1 $2表示函数传进来的参数
return $(($1+$2))
}

## 调用函数 传入2个参数
Sum 2 3

## 获取函数返回值,注意这里只能通过 $?获取返回值
echo $?

switch 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case "$1" in
start)
echo "start_service"
;;
stop)
echo "stop_service"
;;
restart)
echo "restart_service"
;;
*)
echo "unknown argument $1"
echo "usage:$0 [start|stop|restart|start_mock|stop_mock|restart_mock]"
esac

循环

for

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
#!/bin/sh
for i in 1 2 3 4 5 ## 遍历数字列表
do
echo "Looping ... number $i"
done

for i in hello 1 44 2 goodbye ## 遍历字符串列表
do
echo "Looping ... i is set to $i"
done

for file in $(ls) ## 通过shell命令获取一个列表
do
echo $file
done

for (( c=1; c<=5; c++ )) ## 类似C风格的循环
do
echo "c=$c"
done

array=(1 4 6 9 10) ## 遍历数组
for i in ${array[@]}
do
echo "array number $i"
done

while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
n=1
while [ $n -le 10 ] ## n小于10就执行循环
do
echo $n
let n++ #或者写作n=$(( $n + 1 ))
done

## 冒号表示死循环
while :
do
echo I love you forever
done

## true命令永远是true,也是死循环
while /bin/true
do
echo I love you forever
done

until

until与while相反,until是当条件为false就继续循环,为true就结束循环。

1
2
3
4
5
6
7
8
#!/bin/bash
a=0

until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done

后续

未完待续…

使用dlv调试golang程序

原文链接:使用dlv调试golang程序
发布时间:2018-12-09 12:37:35

1、编译选项

1
go build -gcflags=all="-N -l"  ## 必须这样编译,才能用gdb打印出变量,第二个是小写的L,不是大写的i

需要加编译选项,类似gcc中的 -g选项,加入调试信息。关于如何安装dlv,请自行百度或者谷歌。

2、使用dlv调试

dlv的功能介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Usage:
dlv [command]

Available Commands:
attach Attach to running process and begin debugging.
connect Connect to a headless debug server.
core Examine a core dump.
debug Compile and begin debugging main package in current directory, or the package specified.
exec Execute a precompiled binary, and begin a debug session.
help Help about any command
run Deprecated command. Use 'debug' instead.
test Compile test binary and begin debugging program.
trace Compile and begin tracing program.
version Prints version.

由于dlv的功能比较多,我只介绍我常用的几个,包括 attach、debug、exec、core、test,这5个。

1、dlv attach

这个相当于gdb -p 或者 gdb attach ,即跟踪一个正在运行的程序。这中用法也是很常见,对于一个后台程序,它已经运行很久了,此时你需要查看程序内部的一些状态,只能借助attach.

1
dlv attach $PID  ## 后面的进程的ID

2、dlv debug

1
dlv debug main/hundredwar.go ## 先编译,后启动调试

3、dlv exec

1
2
3
dlv exec ./HundredsServer  ## 直接启动调试

dlv exec ./HundredsServer -- -port 8888 -c /home/config.xml ## 后面加参数启动调试

与dlv debug区别就是,dlv debug 编译一个临时可执行文件,然后启动调试,类似与go run。

4、dlv core

用来调试core文件,但是想让go程序生成core文件,需要配置一个环境变量,默认go程序不会产生core文件。

1
export GOTRACEBACK=crash

只有定义这个环境变量,go程序才会coredump。如果在协程入口定义defer函数,然后recover也不会产生core文件。

1
2
3
4
5
6
7
8
9
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("panic error\n")
}
}()
var p *int = nil
fmt.Printf("p=%d\n", *p) // 访问nil指责
}() // 这个协程不会生产core文件

因为recover的作用就是捕获异常,然后进行错误处理,所以不会产生coredump,这个需要注意。这个也是golang的一大特色吧,捕获异常,避免程序coredump。

调试coredump文件

关于调试core文件,其实和C/C++差不多,最后都是找到发生的函数帧,定位到具体某一行代码。但是golang稍有不同,对于golang的core文件需要先定位到时哪一个goroutine发生了异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dlv core ./Server core.26945  ## 启动

Type 'help' for list of commands.
(dlv) goroutines ## 查看所有goroutine
[12 goroutines]
Goroutine 1 - User: /usr/local/go/src/runtime/time.go:102 time.Sleep (0x440d16)
Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
Goroutine 5 - User: /usr/local/go/src/runtime/time.go:102 time.Sleep (0x440d16)
Goroutine 6 - User: /usr/local/go/src/runtime/time.go:102 time.Sleep (0x440d16)
Goroutine 7 - User: /usr/local/go/src/runtime/time.go:100 time.Sleep (0x440ccd)
Goroutine 8 - User: ./time.go:114 main.main.func2 (0x4a33cb) (thread 21239)
Goroutine 9 - User: /usr/local/go/src/runtime/lock_futex.go:227 runtime.notetsleepg (0x40ce42)
Goroutine 17 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
Goroutine 33 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)
Goroutine 49 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x42834a)

上面,可以看到所以的goroutine,需要找到自己的业务代码所在的goroutine,这里需要判断,不像C/C++的core文件,可以定义定位到所在的函数栈。这里的是 Goroutine 8 。

需要进入 8 号 goroutine。

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
(dlv) goroutine 8  ## 切换到 8 号 goroutine
Switched from 0 to 8 (thread 21239)

(dlv) bt ## 查看栈帧
0 0x0000000000450774 in runtime.raise
at /usr/local/go/src/runtime/sys_linux_amd64.s:159
1 0x000000000044cea0 in runtime.systemstack_switch
at /usr/local/go/src/runtime/asm_amd64.s:363
2 0x00000000004265ba in runtime.dopanic
at /usr/local/go/src/runtime/panic.go:597
3 0x00000000004261f1 in runtime.gopanic
at /usr/local/go/src/runtime/panic.go:551
4 0x00000000004250ce in runtime.panicmem
at /usr/local/go/src/runtime/panic.go:63
5 0x0000000000438baa in runtime.sigpanic
at /usr/local/go/src/runtime/signal_unix.go:388
6 0x00000000004a33cb in main.main.func2
at ./time.go:114 ## 显然6号栈是自己的业务代码
7 0x000000000044f6d1 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:2361

(dlv) frame 6 ## 进入6号栈帧
> runtime.raise() /usr/local/go/src/runtime/sys_linux_amd64.s:159 (PC: 0x450774)
Warning: debugging optimized function
Frame 6: ./time.go:114 (PC: 4a33cb)
109: // if r := recover(); r != nil {
110:
111: // }
112: // }()
113: var p *int = nil
=> 114: fmt.Printf("p=%d\n", *p) ## 这里发生了异常
115: }()
116:
117: time.Sleep(10000000)
118: }

5、dlv test

dlv test 也很有特色,是用来调试测试代码的。因为测试代码都是某一个包里面,是以包为单位的。

1
dlv test $packname ## 包名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[KentZhang@LOCAL-192-168-97-2 src]$ dlv test db ## 调试db包内部的测试用例
Type 'help' for list of commands.
(dlv) b TestMoneyDbGet ## 打断点,不用加包名了
Breakpoint 1 set at 0x73c15b for db.TestMoneyDbGet() ./db/moneydb_test.go:9
(dlv) c
> db.TestMoneyDbGet() ./db/moneydb_test.go:9 (hits goroutine(5):1 total:1) (PC: 0x73c15b)
4: "logger"
5: "testing"
6: )
7:
8: //日志不分离
=> 9: func TestMoneyDbGet(t *testing.T) {
10: logger.Init("testlog", ".", 1000, 3, logger.DEBUG_LEVEL, false, logger.PUT_CONSOLE)
11: c := MoneydbConnect("192.168.202.92:12515")
12: if nil == c {
13: t.Error("Init() failed.")
14: return
(dlv)

3、调试命令

关于dlv内的调试命令,和gdb差不多,可以使用help查看所有命令。

help

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
(dlv) help
The following commands are available:
args -------------------------------- Print function arguments.
break (alias: b) -------------------- Sets a breakpoint.
breakpoints (alias: bp) ------------- Print out info for active breakpoints.
call -------------------------------- Resumes process, injecting a function call (EXPERIMENTAL!!!)
check (alias: checkpoint) ----------- Creates a checkpoint at the current position.
checkpoints ------------------------- Print out info for existing checkpoints.
clear ------------------------------- Deletes breakpoint.
clear-checkpoint (alias: clearcheck) Deletes checkpoint.
clearall ---------------------------- Deletes multiple breakpoints.
condition (alias: cond) ------------- Set breakpoint condition.
config ------------------------------ Changes configuration parameters.
continue (alias: c) ----------------- Run until breakpoint or program termination.
disassemble (alias: disass) --------- Disassembler.
down -------------------------------- Move the current frame down.
edit (alias: ed) -------------------- Open where you are in $DELVE_EDITOR or $EDITOR
exit (alias: quit | q) -------------- Exit the debugger.
frame ------------------------------- Set the current frame, or execute command on a different frame.
funcs ------------------------------- Print list of functions.
goroutine --------------------------- Shows or changes current goroutine
goroutines -------------------------- List program goroutines.
help (alias: h) --------------------- Prints the help message.
list (alias: ls | l) ---------------- Show source code.
locals ------------------------------ Print local variables.
next (alias: n) --------------------- Step over to next source line.
on ---------------------------------- Executes a command when a breakpoint is hit.
print (alias: p) -------------------- Evaluate an expression.
regs -------------------------------- Print contents of CPU registers.
restart (alias: r) ------------------ Restart process from a checkpoint or event.
rewind (alias: rw) ------------------ Run backwards until breakpoint or program termination.
set --------------------------------- Changes the value of a variable.
source ------------------------------ Executes a file containing a list of delve commands
sources ----------------------------- Print list of source files.
stack (alias: bt) ------------------- Print stack trace.
step (alias: s) --------------------- Single step through program.
step-instruction (alias: si) -------- Single step a single cpu instruction.
stepout ----------------------------- Step out of the current function.
thread (alias: tr) ------------------ Switch to the specified thread.
threads ----------------------------- Print out info for every traced thread.
trace (alias: t) -------------------- Set tracepoint.
types ------------------------------- Print list of types
up ---------------------------------- Move the current frame up.
vars -------------------------------- Print package variables.
whatis ------------------------------ Prints type of an expression.

help [command]

使用 help command 打印出具体命令的用法,例如:

1
2
3
4
5
6
7
8
(dlv) help set
Changes the value of a variable.

[goroutine <n>] [frame <m>] set <variable> = <value>
(dlv) help on
Executes a command when a breakpoint is hit.

on <breakpoint name or id> <command>.

4、config

1
2
3
4
5
6
7
8
9
(dlv) config -list
aliases map[]
substitute-path []
max-string-len <not defined>
max-array-values <not defined>
max-variable-recurse <not defined>
show-location-expr false
source-list-line-color 34
debug-info-directories [/usr/lib/debug/.build-id]

使用p打较长的字符串时,会显示不全,需要配置更长的长度:

1
(dlv) config max-string-len 99999

5、修改变量值

1
2
3
4
5
set num = 1
## 这种修改string变量的,必须使用 call , set 不行
call ctx.Setting.Password = "gQx%7TUB#1cF"
## call 其实调用函数
call len(arr)

6、dlv的不足之处

dlv和gdb相比,除了支持协程这一优势之外,其他的地方远不如gdb。比如

  • dlv 的print 不支持十六进制打印,gdb就可以,p /x number
  • dlv不支持变量、函数名的自动补全
  • dlv的on 功能与gdb的commands类似,可惜的是dlv只支持print, stack and goroutine三个命令,竟然不支持continue

还有一个特殊情况,如果一个函数有定义,但是没在任何地方调用,那么dlv打断点打不到

gdb调试golang程序

原文链接:gdb调试golang程序
发布时间:2018-12-09 11:26:28

文章目录

gdb是linux系统自带的调试器,功能十分强大,对于C/C++程序员不会陌生,那么使用gdb如何调试golang程序呢?

1、配置gdb

golang这门语言是2009年才出来,gdb并没有原生支持调试golang,这里需要配置gdb,使它支持golang,关于这里面的原理,我还不是很清楚,应该是gdb本身支持扩展,可以以提供插件的形式扩展支持其他语言,具体怎么样,我也不是很清楚,待后续研究。

言归正传,这样配置:

1、打开gdb初始化配置文件

1
vim /home/KentZhang/.gdbinit  ## 注意,KentZhang是我的家目录,你需要改成你的家目录

2、增加如下的一行

1
add-auto-load-safe-path /usr/local/go/src/runtime/runtime-gdb.py ## 注意, /usr/local/go是我的golang安装目录

3、然后重启gdb

使用gdb启动一个go程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
[KentZhang@LOCAL-192-168-97-2 bak]$ gdb demo.exe
GNU gdb (GDB) 7.6.2
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/KentZhang/bak/demo.exe...done.
Loading Go Runtime support.
(gdb)

Loading Go Runtime support 出现这句话,就表示gdb可以支持golang。

2、编译golang

虽然gdb也支持golang了,但是在编译golang仍然需要加一些特殊的参数,否则出现如下的错误:

1
No symbol  in current context

就是程序内的变量,你都无法打印,gdb说找不到。所以编译时,需要加下列的参数:

1
go build -gcflags=all="-N -l"  ## 必须这样编译,才能用gdb打印出变量,第二个是小写的L,不是大写的i

3、打断点

关于打断点,必须使用特定的方式,由于golang是以包为单位管理代码,所有打断点时,要注意包名。

1、包名+函数名

1
2
3
4
5
6
7
8
(gdb) b main.main ## main包的main函数
Breakpoint 1 at 0x2131: file main.go, line 14.

(gdb) b framework/rpcclient/reqprocess.suitableMethods ## 注意包的全路径,这种方式要注意

(gdb) b hundreds.(*Game).OnUserLogin ## *Game,表示指针的方式实现的,用Game反而打不到断点
Note: breakpoint 1 also set at pc 0x67ce70.
Breakpoint 2 at 0x67ce70: file /home/KentZhang/Hundreds/src/hundreds/game.go, line 57.

2、文件名+行号

1
2
(gdb) b main.go:17
Breakpoint 2 at 0x2167: file main.go, line 17.

4、总结

基本上经过三个步骤,就可以像调试C/C++一样调试golang了。

由于gdb不支持协程的调试,所以gdb调试golang有时候显得捉襟见肘。

内存查看工具之free

原文链接:内存查看工具之free
发布时间:2018-12-03 23:32:11

基本使用

free是一个内存查看工具,使用起来非常简单:

1
free -m ## 单位是M 也可以是-g -k
1
2
3
4
5
[KentZhang@ALVPCFLKF-192-168-29-61 707]$ free -m
total used free shared buffers cached
Mem: 32186 31827 359 0 704 22448
-/+ buffers/cache: 8674 23511
Swap: 7999 11 7988

解释含义

从第三行起,解释其含义。

第三行:total 为 32186M,表示系统的物理内存;used 为31827M,表示系统已经使用的内存,注意,这里的used包含了后面的buffers和cached;shared表示共享内存的大小;buffers和cached则是表示用于特殊目的的内存,其中的一个目的是为了提高IO访问速度,buffers是用来写的,cached是用来读的。

第四行:used 为8674M,表示系统实际使用的内存,这些内存无法再被使用,free为23511M,表示可使用的内存。

显然,23511 = 359 + 704 + 22448 8674 = 32186 - 23511

第五行: 是交换区的使用情况,这里暂且不谈。

cache和buffer

内存的访问速速很难快,是纳秒级别的,相对硬盘访问的速度就很慢,是毫秒级的,前者是后者的数万倍。为了解决这个矛盾,于是系统就有了cache和buffer。

有时候,一个硬盘的相同的内容,有可能在短时间内被读出多次,比如一个机器被多个用户使用,于是系统在第一次从硬盘读出数据时,就存入cache中,下次再读的话,就直接从cache读取,那么速度就快了很多。

同样,在向硬盘写入数据时,每次写入数据也是很费时的,要尽量减少写硬盘的次数,于是系统不会立刻把数据写入硬盘,而是先写入buffer中,等到达到一定量的数据,再一次性写入硬盘。这样,对应用程序而言,写数据的效率提高了。