BJLC's Blog

无齌无衋懋葳蕤,鸾凤和鸣蓺清枫!


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

PHP中令人疑惑的strtotime

发表于 2020-02-27 | 分类于 PHP

  在处理时间的时候我们经常会使用 strtotime 结合 -1 month、+1 month、next month、last month,但有时候会得到很困惑的结果,所以使用这个函数的时候会很没底,下面我们来看看吧。

举个例子:

1
2
3
4
5
<?php
$date = date('Y-m-d', strtotime('-1 month', strtotime('2020-07-31')));
var_dump($date);
//执行结果
//string(10) "2020-07-01"

  输出怎么是 2020-07-01呢?我们期望的是 2020-06-30,下面一步步说明这样处理的逻辑:

  1. 先做 -1 month 操作,当前是 07-31,减去一个月是 06-31;
  2. 做日期规范化,因为6月没有31号,所以 06-31 就变成 07-01 了。
    我们来验证一下第二步:
    1
    2
    3
    4
    <?php
    var_dump(date('Y-m-d', strtotime('2020-06-31')));
    //执行结果
    string(10) "2020-07-01"

  也就是说只要涉及到大小月的最后一天就可能有这个疑惑,验证一下:

1
2
3
4
5
6
7
8
9
<?php
var_dump(date("Y-m-d", strtotime("-1 month", strtotime("2020-03-31"))));
//输出2020-03-02
var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2020-08-31"))));
//输出2020-10-01
var_dump(date("Y-m-d", strtotime("next month", strtotime("2020-01-31"))));
//输出2020-03-02
var_dump(date("Y-m-d", strtotime("last month", strtotime("2020-03-31"))));
//输出2020-03-02

  那怎么处理这种时间呢?别着急,从 PHP5.3 开始,date 函数新增了一系列修正语来明确整个问题,也就是 first day of 和 last day of 来限定 date 不自动规范化,验证一下:

1
2
3
4
5
6
7
8
9
<?php
var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime("2020-03-31"))));
//输出2020-02-29
var_dump(date("Y-m-d", strtotime("first day of +1 month", strtotime("2020-08-31"))));
//输出2020-09-01
var_dump(date("Y-m-d", strtotime("first day of next month", strtotime("2020-01-31"))));
//输出2020-02-01
var_dump(date("Y-m-d", strtotime("last day of last month", strtotime("2020-03-31"))));
//输出2020-02-29

  搞清楚了,以后就可以放心使用了。

PHP trim函数剖析

发表于 2020-01-10 | 分类于 PHP

这周遇到了一个关于 trim 函数的问题,问题是这样产生的,由于业务上需要计算购买人数,并对购买人数做特殊展示处理,规则是这样的购买人数大于 10000 展示 xx.x万 人,小于 10000 则展示原数据,产品还有一个特殊需求那就是如果计算结果刚好是 xx.0 那么 .0 是不需要的,所以当时是这么处理的 $buyNum = trim(round($buyNum / 10000, 1), '.0'); ,当数据是诸如 16.0 这样的时候是没有问题的,但是 10.0 这种数据就有问题了,由于 trim 函数特性,最终得到的结果是 1,和我们的预期大相径庭,那么这究竟是怎么一回事呢?那我们就一步步来探究它吧,彻底搞懂它,避免之后再踩坑。以下 PHP源码 基于 PHP7.1.6.

我们先看一下 PHP 官方文档 trim 的解释:

  • trim 函数原型:

    1
    trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] ) : string
  • trim 函数说明:

    This function returns a string with whitespace stripped from the beginning and end of str. Without the second parameter, trim() will strip these characters:

    • “ “ (ASCII 32 (0x20)), an ordinary space.
    • “\t” (ASCII 9 (0x09)), a tab.
    • “\n” (ASCII 10 (0x0A)), a new line (line feed).
    • “\r” (ASCII 13 (0x0D)), a carriage return.
    • “\0” (ASCII 0 (0x00)), the NUL-byte.
    • “\x0B” (ASCII 11 (0x0B)), a vertical tab.
  • trim 参数说明:
    • str
      The string that will be trimmed.
    • character_mask
      Optionally, the stripped characters can also be specified using the character_mask parameter. Simply list all characters that you want to be stripped. With .. you can specify a range of characters.

PHP官方 文档说的是这个函数的一个简单用法,没有第二个参数的时候,trim 函数默认去除 ' '(空格)、\t(水平制表符)、\n(换行符)、\r(回车符)、\0(空字节符)、\x0B/\v(垂直制表符)等几种字符。加了第二个参数以后就是去除所指定的字符,通过 .. 可以指定范围,看这个解释那么如下两个例子应该得到什么结果呢?

1
2
3
4
5
6
<?php
$str = 'Hello World';
$a = trim($str, 'Hdle');
$b = trim($str, 'HdWr');
var_dump($a);
var_dump($b);

结果应该是:

1
2
string(5) "o Wor"
string(7) "ello ol"

执行以后呢?

1
2
string(5) "o Wor"
string(9) "ello Worl"

和我们预期的结果区别不小,这是怎么回事呢?
回到我的那个处理购买人数问题上:

1
2
3
4
5
<?php
$buyNum = 99907;
$buyNum = trim(round($buyNum / 10000, 1), '.0');
var_dump($buyNum);
//string(1) "1"

有点奇怪吧,甚至是有点迷惑,这到底是什么意思呢?当然这个问题不用这个方法也能处理,但是 trim 不能处理或者说处理的不对到底是什么情况呢?看 PHP 官方 文档那应该结果就是 1 ,再看文档没有其他说明了,那就不看了吗?No!我们可以看 trim 源码实现,探究它的本质,真正了解它的实现原理,之后就不会再犯同样的错误,同样在别人说这个 trim 函数不好用有坑的时候你能知道为什么会有坑,坑是怎么产生的。

trim 源码实现在 php-7.1.6/ext/standard/string.c 中的 php_trim 方法,核心代码如下:

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/* {{{ php_trim()
* mode 1 : trim left
* mode 2 : trim right
* mode 3 : trim left and right
* what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0')
*/
PHPAPI zend_string *php_trim(zend_string *str, char *what, size_t what_len, int mode)
{
const char *c = ZSTR_VAL(str);
size_t len = ZSTR_LEN(str);
register size_t i;
size_t trimmed = 0;
char mask[256];

if (what) {
if (what_len == 1) {
char p = *what;
if (mode & 1) {
for (i = 0; i < len; i++) {
if (c[i] == p) {
trimmed++;
} else {
break;
}
}
len -= trimmed;
c += trimmed;
}
if (mode & 2) {
if (len > 0) {
i = len - 1;
do {
if (c[i] == p) {
len--;
} else {
break;
}
} while (i-- != 0);
}
}
} else {
php_charmask((unsigned char*)what, what_len, mask);

if (mode & 1) {
for (i = 0; i < len; i++) {
if (mask[(unsigned char)c[i]]) {
trimmed++;
} else {
break;
}
}
len -= trimmed;
c += trimmed;
}
if (mode & 2) {
if (len > 0) {
i = len - 1;
do {
if (mask[(unsigned char)c[i]]) {
len--;
} else {
break;
}
} while (i-- != 0);
}
}
}
} else {
if (mode & 1) {
for (i = 0; i < len; i++) {
if ((unsigned char)c[i] <= ' ' &&
(c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) {
trimmed++;
} else {
break;
}
}
len -= trimmed;
c += trimmed;
}
if (mode & 2) {
if (len > 0) {
i = len - 1;
do {
if ((unsigned char)c[i] <= ' ' &&
(c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) {
len--;
} else {
break;
}
} while (i-- != 0);
}
}
}

if (ZSTR_LEN(str) == len) {
return zend_string_copy(str);
} else {
return zend_string_init(c, len, 0);
}
}

函数上面的注释说明了这个函数的参数含义:

  • str:原字符串
  • what:需要去除的指定字符串
  • what_len:需要去除的指定字符串长度
  • mode:去除类型,左去除,右去除,左右去除

trim 函数处理逻辑:

  1. 判断是否设置去除内容 what,没有设置去除默认字符(' \t\n\r\v\0');
  2. 判断去除内容长度,1个字符和多个字符去除;
  3. 使用 mode 与 1 和 2 按位与运算判断左右去除;
  4. trim 多个字符去除,是循环去除,直到遇到第一个不在列表中的字符。

这里我们看多字符去除,单字符去除没有歧义,主要是对多字符去除有疑惑,多字符去除主要处理在 php_charmask 函数,定义如下:

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
/* {{{ php_charmask
* Fills a 256-byte bytemask with input. You can specify a range like 'a..z',
* it needs to be incrementing.
* Returns: FAILURE/SUCCESS whether the input was correct (i.e. no range errors)
*/
static inline int php_charmask(unsigned char *input, size_t len, char *mask)
{
unsigned char *end;
unsigned char c;
int result = SUCCESS;

memset(mask, 0, 256);
for (end = input+len; input < end; input++) {
c=*input;
if ((input+3 < end) && input[1] == '.' && input[2] == '.'
&& input[3] >= c) {
memset(mask+c, 1, input[3] - c + 1);
input+=3;
} else if ((input+1 < end) && input[0] == '.' && input[1] == '.') {
/* Error, try to be as helpful as possible:
(a range ending/starting with '.' won't be captured here) */
if (end-len >= input) { /* there was no 'left' char */
php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the left of '..'");
result = FAILURE;
continue;
}
if (input+2 >= end) { /* there is no 'right' char */
php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the right of '..'");
result = FAILURE;
continue;
}
if (input[-1] > input[2]) { /* wrong order */
php_error_docref(NULL, E_WARNING, "Invalid '..'-range, '..'-range needs to be incrementing");
result = FAILURE;
continue;
}
/* FIXME: better error (a..b..c is the only left possibility?) */
php_error_docref(NULL, E_WARNING, "Invalid '..'-range");
result = FAILURE;
continue;
} else {
mask[c]=1;
}
}
return result;
}

php_charmask 函数使用一个 mask 字节数组来标记那些需要去除的字符串,然后执行操作和去除一个字符类似,只是结束条件是寻找到第一个不在字符表里的元素。同时我们也能看到函数对于范围去除的处理,也就是 trim 函数第二个参数中的 .. ,同时也说明了在使用 trim 函数时第二个参数不能有三个点 ... 否则会报错。
了解了 trim 函数内部实现原理以后,下面我们来通过 GDB 跟踪一下函数内部实现

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
 gdb php
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
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-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/local/php71/bin/php...done.
(gdb) b php_charmask
Breakpoint 1 at 0x730705: php_charmask. (4 locations)
(gdb) r ~/Code/PHP/trim.php
Starting program: /bin/php ~/Code/PHP/trim.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, php_trim (str=0x7ffff1c03560, what=0x7ffff1c03598 ".0",
what_len=2, mode=3) at /usr/src/php-7.1.6/ext/standard/string.c:829
829 php_charmask((unsigned char*)what, what_len, mask);
(gdb) s
php_charmask (mask=0x7fffffffaa30 "", len=2, input=0x7ffff1c03598 ".0")
at /usr/src/php-7.1.6/ext/standard/string.c:751
751 memset(mask, 0, 256);
(gdb) n
752 for (end = input+len; input < end; input++) {
(gdb) p input
$1 = (unsigned char *) 0x7ffff1c03598 ".0"
(gdb) p *input
$2 = 46 '.'
(gdb) n
751 memset(mask, 0, 256);
(gdb)
752 for (end = input+len; input < end; input++) {
(gdb)
751 memset(mask, 0, 256);
(gdb)
752 for (end = input+len; input < end; input++) {
(gdb)
761 if (end-len >= input) { /* there was no 'left' char */
(gdb)
php_trim (str=0x7ffff1c03560, what=<optimized out>, what_len=<optimized out>,
mode=<optimized out>) at /usr/src/php-7.1.6/ext/standard/string.c:829
829 php_charmask((unsigned char*)what, what_len, mask);
(gdb) s
php_charmask (mask=0x7fffffffaa30 "", len=<optimized out>,
input=0x7ffff1c03598 ".0") at /usr/src/php-7.1.6/ext/standard/string.c:754
754 if ((input+3 < end) && input[1] == '.' && input[2] == '.'
(gdb)
753 c=*input;
(gdb) p c
$3 = <optimized out>
(gdb) p *input
$4 = 46 '.'
(gdb) n
754 if ((input+3 < end) && input[1] == '.' && input[2] == '.'
(gdb) n
758 } else if ((input+1 < end) && input[0] == '.' && input[1] == '.') {
(gdb) n
797 size_t len = ZSTR_LEN(str);
(gdb) p str
$5 = (zend_string *) 0x7ffff1c03560
(gdb) p *str
$6 = {gc = {refcount = 0, u = {v = {type = 6 '\006', flags = 2 '\002',
gc_info = 0}, type_info = 518}}, h = 9223372043238031460, len = 4,
val = "1"}
(gdb) p len
$7 = 4
(gdb) n
829 php_charmask((unsigned char*)what, what_len, mask);
(gdb) s
php_charmask (mask=0x7fffffffaa30 "", len=<optimized out>,
input=0x7ffff1c03599 "0") at /usr/src/php-7.1.6/ext/standard/string.c:754
754 if ((input+3 < end) && input[1] == '.' && input[2] == '.'
(gdb)
753 c=*input;
(gdb)
754 if ((input+3 < end) && input[1] == '.' && input[2] == '.'
(gdb)
758 } else if ((input+1 < end) && input[0] == '.' && input[1] == '.') {
(gdb)
781 mask[c]=1;

(gdb) p c
$8 = 48 '0'
(gdb) n
752 for (end = input+len; input < end; input++) {
(gdb)
php_trim (str=0x7ffff1c03560, what=<optimized out>, what_len=<optimized out>,
mode=<optimized out>) at /usr/src/php-7.1.6/ext/standard/string.c:831
831 if (mode & 1) {
(gdb)
797 size_t len = ZSTR_LEN(str);
(gdb) n
831 if (mode & 1) {
(gdb) n
832 for (i = 0; i < len; i++) {
(gdb) n
833 if (mask[(unsigned char)c[i]]) {
(gdb) n
839 len -= trimmed;
(gdb) n
840 c += trimmed;
(gdb) n
839 len -= trimmed;
(gdb) n
842 if (mode & 2) {
(gdb) n
843 if (len > 0) {
(gdb) n
846 if (mask[(unsigned char)c[i]]) {
(gdb) p c
$10 = 0x7ffff1c03578 "10.0"
(gdb) p c[i]
$11 = 48 '0'
(gdb) p i
$12 = 3
(gdb) n
851 } while (i-- != 0);
(gdb) n
846 if (mask[(unsigned char)c[i]]) {
(gdb) p c[i]
$13 = 46 '.'
(gdb) p i
$14 = 2
(gdb) n
851 } while (i-- != 0);
(gdb) n
846 if (mask[(unsigned char)c[i]]) {
(gdb) p c[i]
$15 = 48 '0'
(gdb) n
851 } while (i-- != 0);
(gdb) n
846 if (mask[(unsigned char)c[i]]) {
(gdb) p c[i]
$16 = 49 '1'
(gdb) p i
$17 = 0
(gdb) n
883 if (ZSTR_LEN(str) == len) {
(gdb) p len
$18 = 1
(gdb) n
886 return zend_string_init(c, len, 0);
(gdb) p c
$19 = 0x7ffff1c03578 "10.0"
(gdb)
$20 = 0x7ffff1c03578 "10.0"
(gdb) p *c
$21 = 49 '1'

最终验证了我们的结论,也加深了对 trim 函数的理解,这里做个延伸,由于 trim 是基于字节去除的,所以在去除中文的时候可能会出现乱码,这是由于汉字是 UTF-8 编码,一个汉字占 3字节,所以可能会出现乱码,知道了函数实现原理以及实现细节可以避免踩很多坑。

1
2
>>> trim('品、', '、')
=> b"å“"

LNMP源码编译安装

发表于 2019-09-26 | 分类于 PHP

LNMP是一个Web应用软件组合,即(Linux+Nginx+MySQL+PHP)

  首先你需要一个Linux系统,可以是Centos、Debian、Ubuntu等类Linux/Unix系统,下面我们来逐个进行编译安装,这里以Centos为例:

1、Nginx(Nginx1.14.2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//安装依赖
yum install -y pcre-devel zlib-devel openssl-devel
//获取源码
wget http://nginx.org/download/nginx-1.14.2.tar.gz
//解压安装
tar -zxvf nginx-1.14.2.tar.gz
cd nginx-1.14.2
./configure --prefix=/usr/local/nginx
make && make install
//创建用户www
groupadd www
useradd -g www www
//修改nginx.conf配置文件支持php
 fastcgi_param  SCRIPT_FILENAME 
$document_root$fastcgi_script_name;

2、MySQL(MySQL5.7)

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//安装依赖
yum -y install gcc gcc-c++ ncurses ncurses-devel cmake bison
//获取源码
wget https://sourceforge.net/projects/boost/files/boost/1.59.0/boost_1_59_0.tar.gz
wget http://cdn.mysql.com/Downloads/MySQL-5.7/mysql-5.7.13.tar.gz
//创建MySQL用户和用户组
groupadd -r mysql && useradd -r -g mysql -s /sbin/nologin -M mysql
//解压
tar -zxvf boost_1_59_0.tar.gz
tar -zxvf mysql-5.7.13.tar.gz
//创建数据目录
mkdir -p /data/mysql
//进入mysql-5.7.13预编译MySQL
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
-DMYSQL_DATADIR=/data/mysql \
-DWITH_BOOST=../boost_1_59_0 \
-DSYSCONFDIR=/etc \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_PARTITION_STORAGE_ENGINE=1 \
-DWITH_FEDERATED_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_MYISAM_STORAGE_ENGINE=1 \
-DENABLED_LOCAL_INFILE=1 \
-DENABLE_DTRACE=0 \
-DDEFAULT_CHARSET=utf8mb4 \
-DDEFAULT_COLLATION=utf8mb4_general_ci \
-DWITH_EMBEDDED_SERVER=1
//编译安装
make -j `grep processor /proc/cpuinfo | wc -l`
make install
//设置启动脚本,开机自启动
cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld
chmod +x /etc/init.d/mysqld
systemctl enable mysqld
//修改my.cnf配置文件
cat > /etc/my.cnf << EOF
[client]
port = 3306
socket = /dev/shm/mysql.sock
[mysqld]
port = 3306
socket = /dev/shm/mysql.sock
basedir = /usr/local/mysql
datadir = /data/mysql
pid-file = /data/mysql/mysql.pid
user = mysql
bind-address = 0.0.0.0
server-id = 1
init-connect = 'SET NAMES utf8mb4'
character-set-server = utf8mb4
#skip-name-resolve#skip-networking
back_log = 300
max_connections = 1000
max_connect_errors = 6000
open_files_limit = 65535
table_open_cache = 128
max_allowed_packet = 4M
binlog_cache_size = 1M
max_heap_table_size = 8M
tmp_table_size = 16M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
sort_buffer_size = 8M
join_buffer_size = 8M
key_buffer_size = 4M
thread_cache_size = 8
query_cache_type = 1
query_cache_size = 8M
query_cache_limit = 2M
ft_min_word_len = 4
log_bin = mysql-bin
binlog_format = mixed
expire_logs_days = 30
log_error = /data/mysql/mysql-error.log
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /data/mysql/mysql-slow.log
performance_schema = 0
explicit_defaults_for_timestamp
#lower_case_table_names = 1
skip-external-locking
default_storage_engine = InnoDB
#default-storage-engine = MyISAM
innodb_file_per_table = 1
innodb_open_files = 500
innodb_buffer_pool_size = 64M
innodb_write_io_threads = 4
innodb_read_io_threads = 4
innodb_thread_concurrency = 0
innodb_purge_threads = 1
innodb_flush_log_at_trx_commit = 2
innodb_log_buffer_size = 2M
innodb_log_file_size = 32M
innodb_log_files_in_group = 3
innodb_max_dirty_pages_pct = 90
innodb_lock_wait_timeout = 120
bulk_insert_buffer_size = 8M
myisam_sort_buffer_size = 8M
myisam_max_sort_file_size = 10G
myisam_repair_threads = 1
interactive_timeout = 28800
wait_timeout = 28800
[mysqldump]
quick
max_allowed_packet = 16M
[myisamchk]
key_buffer_size = 8M
sort_buffer_size = 8M
read_buffer = 4M
write_buffer = 4M
EOF
//初始化数据库
mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql
//启动数据库
systemctl start mysqld
//设置数据库root用户密码
mysql_secure_installation
//设置MySQL服务可被其他服务调用
echo "/usr/local/mysql/lib" > /etc/ld.so.conf.d/mysql.conf
ldconfig

3、PHP(PHP7.1.6)

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
//安装依赖
yum -y install libxml2 libxml2-devel openssl openssl-devel curl-devel libjpeg-devel libpng-devel freetype-devel libmcrypt-devel
//解压安装
tar -zxvf php-7.1.6.tar.gz
./configure \
--prefix=/usr/local/php71 \
--exec-prefix=/usr/local/php71 \
--bindir=/usr/local/php71/bin \
--sbindir=/usr/local/php71/sbin \
--includedir=/usr/local/php71/include \
--libdir=/usr/local/php71/lib/php \
--mandir=/usr/local/php71/php/man \
--with-config-file-path=/usr/local/php71/etc \
--with-mysql-sock=/dev/shm/mysql.sock \
--with-mcrypt=/usr/include \
--with-mhash \
--with-openssl \
--with-mysqli=shared,mysqlnd \
--with-pdo-mysql=shared,mysqlnd \
--with-gd \
--with-iconv \
--with-zlib \
--enable-zip \
--enable-inline-optimization \
--disable-debug \
--disable-rpath \
--enable-shared \
--enable-xml \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-mbregex \
--enable-mbstring \
--enable-ftp \
--enable-gd-native-ttf \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-soap \
--without-pear \
--with-gettext \
--enable-session \
--with-curl \
--with-jpeg-dir \
--with-freetype-dir \
--enable-opcache \
--enable-fpm \
--with-fpm-user=www \
--with-fpm-group=www \
--without-gdbm \
--disable-fileinfo
make && make install
//配置nginx支持php-fpm,首先创建www用户和用户组
groupadd www
useradd -g www www
//复制php-fpm配置文件
cd /usr/local/php71/etc
cp php-fpm.conf.default php-fpm.conf
//进入php-fpm.d目录,复制www.conf文件
cd php-fpm.d
cp www.conf.default www.conf
//从PHP源码复制php.ini文件
cp /usr/src/php-7.1.6/php.ini-development /usr/local/php71/etc/php.ini

编译安装扩展时出现如下错误,这是因为没有安装autoconf

1
2
3
4
5
//错误
Cannot find autoconf. Please check your autoconf installation and the $PHP_AUTOCONF environment variable. Then, rerun this scrip
//解决办法
yum -y install m4
yum -y install autoconf

4、安装composer

1
2
3
4
5
6
//下载composer
curl -sS https://getcomposer.org/installer | php
//将composer.phar移入bin目录,使composer可全局使用
mv composer.phar /usr/local/bin/composer
//切换国内源,以加快composer速度
composer config -g repo.packagist composer https://packagist.laravel-china.org

5、安装redis(redis5.03)

1
2
3
4
5
6
7
8
9
10
//获取redis源码
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
//解压
tar -zxvf redis-5.0.3.tar.gz
//安装
make && make install
//修改redis.conf,使redis后台运行
daemonize yes
//启动redis
redis-server redis.conf

安装redis扩展

1
2
3
4
5
6
7
//获取源码
git clone https://github.com/phpredis/phpredis.git
//安装
phpize
./configure --with-php-config=/usr/local/php71/bin/php-config
make && make install
//将生成的redis.so加入php.ini文件

6、安装swoole扩展(swoole4.3.2)

1
2
3
4
5
6
7
//获取源码
git clone https://github.com/swoole/swoole-src/releases/tag/v4.3.2-rc1
//安装
phpize
./configure --with-php-config=/usr/local/php71/bin/php-config
make && make install
//将生成的swoole.so加入php.ini文件

7、安装amqp扩展(amqp1.9.4)

1
2
3
4
5
6
7
8
9
10
11
12
13
//安装依赖
wget https://github.com/alanxz/rabbitmq-c/releases/download/v0.7.1/rabbitmq-c-0.7.1.tar.gz
tar -zxvf rabbitmq-c-0.7.1.tar.gz
cd rabbitmq-c-0.7.1
./configure --prefix=/usr/local/rabbitmq-c
make && make install
//安装amqp
wget http://pecl.php.net/get/amqp-1.9.4.tgz
tar -zxvf amqp-1.9.4.tgz
cd amqp-1.9.4
/usr/local/php71/bin/phpize
./configure --with-php-config=/usr/local/php71/bin/php-config --with-amqp --with-librabbitmq-dir=/usr/local/rabbitmq-c
make && make install

未命名

发表于 2019-07-02

Nginx处理HTTP请求的11个阶段

发表于 2019-07-01 | 分类于 PHP

nginx将HTTP请求处理流程分为11个阶段,绝大部分HTTP模块都会将自己的handler添加到某个阶段(将handler加入全局数组phases),nginx处理HTTP请求时会逐个调用每个阶段的handler,其中有4个阶段不能自定义handler,11个阶段代码在/src/http/ngx_http_core_module.h中:

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
typedef enum {
//读取请求内容阶段
NGX_HTTP_POST_READ_PHASE = 0,

//server块配置rewrite指令,重写url
NGX_HTTP_SERVER_REWRITE_PHASE,

//查找匹配的location配置,不能自定义handler
NGX_HTTP_FIND_CONFIG_PHASE,

//location块配置rewrite指令,重写url
NGX_HTTP_REWRITE_PHASE,

//检查是否发生url重写,有,重新回到FIND_CONFIG阶段,不能自定义handler
NGX_HTTP_POST_REWRITE_PHASE,

//访问控制,比如限流模块会注册handler到此阶段
NGX_HTTP_PREACCESS_PHASE,

//访问权限控制,比如基于ip黑名单和用户密码权限控制
NGX_HTTP_ACCESS_PHASE,

//根据访问权限控制阶段做相应处理,不能自定义handler
NGX_HTTP_POST_ACCESS_PHASE,

//开始内容生成前阶段
NGX_HTTP_PRECONTENT_PHASE,

//内容产生阶段,返回响应给客户端
NGX_HTTP_CONTENT_PHASE,

//日志记录阶段,不能自定义handler
NGX_HTTP_LOG_PHASE
} ngx_http_phases;

通过gdb可以看到具体阶段对应的handler:

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
gdb /usr/local/nginx16/sbin/nginx
b ngx_http_block
r
n
b 326
c
(gdb) p cmcf->phases
$1 = {{handlers = {elts = 0x7417f0, nelts = 0, size = 8, nalloc = 1, pool = 0x71d2e0}}, {handlers = {elts = 0x7417f8, nelts = 1, size = 8, nalloc = 1, pool = 0x71d2e0}}, {handlers = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}}, {handlers = {elts = 0x741800, nelts = 1, size = 8, nalloc = 1, pool = 0x71d2e0}}, {handlers = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}}, {handlers = { elts = 0x741e98, nelts = 2, size = 8, nalloc = 2, pool = 0x71d2e0}}, {handlers = {elts = 0x741810, nelts = 2, size = 8, nalloc = 2, pool = 0x71d2e0}}, {handlers = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}}, {handlers = {elts = 0x741820, nelts = 2, size = 8, nalloc = 2, pool = 0x71d2e0}}, {handlers = {elts = 0x741830, nelts = 3, size = 8, nalloc = 4, pool = 0x71d2e0}}, {handlers = { elts = 0x741850, nelts = 1, size = 8, nalloc = 1, pool = 0x71d2e0}}}
(gdb) p cmcf->phases[0]
$2 = {handlers = {elts = 0x7417f0, nelts = 0, size = 8, nalloc = 1, pool = 0x71d2e0}}
(gdb) p *(ngx_http_handler_pt*)cmcf->phases[0].handlers.elts
$3 = (ngx_http_handler_pt) 0x0
(gdb) p cmcf->phases[1]
$4 = {handlers = {elts = 0x7417f8, nelts = 1, size = 8, nalloc = 1, pool = 0x71d2e0}}
(gdb) p *(ngx_http_handler_pt*)cmcf->phases[1].handlers.elts
$5 = (ngx_http_handler_pt) 0x4aaeca <ngx_http_rewrite_handler>
(gdb) p cmcf->phases[2]
$6 = {handlers = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}}
(gdb) p cmcf->phases[3]
$7 = {handlers = {elts = 0x741800, nelts = 1, size = 8, nalloc = 1, pool = 0x71d2e0}}
(gdb) p *(ngx_http_handler_pt*)cmcf->phases[3].handlers.elts
$8 = (ngx_http_handler_pt) 0x4aaeca <ngx_http_rewrite_handler>
(gdb) p cmcf->phases[4]
$9 = {handlers = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}}
(gdb) p cmcf->phases[5]
$10 = {handlers = {elts = 0x741e98, nelts = 2, size = 8, nalloc = 2, pool = 0x71d2e0}}
(gdb) p *(ngx_http_handler_pt*)cmcf->phases[5].handlers.elts+0
$11 = (ngx_int_t (*)(ngx_http_request_t *)) 0x4a226e <ngx_http_limit_conn_handler>
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[5].handlers.elts+1)
$13 = (ngx_http_handler_pt) 0x4a343c <ngx_http_limit_req_handler>
(gdb) p cmcf->phases[6]
$14 = {handlers = {elts = 0x741810, nelts = 2, size = 8, nalloc = 2, pool = 0x71d2e0}}
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[6].handlers.elts+1)
$15 = (ngx_http_handler_pt) 0x4a1872 <ngx_http_access_handler>
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[6].handlers.elts)
$16 = (ngx_http_handler_pt) 0x4a0dcc <ngx_http_auth_basic_handler>
(gdb) p cmcf->phases[7]
$17 = {handlers = {elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0}}
(gdb) p cmcf->phases[8]
$18 = {handlers = {elts = 0x741820, nelts = 2, size = 8, nalloc = 2, pool = 0x71d2e0}}
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[8].handlers.elts)
$19 = (ngx_http_handler_pt) 0x49fbc0 <ngx_http_mirror_handler>
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[8].handlers.elts+1)
$20 = (ngx_http_handler_pt) 0x4a00b5 <ngx_http_try_files_handler>
(gdb) p cmcf->phases[9]
$21 = {handlers = {elts = 0x741830, nelts = 3, size = 8, nalloc = 4, pool = 0x71d2e0}}
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[9].handlers.elts)
$22 = (ngx_http_handler_pt) 0x49bcff <ngx_http_static_handler>
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[9].handlers.elts+1)
$23 = (ngx_http_handler_pt) 0x49c685 <ngx_http_autoindex_handler>
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[9].handlers.elts+2)
$24 = (ngx_http_handler_pt) 0x49ec96 <ngx_http_index_handler>
(gdb) p cmcf->phases[10]
$25 = {handlers = {elts = 0x741850, nelts = 1, size = 8, nalloc = 1, pool = 0x71d2e0}}
(gdb) p *((ngx_http_handler_pt*)cmcf->phases[10].handlers.elts)
$26 = (ngx_http_handler_pt) 0x466ea5 <ngx_http_log_handler>
(gdb) p cmcf->phases[11]
$27 = {handlers = {elts = 0x733288, nelts = 0, size = 0, nalloc = 0, pool = 0x721340}}

11个阶段注册handler如下:
nginx 11个阶段图解

Docker 安装LNMP

发表于 2019-06-14 | 分类于 Docker
  1. 在 /home/用户名/下创建如下文件夹和文件:
    1
    2
    3
    4
    5
    6
    7
    8
    /home/blueyi/www
    ├── data
    ├── nginx
    │   └── conf.d
    │   └── default.conf
    └── web
    ├── index.html
    └── index.php

这些文件夹和文件用于挂载,default.conf内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
listen 80;
server_name localhost;

location / {
root /usr/share/nginx/html;
#添加伪静态支持
try_files $uri $uri/ /index.php?$query_string;
index index.php index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

location ~ \.php$ {
#php 地址直接填容器名
fastcgi_pass php-fpm73:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;
include fastcgi_params;
}
}

  1. 安装 mysql5.7,并进入容器设置远程访问:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #拉取mysql5.7镜像
    docker pull mysql:5.7
    #创建mysql5.7容器
    docker run --name mysql57 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d -v ~/www/data:/var/lib/mysql mysql:5.7

    #进入mysql容器
    docker exec -it mysql57 bash
    #连接mysql设置远程访问
    mysql -uroot -p
    GRANT ALL ON *.* TO 'root'@'%';
    FLUSH PRIVILEGES;
  2. 安装PHP7.3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #拉取PHP7.3镜像
    docker pull php:7.3-fpm
    #创建PHP7.3容器并关联mysql5.7
    docker run --name php-fpm73 -d -v ~/www/web:/var/www/html --link mysql57:mysql php:7.3-fpm

    #进入PHP7.3容器并安装mysql扩展
    docker exec -it php-fpm73 bash
    apt-get update
    apt-get install -y libfreetype6 libfreetype6-dev libmcrypt-dev libmcrypt4 libcurl3-dev
    apt install build-essential libprotobuf-dev libboost-dev openssl protobuf-compiler
    docker-php-ext-install pdo pdo_mysql

    #退出PHP7.3容器,重启PHP7.3容器
    docker restart php-fpm73
  3. 安装nginx:

    1
    2
    3
    4
    #拉取nginx镜像
    docker pull nginx
    #创建nginx容器
    docker run -d --name nginx -p 80:80 -v ~/www/web/:/usr/share/nginx/html -v ~/www/nginx/conf.d:/etc/nginx/conf.d --link php-fpm73:php-fpm nginx
  4. 安装composer:

    1
    2
    3
    4
    #拉取docker镜像
    docker pull composer
    #创建composer容器,并生成Laravel项目
    docker run --rm -it --volume ~/www/web/laravel:/app composer create-project laravel/laravel laravel5.7
  5. 安装phpmyadmin:

    1
    docker run --name phpmyadmin -d --link mysql57:db -p 8080:80 phpmyadmin/phpmyadmin

Docker删除镜像相关命令:

  1. 查询镜像:
    docker images
  2. 查询容器:
    docker ps -a
  3. 删除容器:
    docker rm 容器id
  4. 删除镜像:
    docker rmi 镜像id

    注意:

    1. 删除前需要确保容器已经停止;
    2. docker rmi 删除镜像,docker rm 删除容器;
    3. 必须先删除容器再删除镜像。

docker run 相关命令(通过run命令创建新的容器):
命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
常用选项说明:

  • -d, --detach=false,指定容器运行于前台还是后台,默认false
  • -i, --interactive=false,打开STDIN,用于控制台交互
  • -t, --tty=false,分配tty设备,支持终端登录,默认false
  • -u, --user="",指定容器的用户
  • -a, --attach=[],登录容器(必须是docker run -d启动的容器)
  • -w, --workdir="",指定容器工作目录
  • -c, --cpu-shares=0,设置容器CPU权重,在CPU共享场景使用
  • -e, --env=[],制定环境变量,容器中可以使用此环境变量
  • -m, --memory="",指定容器内存上限
  • -p, --publish=[],指定容器暴露端口
  • -h, --hostname="",指定容器主机名
  • -v, --volume=[],给容器挂载存储卷,挂载到容器某个目录
  • --volumesfrom=[],给容器挂载其他容器上的卷,挂载到容器某个目录
  • --cap-add=[],添加权限
  • --cap-drop=[],删除权限
  • --cidfile="",运行容器后,在指定容器中写入容器PID值,一种典型的监控系统用法
  • --cpuset="",设置容器可以使用那些CPU,此参数可以用来容器独占CPU
  • --device=[],添加主机设备给容器,相当于设备直通
  • --dns=[],指定容器dns服务器
  • --dns-search=[],指定容器dns搜索域名,写入到容器/etc/resolv.conf文件
  • --entrypoint="",覆盖image的入口点
  • --env-file=[],制定环境变量文件,文件格式每行一个环境变量
  • --expose=[],指定容器暴露的端口,即修改镜像暴露端口
  • --link=[],制定容器间关联,使用其他容器IP、env等信息
  • --lxc-conf=[],指定容器配置文件,只有在指定--exec-driver=lxc时使用
  • --name="",指定容器名字,后续可通过名字管理容器,links特性需要使用名字
  • --net="bridge",容器网络设置:
    • bridge:使用docker daemon指定的网桥
    • host:容器使用主机网络
    • container:NAME_OR_ID:使用其他容器的网络,共享IP和PORT等网络资源
    • none:容器使用自己的网络(类似bridge)
  • --privileged=false,指定容器是否为特权容器,特权容器拥有所有capabilities
  • --restart="no",指定容器停止后的重启策略:
    • no:容器退出时不重启
    • on-failure:容器故障退出(返回值非零)时重启
    • always:容器退出时总是重启
  • --rm=false,指定容器停止后自动删除容器(不支持docker run -d启动的容器)
  • --sig-proxy=true,设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP、SIGKILL不能被代理

安装Docker

发表于 2019-06-13 | 分类于 Docker

Docker是一个开源的应用容器引擎,基于Go语言,Docker可以让开发者打包他们的应用程序以及依赖到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。Docker自17.03版本之后分为社区版(Community Edition: CE)和企业版(Enterprise Edition: EE),平时使用社区版就可以了。

  本文以Centos为例安装Docker 18.06,安装Docker CE要求Centos 7,可以通过这个命令查看Centos版本:

1
2
3
4
5
6
7
$ lsb_release -a
//返回结果如下
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description:    CentOS Linux release 7.6.1810 (Core)
Release:        7.6.1810
Codename:       Core

  操作系统满足要求以后,我们可以建立一个Docker repository,之后就可以通过这个仓库来更新和安装Docker了,

  1. 建立Docker repository首先需要安装依赖包:

    1
    2
    3
    $ sudo yum install -y yum-utils \
    device-mapper-persistent-data \
    lvm2
  2. 设置一个稳定的仓库:

    1
    2
    3
    $ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
  3. 安装Docker CE:

    1
    $ sudo yum install docker-ce docker-ce-cli containerd.io
  4. 启动Docker并且将自己的用户加入docker组:

    1
    2
    $ sudo systemctl start docker
    $ usermod -aG docker yourusername
  5. 运行hello-world验证Docker CE安装成功:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ docker run hello-world
    //结果如下
    Unable to find image 'hello-world:latest' locally
    latest: Pulling from library/hello-world
    1b930d010525: Pull complete
    Digest:
    sha256:0e11c388b664df8a27a901dce21eb89f11d8292f7fca1b3e3c4321bf7897bffe
    Status: Downloaded newer image for
    hello-world:latest

    Hello from Docker!
    This message shows that your installation appears
    to be working correctly.

Linux中修改用户名

发表于 2019-05-12 | 分类于 Linux

安装Linux系统以后,有时候可能需要修改用户名,可以手动和通过usermod命令进行修改。

一、 手动修改

  1. 切换用户到root

    1
    sudo su -
  2. 修改 /etc/passwd 文件中的用户名、用户组和主目录部分

  3. 修改 /etc/group 和 /etc/gshadow 文件中的组名
  4. 修改 /etc/shadow 文件中的用户名
  5. 修改 /home/oldname 为/home/newhome
  6. 修改 /etc/sudoers 文件, 添加 root 权限

二、usermod命令修改

  1. 切换用户到root

    1
    sudo su -
  2. usermod -l newname oldname
    此命令相当于将 /etc/passwd 和 /etc/shadow 中用户名从 oldname 修改为 newname 。

  3. usermod -c newname newname
    此命令相当于将 /etc/passwd 下的注解栏修改为 newname。
  4. groupmod -n newname oldname
    此命令相当于修改 /etc/group 和 /etc/gshadow 中的组名为 newname。
  5. usermod -md /home/newname newname
    此命令相当于将 /etc/passwd 中的目录栏修改为 /home/newname ,还有就是将原来的家目录修改为新用户家目录。

Win10子系统WSL(Ubuntu18.04)安装

发表于 2019-05-08 | 分类于 Linux

Windows10 周年更新之后,可以在Windows10上直接运行Linux子系统,也就是Windows Subsystem for Linux(WSL),安装WSL就可以在Windows10上安装Ubuntu了,感觉还不错。

系统安装要求
系统安装要求

安装之后第一次打开会报错

1
2
3
4
The WSL optional component is not enabled. Please enable it and try again.
See https://aka.ms/wslinstall for details.
Error: 0x8007007e
Press any key to continue...

这个错误就是说上文提到的WSL没有开启,开启的方法也很简单,以管理员身份打开powershell,然后输入以下命令:

1
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

安装之后,重启系统就可以了,这时再打开Ubuntu,会提示创建用户名和密码,这时如果创建的名字里面有大写字母会报错,改成小写字母就可以了。报错如下:

1
adduser: Please enter a username matching the regular expression configured via the NAME_REGEX[_SYSTEM] configuration variable.  Use the `--force-badname' option to relax this check or reconfigure NAME_REGEX.

oh-my-zsh打造你的炫酷终端

发表于 2019-05-08 | 分类于 Linux

shell类型很多,Linux下默认是bash,经常看到别人炫酷的终端,很羡慕吧,下面咱们基于开源项目oh-my-zsh打造我们自己的炫酷终端。

安装zsh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//查看单前使用的shell
$ echo $SHELL
/bin/bash

//查看系统是否已经安装zsh
$ cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

//安装zsh(以Centos为例)
yum -y install zsh

//切换默认shell为zsh,并重启
chsh -s /bin/zsh

安装oh-my-zsh

1
2
//安装之前请确保已安装git
wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

安装oh-my-zsh之后可以安装自己喜欢的主题和插件,这里演示一下自动补全插件zsh-autosuggestions和主题agnosterzak的安装方式。

自动补全插件 zsh-autosuggestions
1
2
3
4
//先获取zsh-autosuggestions
git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
//然后修改 ~/.zshrc
plugins=(git zsh-autosuggestions)
主题 agnosterzak
1
2
3
4
//安装agnosterzak主题需要依赖 Powerline fonts
git clone https://github.com/powerline/fonts.git --depth=1
cd fonts
./install.sh

可以通过打印这些字符来检验是否支持Powerline fonts

1
echo "\ue0b0 \u00b1 \ue0a0 \u27a6 \u2718 \u26a1 \u2699"

支持powerline
agnosterzak主题下载地址
将下载的主题放到 ~/.oh-my-zsh/themes,然后修改 ~/.zshrc

1
ZSH_THEME="agnosterzak"

这时候的效果是这样的,这时候如果有新的git文件没被追踪,会是很刺眼的黄色,可以修改刚才下载的主题文件191行自定义颜色。
刺眼的未跟踪git文件

1
2
bgclr='magenta'
fgclr='yellow'

改完的效果这样,舒服了很多
舒服的未跟踪git文件
oh-my-zsh还有很多强大的功能等着大家去发现呢?赶紧去试试吧!

123

清墨彰蓺

21 日志
5 分类
19 标签
© 2020 清墨彰蓺
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Gemini v7.0.1
京ICP备18014858号-1
总访客量 | 总访问量