shell实现面向对象编程

今天看到一篇博客, 有人实现了 shell 的面向对象功能. 虽然class new 等是用函数实现的, 但是思想非常巧妙, 很有意思, 所以就把它贴在这里, 记录一下.
而且看起来还不是很容易弄明白, 可以打开 set -x 配合源码一起看.

其中使用到了 uuidgen, 依赖于系统中的 uuid-runtime. 如果没有安装, 请安装对应的库, ubuntu中的命令是:

1
apt install uuid-runtime

原文地址: https://coolshell.cn/articles/5035.html

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

#---------------------------------
# OO support functions
#---------------------------------
#set -x

DEFCLASS=""
CLASS=""
THIS=0

class() {
DEFCLASS="$1"
eval CLASS_${DEFCLASS}_VARS=""
eval CLASS_${DEFCLASS}_FUNCTIONS=""
}

static() {
return 0
}

func() {
local varname="CLASS_${DEFCLASS}_FUNCTIONS"
eval "$varname=\"\${$varname}$1 \""
}

var() {
local varname="CLASS_${DEFCLASS}_VARS"
eval $varname="\"\${$varname}$1 \""
}

loadvar() {
eval "varlist=\"\$CLASS_${CLASS}_VARS\""
for var in $varlist; do
eval "$var=\"\$INSTANCE_${THIS}_$var\""
done
}

loadfunc() {
eval "funclist=\"\$CLASS_${CLASS}_FUNCTIONS\""
for func in $funclist; do
eval "${func}() { ${CLASS}::${func} \"\$@\"; return \$?; }"
done
}

savevar() {
eval "varlist=\"\$CLASS_${CLASS}_VARS\""
for var in $varlist; do
eval "INSTANCE_${THIS}_$var=\"\$$var\""
done
}

typeof() {
eval echo \$TYPEOF_$1
}

new() {
local class="$1"
local cvar="$2"
shift
shift
local id=$(uuidgen | tr A-F a-f | sed -e "s/-//g")
eval TYPEOF_${id}=$class
eval $cvar=$id
local funclist
eval "funclist=\"\$CLASS_${class}_FUNCTIONS\""
for func in $funclist; do
eval "${cvar}.${func}() {
local t=\$THIS; THIS=$id; local c=\$CLASS; CLASS=$class; loadvar;
loadfunc; ${class}::${func} \"\$@\"; rt=\$?; savevar; CLASS=\$c;
THIS=\$t; return $rt;
}"
done
eval "${cvar}.${class} \"\$@\" || true"
}



#---------------------------------------------------
# Example code
#---------------------------------------------------

# class definition
class Storpel
func Storpel
func setName
func setQuality
func print
var name
var quality

# class implementation
Storpel::Storpel() {
setName "$1"
setQuality "$2"
if [ -z "$name" ]; then setName "Generic"; fi
if [ -z "$quality" ]; then setQuality "Normal"; fi
}

Storpel::setName() { name="$1"; }
Storpel::setQuality() { quality="$1"; }
Storpel::print() { echo "$name ($quality)"; }

#usage
new Storpel one "Storpilator 1000" Medium
new Storpel two
new Storpel three
#
two.setName "Storpilator 2000"
two.setQuality "Strong"

one.print
two.print
three.print

echo ""
echo "one: $one ($(typeof $one))"
echo "two: $two ($(typeof $two))"
echo "Three: $three ($(typeof $three))"

Linux free命令中的buffer和cache有什么区别?

在Linux的free命令输出中, 有一个buffer 和 cache 字段. 如下图:

1
2
3
              total        used        free      shared  buff/cache   available
Mem: 1009028 318676 114392 21640 575960 488928
Swap: 0 0 0

它们代表什么意思呢?

查资料后发现:

  • buffer 是某一个程序临时存数据的地方, 并且这些数据不能被别的程序使用. 举个带宽的例子:当你要立即发送大量数据的时候,但是网卡发送能力较低. 它会将这些数据保存在buffer里, 然后慢慢发送这些数据.
  • cache 是为了快速访问而将频繁用到的数据临时保存在内存里

另一个不同是cache将会多次使用, 而buffer只会使用一次.

但是他们都是为了处理数据而临时存储.

怎么删除文件开头的UTF8 BOM

Bom 是指文件开头的三个连续字节. 0xEF 0xBB 0xBF.
所以我们只要匹配并处理字就可以了.

PHP代码如下

1
2
$bom = pack('H*','EFBBBF');
$text = preg_replace("/^$bom/", '', $text);

Python代码如下:

1
2
3
4
5
import codes

if line[:3] == codecs.BOM_UTF8:
print('BOM')
line = line[3:]

C语言宏定义中为什么使用do while(0)

C语言的多行宏定义, 很多都使用 do {} while(0) 的形式. 比如 PHP 源码中就有很多这样的定义.
我们知道 do {} while 是循环结构, 在C语言中, 这种形式, 会先执行 do {} 中的语句, 再执行 while 中的判断条件. 也就是无论 while 中的条件是什么, do {} 中语句至少会执行一次. 而且 while(0), 所以 do {} 就只执行一次.

这是为什么呢?

我们来看一个例子:

1
#define TEST(a, b)  a++; b++;

这是个宏声明.

如果这个宏使用以下场景中

1
2
3
4
if (cond)
TEST(a, b);
else
...

这种情况下, 编译器会报错. 因为宏替换后, if () 后只包含 a++; 而 b_++; 会被编译器认为是 if 之外的语句.

这种情况也可以在 if 后问题带上 {} 来解决. 但是这不能从程序上完全保证.

那另外的解决方案是宏定义可以使用 {} . 但是这会引用另一个问题.
我们写程序, 不管是正常语句还是宏调用. 都习惯在最后写一个分号.
那如果宏后面有分号, 宏替换后, 会变成:

1
2
3
4
if (cond)
TEST(a, b);
else
...

宏替换后变成:
1
2
3
4
if (cond)
{ a++; b++; };
else
...

同样会编译不通过. 宏最后的分号会被认识是一个空语句, 而且是在 if () 包含之外.

如果换成 do {} while(0) 呢?
我们看一下

1
2
3
4
if (cond)
do { } while(0);
else
...

这样就没有问题, 编译会通完, 而且也没有歧义. 所以这是业界最佳实践.

JSONP的理解

JSONP作为js跨域请求的一个方案,很早就知道了,一直没有详细了解。今天了解了一下,记录下来。

JSONP 是 JSON with Padding 是缩写。是指定数据格式json的一种使用模式,可以让网页从别的域名要数据。

我们都知道浏览器安全最基本的就是“同源策略”

同源策略是指URL的三个要素:

  • 协议相同
  • 域名相同
  • 端口相同

它会限制三种行为:

  • Cookie、LocalStorage
  • DOM 无法获得
  • AJAX 请求不能发送

同源策略的目的是为了保证用户信息的安全。因此是必要的。
但是有时候对开发来讲也会不方便,尤其是 ajax 请求。

因些就是有 jsonp 的方案。

从上面的限制列表中可以看到,浏览器的同源策略限制了 Cookie, DOM, Ajax, 但是是没有HTML的一些标签,比如:

1
2
<img src="">
<script src="">

比如 A 站点的地址是 http://example1.com/page/test.html, 页面上的 script 是

1
<script src="http://example2.com/test.js"></script>

是可以请求到数据,浏览器并不做限制。这样就可以执行 example2.com 上的js文件了。

但是我们有时候需要一种理自由的处理example2.com 上的数据,就需要在 example2.com 上写一些接口,比如:
http://example2.com/test.php
比如这个接口返回json: {“result”: “This is test”}.
但是 script 标签对于返回的 json 并没有什么用。而且script 的返回是由js解析器直接执行了。
因此,我们可以直接 js 函数,或者 js 函数的调用,比如:

1
process({"result": "This is test"});

这样我们就实现了在 example1.com 站点上调用 example2.com 上数据的目的。

Mac 下 SSH Clone Session

因为公司用的是 windows 电脑,连接服务器使用的是 SecureCRT. 这个软件有个非常好用的功能,克隆会话。每天只需要第一次连接的时候使用密码,其它时候就直接克隆会话。

但是回家后是使用自己的 Mac, 于是也想实现这样的效果。于是 Google 一下。

发现其实是 ssh config 的一个功能。所以实现过程如果下:

先切到 .ssh 目录。在主目录下,

1
cd .ssh

然后新建一个 config 文件,并且内容为:

1
vi config
1
2
3
host *
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p

然后你在一个终端下用 ssh 登陆服务器,是需要输入密码的,再新建一个终端tab, 再 ssh 到同一个服务,这次就不用输入密码了,自动登陆。
到此,克隆会话完成。

Config 内容解释:

  • host : ssh 的 confg 是按 host 来匹配的。这里星号表示下面的配置适用于所有的主机。

  • ControlMaster : 开启同一个网络连接的 session 共享。这里可以有多个值,auto 表示后面新建的 ssh 连接的时候,如果存在 master 连接,就复用,如果不存在,就新建一个。新建的话是需要输入密码的。

  • ControlPath : ssh 用 ControlPath 参数来表示一个连接。%r 表示 ssh 登陆名。%h 表示登陆主机名。%p 表示 ssh 远程登陆端口。

Git 不能正常显示中文文件名的问题解决方法

一直以来都使用 Git 来管理代码,而代码的文件名都是英文,因此从来没有试过用 Git 来管理中文文件。今天偶然想使用 Git 来管理一些
中文文件,发现原来在 Git 下,运行

1
git status

时中文文件名显示的转义名,就是以 ‘\’ 开头的字符。
于是就查了 Git 的 man 手册。原来 git config 中一项,core.quotePath 造成这样的问题。

大概意思就是 command 中输出的文件路径中,如果有“非正常”字符,将会把这些字符以 C 语言转义控制字符那样转义,并把它们放到双引号民中。

如果 core.quotepath 这个选项的值是 ture, 那么 git 会把字节值大于 0x80 的字符(其实就是非 ASCII 字符)认为是非法字符。
这个选项的默认值是 ture.

所以这个问题的正确解法是,把这个选项设置成 false.

1
git config --global core.quotepath false

再运行一次 git status, 将会看到,中文文件名可以正常显示了。

Python raw 字符串不能以反斜线结尾

Python raw 字符串可以禁止字符串中的反斜线后的字符转义。

尽管 raw 字符串很有用, 但是一个raw 字符串也不能以单个的反斜线结尾, 因为, 反斜线会转义后续引用的字符, 仍然必须转义外围引号字符以将其嵌入到该字符串中. 也就是说, r’..\’ 不是一个有效的字符中常量, 一个raw字符串不能以奇数个反斜线结束.

如果需要用单个的反斜线结束一个raw 字符串, 有几种方法:

可以使用两个反斜线并分片掉第二个反斜线( r’1\nb\tc\‘[:-1])

或手动添加一个反斜线(r’1\nb\tc’ + ‘\‘)

或忽略raw字符串语法并在常规字符口中中把反斜线改为又反斜线( r’1\nb\tc\‘)

以上三种形式都会创建同样的字符串. 字符串的最后包含一个反斜线

Laravel 5.3 中使用Authorizatioin Middleware时,参数不能正确解析

Laravel 5.3 的 Authorization 使用非常方便,一般的用法是在 Controller 中手动调用.

1
2
3
if ($user->can('view', $post)) {

}

这已经非常方便了,但是 Laravel 还提供了一种更方便的方法,使用 Middleware 在 Route 中 authorization.
但是我按官方文档上的说明进行调用,参数总不能正确解析:

1
Route::get('/posts/{post}', 'PostController@show')->middleware('can:view,post');

通过这样调用,Controller 中接到的参数仍然是 post id, 是个数字。

放狗搜了一下才知道,原来是需要先使用 Middleware bings 来对参数进行绑定。代码如下:

1
Route::get('/posts/{post}', 'PostController@show')->middleware('bindings', 'can:view,post');

一试果然管用。

VIM白天用Dark背景,晚上用Light背景

启动vim的时候,根据现在的时间是白天还是晚上,应用不同的背景。
打开.vimrc。

1
2
3
4
5
if strftime("%H") > 6 && strftime("%H") < 18
set background=dark
else
set background=light
endif

这只会在启动vim的时候启用不同的背景。如果是vim从白天打开,到晚上一直没有关,背景也不会有变化。所以下一步我们就是要解决这个问题。
每次用vim写代码,:w用的很多。所以我们在每次保存文件的时候,执行一次~/.vimrc文件。

1
2
3
if has('autocmd')
autocmd bufwritepost * source ~/.vimrc
endif

至此,已经完成。